Skip to content

Commit 35f692a

Browse files
authored
Merge pull request #56 from OpenForgeProject/fix-code-scanning
Fix code scanning issues
2 parents d8041a6 + e54c64a commit 35f692a

File tree

8 files changed

+145
-78
lines changed

8 files changed

+145
-78
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ on:
66
pull_request:
77
branches: [ main, develop ]
88

9+
permissions:
10+
contents: read
11+
pull-requests: read
12+
913
jobs:
1014
test:
1115
strategy:

.github/workflows/code-quality.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ on:
66
pull_request:
77
branches: [ main, develop ]
88

9+
permissions:
10+
contents: read
11+
pull-requests: read
12+
913
jobs:
1014
lint:
1115
runs-on: ubuntu-latest

.github/workflows/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ on:
44
pull_request:
55
types: [opened, synchronize]
66

7+
permissions:
8+
pull-requests: write
9+
contents: write
10+
checks: read
11+
statuses: read
12+
713
jobs:
814
auto-approve:
915
runs-on: ubuntu-latest

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"license": "GPL-3.0",
88
"icon": "assets/icon.png",
99
"engines": {
10-
"vscode": "^1.100.0"
10+
"vscode": "^1.108.0"
1111
},
1212
"categories": [
1313
"Linters",

src/services/phpstan-service.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,32 +55,35 @@ export class PhpstanService extends BasePhpToolService {
5555
/**
5656
* Build PHPStan command with configuration
5757
*/
58-
protected buildToolCommand(filePath: string): string {
59-
let command = 'phpstan analyze --error-format=json --no-progress';
58+
protected buildToolCommand(filePath: string): string[] {
59+
const command = ['phpstan', 'analyze', '--error-format=json', '--no-progress'];
6060

6161
// Use config file if specified, otherwise use individual settings
6262
if (this.config.configPath) {
6363
// Use specified config file
64-
command += ` -c "${this.config.configPath}"`;
64+
command.push('-c', this.config.configPath);
6565
} else {
6666
// Try to auto-detect common PHPStan config files
6767
const autoConfigPath = this.detectPhpstanConfig();
6868
if (autoConfigPath) {
6969
console.log(`PHPStan: Auto-detected config file: ${autoConfigPath}`);
70-
command += ` -c "${autoConfigPath}"`;
70+
command.push('-c', autoConfigPath);
7171
} else {
7272
// Use individual settings when no config file is found
73-
command += ` --level=${this.config.level}`;
73+
command.push('--level', String(this.config.level));
7474

7575
// Add exclude paths
7676
if (this.config.excludePaths && this.config.excludePaths.length > 0) {
7777
for (const excludePath of this.config.excludePaths) {
78-
command += ` --exclude="${excludePath}"`;
78+
command.push('--exclude', excludePath);
7979
}
8080
}
8181
}
8282
}
8383

84+
// Add the file to analyze
85+
command.push(filePath);
86+
8487
return command;
8588
}
8689

src/shared/services/base-php-tool-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export abstract class BasePhpToolService {
6565
/**
6666
* Build the command to execute the tool
6767
*/
68-
protected abstract buildToolCommand(relativePath: string): string;
68+
protected abstract buildToolCommand(relativePath: string): string[];
6969

7070
/**
7171
* Process the tool output and convert to diagnostics
@@ -193,7 +193,7 @@ export abstract class BasePhpToolService {
193193
try {
194194
// PHPStan returns exit code 1 when errors are found, which is normal
195195
const allowedExitCodes = this.toolName === 'phpstan' ? [0, 1] : [0];
196-
console.log(`Executing ${this.displayName} command: ${toolCommand}`);
196+
console.log(`Executing ${this.displayName} command: ${toolCommand.join(' ')}`);
197197

198198
const output = DdevUtils.execDdev(toolCommand, workspaceFolder.uri.fsPath, allowedExitCodes);
199199
console.log(`${this.displayName} output length: ${output.length} characters`);

src/shared/utils/ddev-utils.ts

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,17 @@
1919
*/
2020

2121
import * as vscode from 'vscode';
22-
import { execSync } from 'child_process';
22+
import * as cp from 'child_process';
23+
import * as fs from 'fs';
24+
import * as path from 'path';
25+
26+
/**
27+
* System calls wrapper for testability
28+
*/
29+
export const sys = {
30+
spawnSync: cp.spawnSync,
31+
existsSync: fs.existsSync
32+
};
2333

2434
/**
2535
* DDEV project validation result
@@ -46,11 +56,8 @@ export class DdevUtils {
4656
*/
4757
public static hasDdevProject(workspacePath: string): boolean {
4858
try {
49-
const ddevConfig = execSync('test -f .ddev/config.yaml && echo "exists"', {
50-
cwd: workspacePath,
51-
encoding: 'utf-8'
52-
});
53-
return ddevConfig.includes('exists');
59+
const configPath = path.join(workspacePath, '.ddev', 'config.yaml');
60+
return sys.existsSync(configPath);
5461
} catch (error) {
5562
return false;
5663
}
@@ -64,11 +71,23 @@ export class DdevUtils {
6471
*/
6572
public static isDdevRunning(workspacePath: string): boolean {
6673
try {
67-
execSync('ddev exec echo "test"', {
74+
const result = sys.spawnSync('ddev', ['exec', 'echo', 'test'], {
6875
cwd: workspacePath,
69-
stdio: 'ignore'
76+
encoding: 'utf-8'
7077
});
71-
return true;
78+
if (result.error) {
79+
vscode.window.showErrorMessage(
80+
'Failed to execute "ddev". Please ensure DDEV is installed and available in your PATH.',
81+
);
82+
return false;
83+
}
84+
if (result.status === null) {
85+
vscode.window.showErrorMessage(
86+
'The "ddev" command did not exit normally. Please check your DDEV installation.',
87+
);
88+
return false;
89+
}
90+
return result.status === 0;
7291
} catch (error) {
7392
return false;
7493
}
@@ -83,7 +102,7 @@ export class DdevUtils {
83102
*/
84103
public static isToolInstalled(toolName: string, workspacePath: string): boolean {
85104
try {
86-
this.execDdev(`${toolName} --version`, workspacePath);
105+
this.execDdev([toolName, '--version'], workspacePath);
87106
return true;
88107
} catch (error) {
89108
return false;
@@ -109,7 +128,7 @@ export class DdevUtils {
109128

110129
// Try to run the tool
111130
try {
112-
this.execDdev(`${toolName} --version`, workspacePath);
131+
this.execDdev([toolName, '--version'], workspacePath);
113132

114133
return {
115134
isValid: true
@@ -174,37 +193,60 @@ export class DdevUtils {
174193
/**
175194
* Execute a command in the DDEV container
176195
*
177-
* @param command Command to execute
196+
* @param command Command to execute (as array of strings)
178197
* @param workspacePath Path to the workspace
179198
* @param allowedExitCodes Array of exit codes that should not throw (default: [0])
180199
* @returns Output of the command
181200
* @throws Error if the command fails with disallowed exit code
182201
*/
183-
public static execDdev(command: string, workspacePath: string, allowedExitCodes: number[] = [0]): string {
202+
public static execDdev(command: string[], workspacePath: string, allowedExitCodes: number[] = [0]): string {
184203
try {
185-
// Wrap command in bash -c to allow setting environment variables (specifically disabling Xdebug)
186-
// This fixes issues where Xdebug causes the command to hang or run slowly
187-
// We use single quotes for the bash command and escape any single quotes in the original command
188-
const escapedCommand = command.replace(/'/g, "'\\''");
189-
return execSync(`ddev exec bash -c 'XDEBUG_MODE=off ${escapedCommand}'`, {
204+
// Use spawnSync to avoid shell injection and safely pass arguments
205+
// We use 'env' to set environment variables inside the container
206+
const args = ['exec', 'env', 'XDEBUG_MODE=off', ...command];
207+
208+
const result = sys.spawnSync('ddev', args, {
190209
cwd: workspacePath,
191210
encoding: 'utf-8'
192211
});
193-
} catch (error: any) {
194-
// Check if this is an acceptable exit code (e.g., PHPStan returns 1 when errors are found)
195-
if (error.status && allowedExitCodes.includes(error.status)) {
212+
213+
if (result.error) {
214+
throw result.error;
215+
}
216+
217+
// Check if this is an acceptable exit code
218+
if (result.status !== null && allowedExitCodes.includes(result.status)) {
196219
// Return stdout even if exit code is non-zero but allowed
197-
console.log(`Command exited with allowed code ${error.status}: ${command}`);
198-
return error.stdout || '';
220+
if (result.status !== 0) {
221+
console.log(`Command exited with allowed code ${result.status}: ${command.join(' ')}`);
222+
}
223+
return result.stdout || '';
224+
}
225+
226+
if (result.status !== 0) {
227+
// Enhance error message with more details
228+
const enhancedError = new Error(result.stderr || 'Command execution failed');
229+
enhancedError.name = 'CommandError';
230+
(enhancedError as any).status = result.status;
231+
(enhancedError as any).stderr = result.stderr;
232+
(enhancedError as any).stdout = result.stdout;
233+
(enhancedError as any).command = `ddev exec ${command.join(' ')}`;
234+
(enhancedError as any).workspacePath = workspacePath;
235+
throw enhancedError;
236+
}
237+
} catch (error: any) {
238+
// If error was already thrown above, rethrow it
239+
if (error.name === 'CommandError') {
240+
throw error;
199241
}
200242

201-
// Enhance error message with more details
243+
// Handle unexpected errors
202244
const enhancedError = new Error(error.message || 'Command execution failed');
203245
enhancedError.name = error.name || 'CommandError';
204246
(enhancedError as any).status = error.status;
205247
(enhancedError as any).stderr = error.stderr;
206248
(enhancedError as any).stdout = error.stdout;
207-
(enhancedError as any).command = `ddev exec ${command}`;
249+
(enhancedError as any).command = `ddev exec ${command.join(' ')}`;
208250
(enhancedError as any).workspacePath = workspacePath;
209251

210252
throw enhancedError;

0 commit comments

Comments
 (0)