mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
fix(hooks): add Windows .cmd support with shell injection guard
Handle Windows .cmd shim resolution via spawnSync with strict path validation. Removes shell:true injection risk, uses strict equality, and restores .cmd support with path injection guard.
This commit is contained in:
@@ -17,9 +17,12 @@
|
||||
* Fails silently if no formatter is found or installed.
|
||||
*/
|
||||
|
||||
const { execFileSync } = require('child_process');
|
||||
const { execFileSync, spawnSync } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
// Shell metacharacters that cmd.exe interprets as command separators/operators
|
||||
const UNSAFE_PATH_CHARS = /[&|<>^%!]/;
|
||||
|
||||
const { findProjectRoot, detectFormatter, resolveFormatterBin } = require('../lib/resolve-formatter');
|
||||
|
||||
const MAX_STDIN = 1024 * 1024; // 1MB limit
|
||||
@@ -50,11 +53,29 @@ function run(rawInput) {
|
||||
// Prettier: `--write` = format only
|
||||
const args = formatter === 'biome' ? [...resolved.prefix, 'check', '--write', resolvedFilePath] : [...resolved.prefix, '--write', resolvedFilePath];
|
||||
|
||||
execFileSync(resolved.bin, args, {
|
||||
cwd: projectRoot,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
timeout: 15000
|
||||
});
|
||||
if (process.platform === 'win32' && resolved.bin.endsWith('.cmd')) {
|
||||
// Windows: .cmd files require shell to execute. Guard against
|
||||
// command injection by rejecting paths with shell metacharacters.
|
||||
if (UNSAFE_PATH_CHARS.test(resolvedFilePath)) {
|
||||
throw new Error('File path contains unsafe shell characters');
|
||||
}
|
||||
const result = spawnSync(resolved.bin, args, {
|
||||
cwd: projectRoot,
|
||||
shell: true,
|
||||
stdio: 'pipe',
|
||||
timeout: 15000
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
if (typeof result.status === 'number' && result.status !== 0) {
|
||||
throw new Error(result.stderr?.toString() || `Formatter exited with status ${result.status}`);
|
||||
}
|
||||
} else {
|
||||
execFileSync(resolved.bin, args, {
|
||||
cwd: projectRoot,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
timeout: 15000
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Formatter not installed, file missing, or failed — non-blocking
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ async function main() {
|
||||
if (hookModule && typeof hookModule.run === 'function') {
|
||||
try {
|
||||
const output = hookModule.run(raw);
|
||||
if (output != null) process.stdout.write(output);
|
||||
if (output !== null && output !== undefined) process.stdout.write(output);
|
||||
} catch (runErr) {
|
||||
process.stderr.write(`[Hook] run() error for ${hookId}: ${runErr.message}\n`);
|
||||
process.stdout.write(raw);
|
||||
|
||||
@@ -86,7 +86,7 @@ function detectFormatter(projectRoot) {
|
||||
const pkgPath = path.join(projectRoot, 'package.json');
|
||||
if (fs.existsSync(pkgPath)) {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
if (pkg.prettier != null) {
|
||||
if ('prettier' in pkg) {
|
||||
formatterCache.set(projectRoot, 'prettier');
|
||||
return 'prettier';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user