mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-14 12:11:27 +08:00
fix(gateguard): gate force/path git checkout as destructive (#2158)
* fix(gateguard): gate force/path git checkout as destructive The destructive-command gate's `checkout` handler only flagged `git checkout -- <path>`. It missed `git checkout --force` / `-f <branch>` and `git checkout .`, all of which discard uncommitted working-tree changes, so they bypassed the gate (once the once-per-session routine-Bash gate is satisfied, they ran with no challenge). The sibling `switch` handler already covers these force forms; mirror it for `checkout`. * test(gateguard): document Test 7b force-checkout case --------- Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
This commit is contained in:
@@ -318,7 +318,14 @@ function isDestructiveGit(tokens) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (command === 'checkout') {
|
if (command === 'checkout') {
|
||||||
return rest.includes('--');
|
// `git checkout -- <path>`, `git checkout .`, and the force forms
|
||||||
|
// (`--force` / `-f`) all discard uncommitted working-tree changes,
|
||||||
|
// mirroring the `switch` handler below.
|
||||||
|
return rest.some(t => {
|
||||||
|
if (t === '--' || t === '.' || t === '--force') return true;
|
||||||
|
if (!t.startsWith('-') || t.startsWith('--')) return false;
|
||||||
|
return t.slice(1).includes('f');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command === 'clean') {
|
if (command === 'clean') {
|
||||||
|
|||||||
@@ -301,6 +301,25 @@ function runTests() {
|
|||||||
assert.ok(output.hookSpecificOutput.permissionDecisionReason.includes('rollback'));
|
assert.ok(output.hookSpecificOutput.permissionDecisionReason.includes('rollback'));
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test 7b: `git checkout -f <branch>` (force checkout) discards uncommitted
|
||||||
|
* working-tree changes, so it must be gated as destructive Bash.
|
||||||
|
*/
|
||||||
|
clearState();
|
||||||
|
if (test('denies git checkout -f as destructive Bash', () => {
|
||||||
|
const input = {
|
||||||
|
tool_name: 'Bash',
|
||||||
|
tool_input: { command: 'git checkout -f main' }
|
||||||
|
};
|
||||||
|
const result = runBashHook(input);
|
||||||
|
assert.strictEqual(result.code, 0, 'exit code should be 0');
|
||||||
|
const output = parseOutput(result.stdout);
|
||||||
|
assert.ok(output, 'should produce JSON output');
|
||||||
|
assert.strictEqual(output.hookSpecificOutput.permissionDecision, 'deny');
|
||||||
|
assert.ok(output.hookSpecificOutput.permissionDecisionReason.includes('Destructive'));
|
||||||
|
assert.ok(output.hookSpecificOutput.permissionDecisionReason.includes('rollback'));
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
// --- Test 8: denies first routine Bash, allows second ---
|
// --- Test 8: denies first routine Bash, allows second ---
|
||||||
clearState();
|
clearState();
|
||||||
if (test('denies first routine Bash, allows second', () => {
|
if (test('denies first routine Bash, allows second', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user