docs(sandbox): replace the degrade-open enumeration with a rule that cannot drift - #6399
Conversation
d134049 to
bb65118
Compare
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
Design Review (Fable 5, fork) — ✅ PASSDesign-level review of All description claims check out against the base: the two-member enumeration exists at Design-Verdict: PASS Replacing the roster with a rule removes the drift mechanism itself; the fix targets the root cause, is prose-only, and fully reversible. [DESIGN-REVIEWED] b76c5db |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed |
First Principles Review (Fable 5, fork) — ✅ PASSPremise-level review of Verification complete. The base tree confirms every load-bearing claim: the two-item enumeration at First-Principles-Verdict: PASS The stale count is the cause of the doc defect, and the change deletes the count itself rather than incrementing it — a cause-level fix with zero new surface. What this change shipsIntent: keep the launcher's one fail-closed/fail-open policy statement true so the next reader cannot cite it to justify a new degrade — a FIX (documentation defect).
Zero option for item 1 leaves a false closed enumeration in the one place the policy is stated; a bare three-item list was considered and rejected for the counted reason above. No new config, symbol, or schema — nothing to consumer-count. [FIRST-PRINCIPLES-REVIEWED] b76c5db |
bb65118 to
7b4b19f
Compare
7b4b19f to
4348488
Compare
4348488 to
bc4ea71
Compare
bc4ea71 to
f2e8c3c
Compare
5c5d88c to
afe9465
Compare
afe9465 to
709a93b
Compare
709a93b to
12a2eae
Compare
…cannot drift The docstring closed its exception list at two, which a third member already falsified; a rule plus one named example cannot go stale at the fourth.
12a2eae to
b76c5db
Compare
Problem / Motivation
_mount_or_die's docstring insrc/kiro_crew/sandbox.pyexplains why every mount in thenamespace launcher refuses rather than degrades, then draws a boundary around the decisions
nearby that do degrade open. On
mainthat boundary reads:There is a third degrade-open decision in the same launcher: the
EXPOSE_FILESpre-read.Its own inline comment already places it in that class, in those words:
So the file currently contains a two-member enumeration and, ~110 lines below it, a third member
that self-identifies as belonging to it.
Neither author could have caught this. The docstring landed in
2bfa78f8b376("refuse to exec when a credential-hiding mount fails", #6074). The
EXPOSE_FILESdegradelanded in
f65bd17ad4("let an unreadable cc expose source degrade, not abort", #5992), laterthe same day. Each merged without the other in view.
Why it matters
This docstring is the one place that states the launcher's fail-closed/fail-open policy, and it
is written to be read by the next person deciding whether some new failure path may degrade. An
enumeration that is short by one teaches that the exception list is closed when it is not, and
the closing clause — "neither concerns the hiding itself" — is the part a reader would lean on
to justify a new degrade. Left as is, it is a plausible-looking licence to degrade something
that should refuse.
Documentation only. No behaviour changes, so there is nothing to regress at runtime; the cost of
leaving it is entirely in what the next reader concludes.
What changed (motivation → approach → change)
The obvious repair is to make the list three items long. That is not sufficient, and working out
why is most of this change.
I read all three sites before editing, and they do not degrade in the same relation to the
hiding:
sandbox.pyL1661-1679). When no cross-fs tmpfs is available,_tmpfs_srcstaysNoneand the empty source inode comes from the default tempdir. The bindstill runs through
_mount_or_die(L1731, L1757) either way, so the hiding is establishedregardless; what is given up is cross-fs hardening against a teardown propagation race.
weakens detection of an alias that circumvents the hiding. Step 7 runs after the hiding
loops, so the mounts themselves are already in place.
EXPOSE_FILESpre-read (L1682-1717). OnOSErrorthe loop warns and does not populateexpose_data; the restore loop is gatedif src_path in expose_data, so the file is neverwritten back into the empty mount. It therefore fails toward more hiding.
That last one is why adding a bullet alone would not have been enough — and, on the same logic,
why a three-item list is not enough either.
EXPOSE_FILESis the deliberate exception to thehiding, so the old closing clause ("neither concerns the hiding itself") was false of it. But a
list closed at three goes stale at the fourth degrade exactly as the list closed at two went stale
at the third, and the launcher already has more: the depth-5 walk truncation (
sandbox.pyL2079-2083) and three silent
except OSError: passskips in the scan (L2057, L2065, L2095) eachabandon detection while every mount holds.
So the shipped change removes the count rather than incrementing it. The docstring now carries
a rule instead of a roster: a failed hiding mount is the one failure
_mount_or_dieexists toprevent, nothing else in the launcher is one, and each of those others argues its case at its own
site. The
EXPOSE_FILESpre-read is named there as an example, explicitly not as a roster, so afourth degrade-open decision cannot make the paragraph false. The rule is read narrowly on
purpose: it says none is a failed hiding mount, NOT the stronger claim that no credential can end
up reachable — a budget- or depth-truncated hardlink scan can leave an alias unchecked. A closing
norm keeps the boundary the original clause was reaching for: a degrade elsewhere is never license
to degrade a mount.
Exactly ONE site comment is edited — the hardlink budget comment at
sandbox.pyL2020 — whichgains that truncation cost explicitly, since it was the one consequence no site stated. The tmpfs
fallback and
EXPOSE_FILESsites already carry their own rationale, so nothing was added there;growing the diff to make a sentence true would be the wrong trade.
One file,
src/kiro_crew/sandbox.py, prose only. Nomount, pre-read,raise,sys.exit, orcontrol flow is touched. The docstring sits inside the
f'''...'''launcher template, so theedit deliberately introduces no
{or}.Tests
No test changes. Nothing pins the edited prose:
test/test_sandbox_mount_checked.pyslices the helper region fromdef _mount_or_die(toREAL_UID =, which contains this docstring, but its_LANDMARKSare structural codemarkers only (deliberately so — see the comment above them) and its text assertions are on
the four
_mount_or_die(...)call forms. Itsscript.count("_mount_or_die(") == 5invariantstill holds.
deliberately narrower,neither concerns the hiding itself) finds no test referencing them.Existing coverage run:
test/test_sandbox*.pysuites: 523 passed, 1 skipped.pytest -q -n auto --dist loadgroup): see Manual verification.Manual verification
Because the docstring lives inside an f-string template, the real risk is a quoting or brace
error that would break launcher generation rather than a test assertion. Verified directly by
building the script from the edited module:
_build_launcher_script("strict")returns 32506 chars._mount_or_die(in the generated launcher: 5 (1 definition + 4 call sites).defline in the emitted source.Related Issues
Follow-up to #6074 (which added the enumeration) and #5992 (which added the third member).
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)