fix(security): unconditional hard-block for critical destructive commands (sable-nf2i) - #181
Merged
Merged
Conversation
…ands (sable-nf2i)
CommandInterceptor.evaluate() returned {allowed:true, requiresApproval:false}
for critical destructive commands (rm -rf /, fork bombs, dd to disk, mkfs, …)
whenever no commandPolicy was configured — which is the real-world default
(the user's ~/.rafter/config.json often omits agent.commandPolicy). The pretool
hook only denies on (!allowed && !requiresApproval) || requiresApproval, so
allowed=true+approval=false sailed straight through: a security CLI that did not
block `rm -rf /` by default.
Fix: add an UNCONDITIONAL critical-destructive hard-block at the very top of
evaluate(), before any policy is loaded, in both implementations. Critical
commands now always return allowed=false, requiresApproval=false — independent
of whether a policy exists or which mode is set (allow-all and custom deny-lists
can no longer opt out of catastrophic commands). With no policy configured,
high-risk commands now fall back to requiring approval instead of allow-all.
- node/src/core/risk-rules.ts: add matchedCriticalPattern() helper
- python/rafter_cli/core/risk_rules.py: add match_critical_pattern() helper
- both interceptors: hard-block keyed on assessCommandRisk()=="critical",
surfacing the matched built-in pattern; exact node/python parity verified
(16/16 commands identical under the same config)
Updated the policy-mode tests that encoded the old (insecure) contract
(allow-all / custom deny-list / approve-dangerous permitting critical commands)
to assert the hard-block. Repairs the 13 red command-interceptor.test.ts cases,
the hook-integration "blocks rm -rf / even in a git repo" case, and the two
error-handling-gauntlet interceptor cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes sable-nf2i (P1 security):
CommandInterceptor.evaluate()returned{allowed:true, requiresApproval:false, riskLevel:'critical'}for catastrophic destructive commands (rm -rf /,rm -fr /, fork bombs,ddto disk,mkfs, …) whenever nocommandPolicywas configured — the real-world default, since a user's~/.rafter/config.jsonoften omitsagent.commandPolicy.The pretool hook denies only when
(!allowed && !requiresApproval) || requiresApproval, soallowed=true+requiresApproval=falsebypassed the hard-block → a security CLI that did not blockrm -rf /by default.Fix
Add an unconditional critical-destructive hard-block at the very top of
evaluate(), before any policy is loaded, in both implementations:allowed:false, requiresApproval:false, surfacing the matched built-in pattern — regardless of whether a policy exists or which mode is set.allow-alland custom deny-lists can no longer opt out of catastrophic commands (closing the trivial bypass).matchedCriticalPattern()/match_critical_pattern()report which built-in rule matched.Contract change (intentional)
Previously
allow-allmode (and custom deny-lists that replaced defaults, andapprove-dangerousfor critical) permitted / merely-promptedrm -rf /. Now critical-destructive commands are always hard-blocked. The policy-mode tests that encoded the old behavior were updated to assert the hard-block.Verification
command-interceptor.test.tscases, thehook-integration"blocks rm -rf / even in a git repo" case, and the twoerror-handling-gauntletinterceptor cases.Security gate (per CLAUDE.md)
rafter-code-reviewwalked (CWE Top-25 / CLI): no new findings; surfaced 2 pre-existing items (sable-619t over-block, sable-c3w1 config-default parity gap).rafter secretsclean on source changes.rafter run(remote SAST/SCA) not run locally —RAFTER_API_KEYnot set in this env. Please ensure CI runs it on this PR.Closes sable-nf2i.
🤖 Generated with Claude Code