fix(bash-policy): bind group denials in every mode and screen only executed text - #53
zhanghanduo wants to merge 5 commits into
Conversation
…ecuted text Syncs the behavior of ApodexHarness #497, #503 and #631 into Frontier's own policy (not a cherry-pick; the parsers had diverged): - Layer 1.5: privilege escalation, remote/exfil clients and signal senders are denied in every mode, including the default `off`. An interactive caller (the apodex CLI) gets them as a group-tagged `confirm` that only a human answering that prompt can approve. - Mode resolution: an unknown mode warns instead of silently falling to `off`; ExecutionScope metadata can only tighten the mode. - One substitution scanner feeds masking, extraction and top-level splitting, with arithmetic expansions told apart and unterminated expansions fail-closed. - Layer-1 word screens run on executed-text views: quoted data and non-shell heredoc bodies are blanked for known data consumers; shell -c / heredocs / stdin, evaluators, find -exec and systemctl shutdown units stay screened. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… $'...' Review follow-up on #53. Three forms ran a command the always-denied group check (Layer 1.5) never saw, so `off` mode allowed them: - evaluator payloads (`watch 'sudo id'`, `script -c`, `tmux new`, `screen`, `ssh host '...'`) are now parsed as nested commands; an evaluator whose argument form is not recognised has every word checked instead of guessed. - `env -S` / `--split-string` payloads are parsed as the command env runs. - `$'...'` ANSI-C quoting is decoded into plain quoting before any scanner, instead of shlex reading `$'sudo id'` as the executable `$sudo`. All three also reproduced on Harness HEAD (sandbox_full / off); they were inherited gaps, not porting errors. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
All three findings are valid and are fixed in 5428172 ( Origin. I checked the same 8 commands in
Regression tests (
Verification: full These gaps also exist in Harness. I'll report them to the Harness side separately. |
`env -S '-i sudo id'` (the shebang idiom `env -S -i python3`) put env's own options at the start of the split string, so the nested parse read `-i` as the executable and Layer 1.5 never saw `sudo`. The payload is now re-parsed as an `env` command line, which skips those options the same way the outer one does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Handoff item 2 (Harness → FrontierAgent sync): ports the behavior of internal ApodexHarness fixes into
plugins/tools/_bash_policy.py. Independent of #52.Frontier's parser had diverged from Harness (it has its own redirect-sentinel tokenizer, no product tiers), so this ports the behavior, and the Harness regression suites along with it. It is not a cherry-pick.
Bugs reproduced from the handoff (checked only, never executed)
sudo id(modeoff)printf '%s\n' 'halt'Ported behavior
#497 (partial, adapted)
_argv_group_deny:priv_esc,exfil,process_killare denied in every mode. Before, they were only checked by Layer 2, which the defaultoffmode never reaches. The_DENIED_BINARIESkeys and messages are unchanged; the table is now built from named groups.interactive=True(passed only by calling code, never by config or a request) turns a group hit intoconfirmwithgroupset. apodex's gate maps this tomust_ask: it needs a typedyes, andauto_for_meand saved allow rules cannot downgrade it. Under auto-approve (-y/ bypass) it is blocked with an explanation instead of running.set_policy_modewith an unknown value now logs a warning; before, it silently fell back tooff. ExecutionScopebash_allowlist_modecan only tighten the mode.env -u X,timeout --signal TERM 5) are skipped when resolving the real command.#503
_find_expansion_end/_substitution_spans) now feeds masking, extraction and_split_top_level. This fixes the quoted-), apostrophe-in-"…"and unterminated-$(bypasses.$((…))is arithmetic, not a command. A substitution nested inside it is still assessed.enforce; the internal sentinels never leak into reasons.#631
mkfs,dd of=/dev, fork bomb) run on_raw_screen_views. Quoted args and non-shell heredoc bodies are blanked only for known data consumers. Shell-c/-lc, shell heredocs, stdin into a shell,at/batch, evaluators (watch/tmux/screen/script/ssh/su…),find -exec, write-then-run scripts andsystemctlshutdown-unit activation are still screened. Any other command falls back to screening the whole text.<<-,<<as a shift in$[..]/${..}/subscripts, and unterminated heredocs are fail-closed.bash -lc '…'payloads are now parsed. This also closes thebash -lc 'rm -rf /'gap that was noted in #631.echo x > "/dev/sda"is caught.Not ported (product differences)
sandbox_full/sandbox_guarded/native), their derivation from sandbox state, and theratchet_tierwiring. Frontier's sandboxed workflows already pinenforce, which is roughlyguarded. The apodex CLI keeps its own human gate, so there is no container "full" tier to unlock.DROP TABLErule keeps screening the whole text, as in #631, so SQL in documentation can still be refused.Verification (this repo)
tests/test_bash_policy.py(520 cases) plus the Harnesscomposition/data_consumers/consumer_auditsuites ported with the tier dimension removed: 1616 cases. Against the old policy, 372 of these fail; against this branch, all pass.apodex/tests/test_features.py(must-ask, typed confirmation, auto-approve block, hard deny still wins).pytest: 3937 passed, 1 skipped.pyright: 0 errors.ruff check: 13 errors, the same asmain(all pre-existing).