fix(security): derive the UNC path-token boundary from whitespace - #7957
Conversation
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS A derived boundary class replaces the enumeration that drifted from the patterns it mirrors — root-cause fix, minimal, revert-verified, allow-side pinned. The fix lands at the right layer (the one classifier [DESIGN-REVIEWED] d3fceaa |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — ✅ PASSPremise-level review of All verification done. The rename has exactly one consumer, no sibling hand-enumerated whitespace classes exist on security-matching paths (the First-Principles-Verdict: PASS A live-verified fence bypass closed at cause level — the enumerated whitespace class is replaced by the derived one, so it cannot drift again. What this change shipsIntent: stop a doubled-separator UNC spelling of a fenced path on a continuation line from slipping past the sensitive-path gate — a FIX.
No new config key, flag, or exported symbol; zero one-way-door surface. Grepped Watch
[FIRST-PRINCIPLES-REVIEWED] d3fceaa |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
`_PATH_TOKEN_BOUNDARY` enumerated the whitespace a path operand may start after as `" \t"` and stopped there, while the sensitive-path patterns accept the whole whitespace class. `_separator_collapsed_variants` used it to decide whether a separator run is LEADING (a UNC prefix, which must survive as a pair) or interior (collapsed to one), so a UNC path on a CONTINUATION LINE -- newline immediately before it, as in a multi-line PowerShell command -- was read as interior. Every emitted variant then destroyed the UNC anchor, and the original command still missed on its interior doubled run, so the DOUBLED spelling of a fenced file was permitted where the single-separator spelling of the same file was blocked. That is the #6350 defect class surviving at a newline boundary. `\r`, `\v` and `\f` were missing for the same reason. Reachable on the crew-directory read fence over `security_policy.json` and on the `~/.kiro/agents` WRITE gate, where a planted agent spec becomes a gateway-exec'd MCP server command outside the per-session sandbox. The boundary is now derived: `str.isspace()` plus the punctuation set, which is exactly the class the patterns themselves accept, so the two cannot drift apart again. The constant is renamed to `_PATH_TOKEN_BOUNDARY_PUNCTUATION` to say what it now holds; it had no other consumer. Evidence, `is_sensitive_bash_command` on a continuation-line UNC read of `\\server\share\.kiro\crew\security_policy.json`: boundary spelling before after \n single separator BLOCKED BLOCKED \n doubled separator ALLOWED BLOCKED \r doubled separator ALLOWED BLOCKED \v doubled separator ALLOWED BLOCKED \f doubled separator ALLOWED BLOCKED space doubled separator BLOCKED BLOCKED (control) The `~/.kiro/agents` write gate leaked identically (`Set-Content` after a newline: ALLOWED before, BLOCKED after). Tests assert the whole whitespace class against both the read fence and the write gate rather than the newline alone, since enumerating is what produced the gap; the single-separator control is pinned on the same commands so a future change cannot close the gap by relaxing that side instead. A second case holds the allow side: an unfenced UNC path on a continuation line stays allowed in both spellings. Revert-verified -- removing the fix fails 5 of the 11 new cases (`\n`, `\r`, `\r\n`, `\v`, `\f`) and leaves the space, tab and benign cases passing. A 17-command benign corpus of multi-line commands with separator runs returns byte-identical verdicts before and after, so the fence did not widen.
7f7924c to
d3fceaa
Compare
Problem / Motivation
_PATH_TOKEN_BOUNDARYinsrc/kiro_crew/security.pyspelled out the whitespace a path operand may start after as" \t"and stopped there, while the sensitive-path patterns accept the whole whitespace class._separator_collapsed_variantsused that constant to decide whether a separator run is LEADING (a UNC prefix, which must survive as a pair for the UNC anchor to match) or interior (collapsed to one). So a UNC path on a continuation line — newline immediately before it, as in a multi-line PowerShell command — was misclassified as interior. Every emitted variant then destroyed the UNC anchor, while the original command still missed because of its interior doubled run.Net effect: the doubled spelling of a fenced path was permitted where the single-separator spelling of the same file was blocked. That is the #6350 defect class surviving at a newline boundary.
\r,\vand\fwere missing for the same reason.Why it matters
Two reachable surfaces, both governance-critical:
security_policy.json~/.kiro/agentswrite gate, where a planted agent spec becomes a gateway-exec'd MCP server command outside the per-session sandboxPost-merge patrol finding from #6993, verified live on
origin/mainbefore this fix.What changed
The boundary is now derived rather than enumerated:
str.isspace()plus the punctuation set, which is exactly the class the patterns themselves accept before a path operand, so the two cannot drift apart again. The constant is renamed to_PATH_TOKEN_BOUNDARY_PUNCTUATIONto say what it now holds — it had no consumer outside this function.Deliberately minimal: two lines of logic, one constant, one docstring. No pattern was widened.
Before / after
is_sensitive_bash_commandagainst a continuation-line UNC read of\\server\share\.kiro\crew\security_policy.json— measured on theorigin/mainblob, then on this branch:\n\n\r\v\fThe
~/.kiro/agentswrite gate leaked identically:Set-Content \\n\server\share.kiro\agents\evil.json -Value x`Set-Content \\n\server\share.kiro\agents\evil.json -Value x`Tests
TestWindowsSeparatorRunsgains two cases, both parametrized over the whitespace class rather than the newline alone — enumerating is what produced this gap:test_a_unc_path_after_any_whitespace_boundary_is_still_fenced— the doubled spelling across\n \r \r\n \v \fspace tab, on both the read fence and the agents write gate, plus the single-separator control on the same commands so a future change cannot close the gap by relaxing that side instead.test_a_benign_unc_path_after_a_whitespace_boundary_stays_allowed— holds the allow side: an unfenced UNC path on a continuation line stays allowed in both spellings.Revert-verified. With the fix mutated back to the pre-fix boundary, 5 of the 11 new cases fail (
\n,\r,\r\n,\v,\f) and the space, tab and benign-allow cases still pass. Restoring the fix returns all 11 to green. Re-run after the rebase onto currentmain.No false positives. A 17-command benign corpus — multi-line PowerShell/POSIX commands with separator runs at a newline boundary (
robocopy,Copy-Item,grep -rover a UNC share,cd node_modules//pkg,cat /etc//hostname) — returns byte-identical verdicts before and after this change. Widening the fence is the failure mode here, so this was checked as a diff of the two verdict sets rather than by inspection.test/test_security.pysecurity.py)BRAND_BASE_REFset)Diff-scoped gates were run with
BASE_REF=$(git merge-base HEAD origin/main)exported — without it they report a false green.Related Issues
Follows #6350 (the separator-run collapse this boundary belongs to). Patrol finding from #6993.
Pattern harvest
Rule candidate: semgrep
Pattern: hand-enumerated character class standing in for a derivable one (
" \t"wherestr.isspace()is meant) on a security-matching path — the enumeration and the pattern it mirrors drift apart silently, and the drift is only observable as a boundary the fence stops covering.The same shape is worth flagging wherever a gate compares against a literal string of separator/whitespace characters instead of the classifier the regexes use; a review-prompt line asking "is this class derivable?" would have caught it at the original #6350 review.