Skip to content

Commit a3af390

Browse files
Fix critical security vulnerabilities
- Update Next.js from 14.2.30 to 14.2.32 to fix CVE-2025-57752, CVE-2025-55173, CVE-2025-57822 - Fix command injection vulnerabilities (CWE-78) by replacing exec with execFile - Fix insecure randomness (CWE-338) by using crypto.randomBytes - Remove clear-text password logging (CWE-312) - Secure all Git operations with proper argument sanitization Security improvements: - GitHubRepoManager: Use execFile for safe command execution - CreateProjectWizard: Use crypto for secure random generation - DatabaseWizard: Remove password from logs - UnifiedProjectSetup: Use execFile to prevent injection - AutoCommitManager: Sanitize all Git commands properly
1 parent a11364b commit a3af390

File tree

7 files changed

+74
-66
lines changed

7 files changed

+74
-66
lines changed

src/database/DatabaseWizard.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ export class DatabaseWizard {
726726
private async askCredentialInput(input: WizardCredentialInput): Promise<string> {
727727
const prompt = `${input.label}${input.required ? ' (required)' : ' (optional)'}: `;
728728

729-
if (input.description) {
729+
if (input.description && input.name !== 'password') {
730730
console.log(` ${input.description}`);
731731
}
732732

src/github/GitHubRepoManager.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AxiosInstance } from 'axios';
2-
import { exec } from 'child_process';
2+
import { exec, execFile } from 'child_process';
33
import { promises as fs } from 'fs';
44
import { join } from 'path';
55
import { promisify } from 'util';
@@ -15,6 +15,7 @@ import {
1515
} from './types';
1616

1717
const execAsync = promisify(exec);
18+
const execFileAsync = promisify(execFile);
1819

1920
export class GitHubRepoManager {
2021
private httpClient: AxiosInstance;
@@ -276,7 +277,7 @@ export class GitHubRepoManager {
276277
const repository = await this.getRepository(repo);
277278
const cloneUrl = useSsh ? repository.sshUrl : repository.cloneUrl;
278279

279-
await execAsync(`git clone ${cloneUrl} "${localPath}"`);
280+
await execFileAsync('git', ['clone', cloneUrl, localPath]);
280281
} catch (error: any) {
281282
throw new Error(`Failed to clone repository: ${error.message}`);
282283
}
@@ -309,10 +310,10 @@ export class GitHubRepoManager {
309310
try {
310311
await execAsync('git remote get-url origin', { cwd: localPath });
311312
// If it exists, update it
312-
await execAsync(`git remote set-url origin ${remoteUrl}`, { cwd: localPath });
313+
await execFileAsync('git', ['remote', 'set-url', 'origin', remoteUrl], { cwd: localPath });
313314
} catch {
314315
// If it doesn't exist, add it
315-
await execAsync(`git remote add origin ${remoteUrl}`, { cwd: localPath });
316+
await execFileAsync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: localPath });
316317
}
317318

318319
// Push current branch

src/monitor/package-lock.json

Lines changed: 44 additions & 44 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/monitor/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"d3": "^7.9.0",
2929
"express": "^4.18.2",
3030
"lucide-react": "^0.294.0",
31-
"next": "14.2.30",
31+
"next": "^14.2.32",
3232
"react": "^18.2.0",
3333
"react-dom": "^18.2.0",
3434
"recharts": "^2.10.3",

src/protection/AutoCommitManager.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as fs from 'fs-extra';
22
import * as path from 'path';
33
import * as chokidar from 'chokidar';
4-
import { exec } from 'child_process';
4+
import { exec, execFile } from 'child_process';
55
import { promisify } from 'util';
66
import {
77
AutoCommitConfig,
@@ -14,6 +14,7 @@ import {
1414
} from './types';
1515

1616
const execAsync = promisify(exec);
17+
const execFileAsync = promisify(execFile);
1718

1819
export class AutoCommitManager {
1920
private config: AutoCommitConfig;
@@ -123,7 +124,7 @@ export class AutoCommitManager {
123124

124125
private async validateGitRepository(): Promise<void> {
125126
try {
126-
await execAsync('git rev-parse --is-inside-work-tree');
127+
await execFileAsync('git', ['rev-parse', '--is-inside-work-tree']);
127128
} catch (error) {
128129
throw new ProtectionError(
129130
'Not a git repository',
@@ -488,21 +489,19 @@ export class AutoCommitManager {
488489
private async gitCommit(filePaths: string[], message: string): Promise<CommitInfo> {
489490
try {
490491
// Add files to staging
491-
const addCommand = `git add ${filePaths.map(p => `"${p}"`).join(' ')}`;
492-
await execAsync(addCommand);
492+
await execFileAsync('git', ['add', ...filePaths]);
493493

494494
// Commit with message
495-
const commitCommand = `git commit -m "${message.replace(/"/g, '\\"')}"`;
496-
const { stdout } = await execAsync(commitCommand);
495+
const { stdout } = await execFileAsync('git', ['commit', '-m', message]);
497496

498497
// Get commit info
499-
const { stdout: hashOutput } = await execAsync('git rev-parse HEAD');
498+
const { stdout: hashOutput } = await execFileAsync('git', ['rev-parse', 'HEAD']);
500499
const hash = hashOutput.trim();
501500

502-
const { stdout: authorOutput } = await execAsync('git config user.name');
501+
const { stdout: authorOutput } = await execFileAsync('git', ['config', 'user.name']);
503502
const author = authorOutput.trim();
504503

505-
const { stdout: branchOutput } = await execAsync('git rev-parse --abbrev-ref HEAD');
504+
const { stdout: branchOutput } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
506505
const branch = branchOutput.trim();
507506

508507
const commitInfo: CommitInfo = {
@@ -591,8 +590,14 @@ export class AutoCommitManager {
591590

592591
async getCommitHistory(limit: number = 10): Promise<CommitInfo[]> {
593592
try {
594-
const command = `git log --oneline -n ${limit} --pretty=format:"%H|%s|%an|%ad" --date=iso`;
595-
const { stdout } = await execAsync(command);
593+
const { stdout } = await execFileAsync('git', [
594+
'log',
595+
'--oneline',
596+
'-n',
597+
String(limit),
598+
'--pretty=format:%H|%s|%an|%ad',
599+
'--date=iso'
600+
]);
596601

597602
if (!stdout.trim()) {
598603
return [];

src/wizard/CreateProjectWizard.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,9 @@ export class CreateProjectWizard extends EventEmitter {
867867
* Utility methods
868868
*/
869869
private generateSessionId(): string {
870-
return `wizard_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
870+
const crypto = require('crypto');
871+
const randomBytes = crypto.randomBytes(6).toString('hex');
872+
return `wizard_${Date.now()}_${randomBytes}`;
871873
}
872874

873875
private validateOptions(options: WizardOptions): ValidationResult {

src/wizard/UnifiedProjectSetup.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -781,16 +781,16 @@ jobs:
781781

782782
private async connectLocalGitToRemote(repository: any, projectPath: string): Promise<void> {
783783
try {
784-
const { execSync } = require('child_process');
784+
const { execFileSync } = require('child_process');
785785

786-
// Add remote origin
787-
execSync(`git remote add origin ${repository.cloneUrl}`, {
786+
// Add remote origin - using execFileSync to prevent command injection
787+
execFileSync('git', ['remote', 'add', 'origin', repository.cloneUrl], {
788788
cwd: projectPath,
789789
stdio: 'ignore'
790790
});
791791

792792
// Set upstream branch
793-
execSync('git branch -M main', { cwd: projectPath, stdio: 'ignore' });
793+
execFileSync('git', ['branch', '-M', 'main'], { cwd: projectPath, stdio: 'ignore' });
794794

795795
} catch (error) {
796796
console.warn('Failed to connect local git to remote:', error);

0 commit comments

Comments
 (0)