fix(hooks): anchor shutdown rule to command position, not any substring

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-08-09 13:02:01 +02:00
commit e1cf545a0c
2 changed files with 39 additions and 1 deletions

View file

@ -105,7 +105,11 @@ const BLOCK_RULES = [
// --- Executor-specific additions ---
{
name: 'System shutdown/reboot',
pattern: /\b(?:shutdown|reboot|halt|poweroff)\b/,
// Anchored to command position — start of string/line, or after a
// separator (`;`, `|`, `&&`, `||`), with optional `sudo` and an optional
// absolute path. An unanchored \b match blocked the bare word anywhere,
// including quoted grep patterns, heredoc data, and commit messages.
pattern: /(?:^|[\n;|]|&&)\s*(?:sudo\s+(?:-[a-zA-Z]+\s+)*)?(?:[\w./-]*\/)?(?:shutdown|reboot|halt|poweroff)\b/,
description: 'System shutdown/reboot commands are blocked during execution.',
},
{

View file

@ -129,6 +129,40 @@ test('pre-bash-executor BLOCKS system shutdown command', async () => {
assert.strictEqual(code, 2);
});
test('pre-bash-executor BLOCKS a privileged halt at command position', async () => {
const { code } = await runHook(PRE_BASH, bashInput('sudo shutdown -h now'));
assert.strictEqual(code, 2);
});
test('pre-bash-executor BLOCKS a destructive keyword after a separator', async () => {
const { code } = await runHook(PRE_BASH, bashInput('echo done && poweroff'));
assert.strictEqual(code, 2);
});
// -----------------------------------------------------------------------
// ALLOW — the same keywords as DATA, not at command position.
// The rule matched the bare word anywhere in the string, so a quoted grep
// pattern, ordinary prose, or a commit message that merely named the rule
// was blocked. Anchoring to command position is what separates the two.
// -----------------------------------------------------------------------
test('pre-bash-executor ALLOWS the keyword inside a quoted grep pattern', async () => {
const { code } = await runHook(PRE_BASH, bashInput("grep 'halt' f.mjs"));
assert.strictEqual(code, 0);
});
test('pre-bash-executor ALLOWS the keyword inside echoed prose', async () => {
const { code } = await runHook(PRE_BASH, bashInput('echo "we should halt here"'));
assert.strictEqual(code, 0);
});
test('pre-bash-executor ALLOWS a commit message that names the rule', async () => {
const { code } = await runHook(
PRE_BASH,
bashInput('git commit -m "fix(hooks): anchor shutdown rule to command position"'),
);
assert.strictEqual(code, 0);
});
// -----------------------------------------------------------------------
// BLOCK — cron persistence
// -----------------------------------------------------------------------