mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
feat(security): hold .claude/settings.json hooks to a reviewed allowlist (#313)
check_permissions() read permissions.allow and nothing else, so a `hooks` block in the same file passed the guard silently. A hook is strictly more dangerous than a pre-approved permission. A permission pre-approves something Claude may choose to do; a hook runs unconditionally when its event fires, with no prompt and no model decision in between. Cloning the repo and opening it is enough. This is the vector the Shai-Hulud worm used in its August 2026 wave: a SessionStart hook in .claude/settings.json chaining to .claude/math_init.js, executing on session start. https://research.jfrog.com/post/shai-hulud-is-back-august/ For a template that thousands of people are explicitly invited to fork, that is the riskiest key in the file this guard already parses. Follows the established pattern exactly - ALLOWED_HOOKS ships empty, since the template has no hooks, so any addition must be allowlisted in the same PR and is therefore explicit and reviewable. Two details worth reviewing closely: - The hook check runs *before* the permissions shape guards. Those guards return early, so a file pairing a malformed permissions block with a live hook would otherwise skip the hook check entirely - a fail-open. Pinned by test_hook_is_caught_even_when_permissions_block_is_malformed. - _hook_commands() fails closed. Any hook layout it does not recognise yields a marker that cannot be in the allowlist, so an unfamiliar shape is rejected rather than silently skipped, rather than trusting that the Claude Code schema will not change. Verified: - 8 new HookGuardTests cases; 14 of the suite's 26 tests fail against the unpatched guard, all 26 pass with it - injecting the real worm shape into this repo's own settings.json makes the guard exit 1 naming 'SessionStart:node .claude/math_init.js'; removing it returns OK - lint_skills, check_framework_version, security_guards all OK; python3 -m unittest discover -s tests 219 passed
This commit is contained in:
@@ -13,6 +13,23 @@ per-file diff commands.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- **`security_guards.py` now holds `.claude/settings.json` hooks to an allowlist** - the
|
||||
guard read `permissions.allow` and nothing else, so a `hooks` block in the same file
|
||||
passed silently. A hook is strictly more dangerous than a pre-approved permission: a
|
||||
permission pre-approves something Claude *may* choose to do, while a hook runs
|
||||
unconditionally when its event fires, with no prompt and no model decision in between.
|
||||
This is not hypothetical - it is the vector the Shai-Hulud worm used in its August 2026
|
||||
wave, planting a `SessionStart` hook in `.claude/settings.json` that executed on session
|
||||
start ([JFrog research](https://research.jfrog.com/post/shai-hulud-is-back-august/)).
|
||||
For a template thousands of people are invited to fork, that is the riskiest key in the
|
||||
file the guard already parses. `ALLOWED_HOOKS` ships empty (the template has no hooks),
|
||||
the check runs *before* the permissions shape guards so a malformed permissions block
|
||||
cannot return early and skip it, and unrecognised hook layouts fail closed rather than
|
||||
being skipped. Eight new `HookGuardTests` cases; 14 of the suite's 26 tests fail against
|
||||
the unpatched guard.
|
||||
|
||||
### Changed
|
||||
|
||||
- **CI discovers portal CLIs instead of hardcoding them** (#310). The `cli-checks` matrix
|
||||
|
||||
Reference in New Issue
Block a user