security: Fix critical vulnerabilities in worktree init script feature

Fix multiple command injection and security vulnerabilities in the worktree
initialization script system:

**Critical Fixes:**
- Add branch name validation to prevent command injection in create/delete endpoints
- Replace string interpolation with array-based command execution using spawnProcess
- Implement safe environment variable allowlist to prevent credential exposure
- Add script content validation with 1MB size limit and dangerous pattern detection

**Code Quality:**
- Centralize execGitCommand helper in common.ts using @automaker/platform's spawnProcess
- Remove duplicate isGitRepo implementation, standardize imports to @automaker/git-utils
- Follow DRY principle by reusing existing platform utilities
- Add comprehensive JSDoc documentation with security examples

This addresses 6 critical/high severity vulnerabilities identified in security audit:
1. Command injection via unsanitized branch names (delete.ts)
2. Command injection via unsanitized branch names (create.ts)
3. Missing branch validation in init script execution
4. Environment variable exposure (ANTHROPIC_API_KEY and other secrets)
5. Path injection via command substitution
6. Arbitrary script execution without content limits

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Shirone
2026-01-11 01:14:07 +01:00
parent 385e7f5c1e
commit 8ed2fa07a0
6 changed files with 165 additions and 52 deletions

View File

@@ -3,15 +3,51 @@
*/
import { createLogger } from '@automaker/utils';
import { spawnProcess } from '@automaker/platform';
import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
import { FeatureLoader } from '../../services/feature-loader.js';
const logger = createLogger('Worktree');
export const execAsync = promisify(exec);
const featureLoader = new FeatureLoader();
// ============================================================================
// Secure Command Execution
// ============================================================================
/**
* Execute git command with array arguments to prevent command injection.
* Uses spawnProcess from @automaker/platform for secure, cross-platform execution.
*
* @param args - Array of git command arguments (e.g., ['worktree', 'add', path])
* @param cwd - Working directory to execute the command in
* @returns Promise resolving to stdout output
* @throws Error with stderr message if command fails
*
* @example
* ```typescript
* // Safe: no injection possible
* await execGitCommand(['branch', '-D', branchName], projectPath);
*
* // Instead of unsafe:
* // await execAsync(`git branch -D ${branchName}`, { cwd });
* ```
*/
export async function execGitCommand(args: string[], cwd: string): Promise<string> {
const result = await spawnProcess({
command: 'git',
args,
cwd,
});
// spawnProcess returns { stdout, stderr, exitCode }
if (result.exitCode === 0) {
return result.stdout;
} else {
const errorMessage = result.stderr || `Git command failed with code ${result.exitCode}`;
throw new Error(errorMessage);
}
}
// ============================================================================
// Constants
@@ -99,18 +135,6 @@ export function normalizePath(p: string): string {
return p.replace(/\\/g, '/');
}
/**
* Check if a path is a git repo
*/
export async function isGitRepo(repoPath: string): Promise<boolean> {
try {
await execAsync('git rev-parse --is-inside-work-tree', { cwd: repoPath });
return true;
} catch {
return false;
}
}
/**
* Check if a git repository has at least one commit (i.e., HEAD exists)
* Returns false for freshly initialized repos with no commits