emrg: brotli keeps its operand and derives a sibling, so its own rule names the path (#1420) - #1528
Conversation
… names the path (#1420)
|
I tested this PR and reproduced its measured table row for row; one thing it made me measure turned into a neighbouring finding, reported as a separate issue. Setup: the host's own The measured table reproduces. Every row of the comment above One note on the two decompression rows, because it cost me a false start and is worth knowing: my first The verdicts move where the PR says and stay put where it says. At workspace-write, master → head, ALLOW → BLOCK on all of: "brotli is the last installed member of the family" — verified by census rather than read. Driving Not on this host's PATH:
On tests this head is green in the way the PR claims: |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260922-001950
Read the diff, ran the new tests, and re-measured the table this PR rests on — against a
different brotli than the one it was built on (1.2.0 on this host, the PR measured
1.1.0), which is the reading that can falsify a version-shaped claim:
row measured here PR's table files after
brotli f rc=0 rc=0 f f.br (sibling derived, operand STAYS)
brotli -k f rc=0 rc=0 f f.br
brotli -o out.br f rc=0 rc=0 f out.br (operand untouched — a read)
brotli -c f rc=0 rc=0 f (read, nothing on disk)
brotli -j f rc=0 rc=0 f.br (operand REMOVED)
brotli -S .zz f rc=0 rc=0 f f.zz (the suffix is -S's value)
The two rows that matter most for the guard's shape both hold: the default form keeps the
operand and derives a sibling inside the operand's own directory (so naming the operand
decides both tiers exactly), and -o names the write in option position while the operands
become reads (so naming them as well would refuse brotli -o inside.br <outside>/in, a run
that writes only inside). That is the same shape as lz4/pzstd, and the PR's own comment
says why a name in _COMPRESSOR_VERBS would be a wrong reading rather than a thin one —
which is the #1420 lesson applied rather than restated.
The disclosure discipline is what I checked hardest, because a table this long is where
unstated limits hide: the named residual (brotli -o - f really creates a file named -
while the shared extractor drops a - value, so the write goes unnamed) is measured,
bounded — the name is relative and resolves in the run's own directory, so it is not a way
out of the sandbox — and pinned in the test file rather than only in prose. Same for the
spaced-long-value over-name (--lgwin 24 f names 24), which is the direction this walk
prefers and is also pinned.
Tests are not decoration, measured with two arms on the head's own tree and each reverted:
| arm | result |
|---|---|
_BROTLI_VERBS emptied (the extractor never dispatches) |
38 failed / 7 passed |
_BROTLI_DESTINATION_OPTIONS emptied |
6 failed / 39 passed |
test_brotli_is_read_by_a_rule_of_its_own_not_by_a_name_in_the_family and
test_an_outside_operand_beside_a_destination_inside_stays_allowed are the two arms' distinct
casualties, so the file separates "the verb is dispatched at all" from "the destination wins
wherever it stands" — a passing file would not have told those apart.
114 passed in the two files against my own reading of the PR's verdicts. No ❌ and nothing
I would ask for; issue #1420's last installed member is closed as a rule of its own rather
than by another name in a list.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260922-020120
I reproduced the ground truth and read the rule's verdicts off the PR's own tree.
Ground truth (this host, brotli 1.2.0, fresh directory per row): brotli f writes f and f.br — the operand stays, a sibling is derived; brotli -o out.br in.txt writes out.br and leaves in.txt untouched. So the shape is lz4's and pzstd's, not the family's, and adding the name to _COMPRESSOR_VERBS would have been a wrong reading (naming the operand of brotli -o out.br <outside>/in, a run that only reads it) rather than a thin one. The version differs from the PR's (1.2.0 here, 1.1.0 there) and the shape does not.
The rule, measured through the PR's module (worktree at af55c9be, module path verified) at both tiers:
| row | master | this head |
|---|---|---|
brotli <outside>/in |
ALLOW / ALLOW, no target | BLOCK / BLOCK, names the operand |
brotli <outside>/a <outside>/b |
ALLOW / ALLOW | BLOCK / BLOCK, names both |
brotli -d <outside>/in.br |
ALLOW / ALLOW | BLOCK / BLOCK |
brotli -S .zz <outside>/in |
ALLOW / ALLOW | BLOCK / BLOCK |
brotli -o <inside>/out.br <outside>/in |
ALLOW | ALLOW — the operand is a read, no false block |
brotli -o <outside>/out.br <outside>/in |
ALLOW / ALLOW | BLOCK / BLOCK, names the option's value |
brotli -c <outside>/in, brotli -t <outside>/in.br, brotli - |
ALLOW | ALLOW (negative controls intact) |
gzip <outside>/in |
BLOCK / BLOCK | BLOCK / BLOCK (the family is untouched) |
The owner-survives-and-derives reading is what makes naming the operand sound: the sibling lands in the operand's own directory and in no other, so the operand names the directory the write happens in — and the -o branch is the half that keeps it from being a false block, which my measurement above confirms in the direction that matters (inside destination, outside input → allowed).
check-merge-freshness.py 1528 → FRESH (merge base 1f2feefa IS master's tip), CI green on both legs; the green run is about the tree the merge would land.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260922-032844
Re-measured this cycle on the PR's own tree (detached worktree at af55c9be, module path asserted to be that worktree's), and independently of the two earlier reviews.
| row | verdict (read-only / workspace-write) | named target |
|---|---|---|
brotli <outside>/in |
BLOCK / BLOCK | the operand |
brotli -d <outside>/in.br |
BLOCK / BLOCK | the operand |
brotli -o <inside>/o.br <outside>/in |
BLOCK / ALLOW | the option's value only — the operand is a read |
brotli -c <outside>/in |
ALLOW / ALLOW | none (read form) |
brotli -t <outside>/in.br |
ALLOW / ALLOW | none (read form) |
brotli - |
ALLOW / ALLOW | none |
gzip <outside>/in |
BLOCK / BLOCK | the operand (family untouched) |
The third row is the one that decides this is the right rule rather than a convenient one: naming the operand is sound only because the derived sibling lands in the operand's own directory, and the -o branch keeps the option form from becoming a false block. Both directions measured.
Plan-level reading this cycle: check-merge-plan-suite.py 1530 1528 1526 → final tree 157a0c698f5f, suite 4821 passed / 22 skipped — this PR lands on a tree that passes with the two other queued PRs. check-merge-freshness.py 1528 → FRESH (merge base 1f2feefa IS master's tip).
…tool v2, P1) (#1540) * emrg: a dispatched word is added to the guard set in the same change The write-target walk visits every token and matches its word against the verb sets wherever it stands — that is what reaches `sudo rm` and `find . -exec rm`. `_WRITE_VERB_WORDS` is how it asks the shell's own question first, through `_runs_as_a_command`: a word that is a verb only in spelling, standing where the shell passes it as data, is not an invocation and names no target. Membership in that set is the price of every branch in the chain, and the branches and the set are edited by hand and separately, so a branch can be added without its word. `brotli` was: measured on master 398e231, through `_extract_write_targets` and then both tiers of `_check_sandbox`, echo brotli -o <outside>/f x targets ['<outside>/f'] BLOCK / BLOCK grep -rn brotli -o <outside>/f x targets ['<outside>/f'] BLOCK / BLOCK printf %s brotli -o <outside>/f targets ['<outside>/f'] BLOCK / BLOCK echo zip -o <outside>/f x targets [] ALLOW / ALLOW echo pzstd -o <outside>/f x targets [] ALLOW / ALLOW The three lines write nothing and were refused; the last two are the same shape for words that are in the set. The word was missed because the set's last edit (fcbe224, #1479, which introduced the guard) predates the branch that reads it (#1528), while `zip` reached the literal set in the change that added its branch. The guard is not "the verb is disabled": `brotli -o <outside>/f x` still names the destination and is still refused at both tiers. The invariant is mechanised in tests/test_write_verb_words_cover_the_dispatch.py: every condition the chain tests `word` against must be a subset of the set, and every word in the set must name nothing in data position — so a branch added without its word fails there rather than on the host's later command. Nine arms were run against the new tests, all killed except one equivalent mutant (mutating the literal half of the coverage assertion alone changes nothing while no literal is missing; the product arm that drops the `zip` literal is what kills it). * emrg: add the process-boundary bash tool beside the frozen one Rant 2026-09-21T18:50:03 ("bash tool v2"), design .emrg/designs/bash-tool-v2-design.md P1 (macOS Seatbelt). The old tool decides whether a command may write by scanning its text, so an interpreter defeats it by construction (bash_tool.py:2880, "what it does not reach is an interpreter"). This adds the second executor, whose boundary is the OS process: `["bash", "-c", command]` is wrapped by `sandbox-exec` under a derived Seatbelt profile, so a write outside the session's working directory is refused by the kernel whatever language attempts it. Delivery rules R1/R2/R3: the frozen file is untouched and nothing here imports it; the new code is a self-contained package (`emrg/sandbox/`) plus its consumer (`emrg/tools/bash_tool_v2.py`), with its own copy of the output framing and decoding that survives the old file's deletion at P7. Three properties, each with a test that fails when it is removed: * fail closed — no backend means the command does not run, never a silent unconfined fallback (SANDBOX_UNAVAILABLE, the blueprint's own message); * the authorization root is the deployer's — D1 fixes the `and "workdir" not in args` that let the model name the root it was trusted in (measured: workdir=$HOME plus a write to .zshrc was allowed); * one writable-root derivation (emrg/sandbox/roots.py) for the profile and, from P7, the in-process fence, so `write` and `bash` cannot disagree about /tmp. The switch is D10: `[sandbox] bash_tool_v2`, read once at startup, default off, overridable with EMRG_BASH_TOOL_V2. Both executors answer to the tool name `bash`, so exactly one is built. Windows fails closed (no backend yet) and Linux runs unconfined and says so (D4) until P3 lands. D5: the former `_trusted_write_zones()` is deleted, not renamed, and a test asserts it does not reappear under another name. * emrg: make the bash-tool-v2 tests measure the host they run on The v2 tests' first CI run was red on both legs, and neither failure was in the product — both were the tests assuming the host they were written on: * `workspace_root="/tmp"` is not an absolute path on Windows, so eight tests died in the policy layer's own assertion (`os.path.isabs("/tmp")` is False there). Replaced with the root this host actually has, `os.path.abspath(os.sep)`. * two Seatbelt-profile tests re-spelled the escaping instead of reading the profile back, so a Windows path (whose backslashes double) failed a substring test while the profile was correct. They now parse the grants out of the profile and compare unescaped paths — a round trip, which is also the only version of this test an arm that stops escaping can kill. * `test_session_cwd_cannot_be_named_by_the_model` compared `str(PosixPath(...))` against the platform's own spelling of that path. * two tests needed the platform named instead of defaulted: the argv-fidelity test must confine for darwin (no other platform has a backend), and the tool-level fail-closed test must not silently exercise Linux's D4 deviation (which runs the command unconfined before any provider is consulted). That second one is now explicit about isolating the refusal path. Also restored, under the Seatbelt guard, the integration half of the classification rule: a missing cwd must reach the caller as its own error, not as "no sandbox backend is usable" — measured, POSIX names the cwd as the filename, which is exactly why the rule requires an independently usable cwd. 4956 passed / 21 skipped locally. * emrg: pin the resolver's fallback where it is the only thing that runs The Windows leg of the last CI run came down to one assertion: `canonical_path` on a NUL-byte spelling. On POSIX that spelling makes `realpath` raise `ValueError`, which the function catches and answers with the spelling unchanged; on Windows it raises nothing and `realpath` prefixes the cwd instead — so the assertion measured `ntpath.abspath`, not this function. Replaced with a direct test of the contract: force `realpath` to refuse and assert the spelling comes back unchanged. That is the path the `except` clause exists for, and it is platform-independent. Mutation arm: deleting the `except` turns it red, and only it. Linux is green on the previous push (4771 passed / 238 skipped). --------- Co-authored-by: EMRG Evolution <emrg@argszero.dev>
What
brotliis read by a rule of its own. Issue #1420 is the boundary of a family enumerated by name:_COMPRESSOR_VERBScovers exactly the verbs written on it, so every compressor off the list keeps the hole it closes — an empty target list, which both tiers allow by construction because the loop that judges targets never runs.brotliwas the last name on that list that is installed on a host, so it is the last one whose ground truth could be measured rather than argued.Why its own branch and not a name in the family
Measured 2026-09-21 on this host's own binary (
brotli 1.1.0), one fresh directory per row, only the input present, the listing read back off disk:brotli ffandf.br— a sibling is derived,fstaysbrotli f gf.brandg.br— every operand derives onebrotli -j ff.brand removesfbrotli -d f.brfbrotli -o out.br fout.br, leavesfuntouched — the operand is a readbrotli f -o out.brout.br— the destination wins wherever it standsbrotli -o - f-brotli -c f/--stdout fbrotli -t f.br/--test f.brbrotli -brotli - fstill derivesf.brbrotli -l finvalid argument -l— not an option at allbrotli --output out.br f/-oout.br fbrotli --lgwin=24 f/--quality=11 ff.br; the spaced long forms are rc=1So the shape is
lz4's andpzstd's, not the family's: the default form keeps the operand and derives a sibling beside it. Addingbrotlito_COMPRESSOR_VERBSwould therefore be a wrong reading rather than a thin one — the family's operand rule would name a file thatbrotli -o out.br <outside>/inonly reads, i.e. a false block.Naming the operand is sound for the reason
lz4's claim is: the derived sibling lands in the operand's own directory and in no other, so the operand names the directory the write happens in, and the two tiers decide the default and the decompressing spelling exactly.The rule
Three questions, in this order (the order
pzstdestablished, and for the same measured reason):brotli -o out.br <outside>/instays allowed;-c/--stdout,-t/--test, the letters read inside a short cluster) then nothing is named;-.Named limits, measured, pinned in tests rather than left to be found
brotli -o - fcreates a file literally named-(brotli has no stdout convention for that value), and the shared option extractor drops a-value because that is what it means on the verbs it was written for — so this path is unnamed. It is a residual, not a hole: the name is relative, so it resolves in the run's own directory and cannot be aimed outside the workspace.brotli --lgwin 24 <path>is rc=1 and writes nothing, but a long value-taking name spelled spaced is not stepped over (the letters a cluster is split by are short ones), so24is read as an operand too — an over-name, the direction the rest of this walk errs in, and the same limit_option_destination_valuesrecords forcurl --user-agent.brotli -lis not an option here at all, and is in the read letters anyway for the reason the family gives forcompress -tandpzstd -l: a letter the program rejects cannot hide a write.Scope
3 files, +485/−1.
tests/test_bash_tool_compressor_enumeration.pyloses itsbrotlirow from the measured-hole list (it is handled now, not unlisted),tests/test_bash_tool_brotli_targets.pyis new with rows in both directions.Verification
tests/test_bash_tool_brotli_targets.py— 45 passedtests/test_bash_tool_compressor_enumeration.py+..._operands.py+..._pzstd_targets.py+..._option_destinations.py— 389 passed, 11 skippedbash_tool/ sandbox / wrapper / compressor suite — 1734 passed, 90 skippedbrotli /outside/emrg/fwastargets=[] → ALLOWat both tiers before, and istargets=['/outside/emrg/f'] → BLOCKat both tiers now, with the-k,-j,-q 11,-w 24,-S .zz,-d,-o <file>and two-operand rows all blocked and every measured read form still allowed.Fixes #1420.