Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions hook-scripts/post-tool-use/auto-stage.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { execSync, spawnSync } = require('child_process');

const LOG_DIR = path.join(process.env.HOME, '.claude', 'hooks-logs');

Expand All @@ -49,7 +49,11 @@ function isInGitRepo(filePath) {
function stageFile(filePath) {
try {
const dir = path.dirname(filePath);
execSync(`git add "${filePath}"`, { cwd: dir, stdio: 'pipe' });
const result = spawnSync('git', ['add', '--', filePath], { cwd: dir, stdio: 'pipe' });
if (result.status !== 0) {
const stderr = result.stderr ? result.stderr.toString() : '';
return { success: false, error: stderr || `exit code ${result.status}` };
}
return { success: true };
} catch (e) {
return { success: false, error: e.message };
Expand Down
22 changes: 22 additions & 0 deletions hook-scripts/tests/post-tool-use/auto-stage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,28 @@ describe('Unit: stageFile()', () => {
const result = stageFile(path.join(tempDir, 'nonexistent.txt'));
assert.ok('success' in result);
});

it('does not execute shell commands embedded in filenames', () => {
// A filename containing shell metacharacters must be treated as a literal
// path, not executed. With the old execSync(`git add "${filePath}"`), a
// name like: evil"; touch marker; echo "x would break out of the quotes
// and run `touch marker` with cwd=tempDir.
const markerName = `injection-marker-${Date.now()}`;
const markerPath = path.join(tempDir, markerName);
// Use only characters valid in a Unix filename (no slashes).
// The injected command touches markerName relative to cwd (tempDir).
const maliciousName = `evil"; touch ${markerName}; echo "x.txt`;
const maliciousFile = path.join(tempDir, maliciousName);
fs.writeFileSync(maliciousFile, 'payload');

stageFile(maliciousFile);

assert.strictEqual(
fs.existsSync(markerPath),
false,
`Shell injection succeeded β€” marker file was created at ${markerPath}`
);
});
});

// ─────────────────────────────────────────────────────────────────────────────
Expand Down