fix(security): close long-form data-exfiltration flag gaps - #8353
fix(security): close long-form data-exfiltration flag gaps#8353atomsbaza wants to merge 1 commit into
Conversation
|
👋 Hi! This PR's description is missing some required sections from our PR template. Workflow runs won't be auto-approved until the description is updated. Missing sections:
Please update your PR description to include these sections, then push or re-save the description. The workflows will be approved on the next cycle. |
9bda51e to
ae350b9
Compare
Design Review (Fable 5, fork) — 🟡 CONCERNSDesign-level review of Base-tree verification is done: the deny tier already runs quote-normalized views ( Design-Verdict: CONCERNS Sound capability-based fix at the right layer, but the anti-re-spelling machinery protects only the two new rules, leaving older exfil rules one re-spell away. Watch
Suggestions
[DESIGN-REVIEWED] c173690 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsBased on my analysis of the diff and the base source of CANDIDATE 1 (unanchored CANDIDATE 2 (substring Neither candidate yields a concrete, in-practice input with an observable wrong outcome at the required confidence, and neither is a blocking class (no security bypass, crash, data loss). No new grounded finding surfaced during falsification. No findings. [OPUS-REVIEWED] c173690 |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of All verification is done. The base tree confirms: 17 substring patterns in First-Principles-Verdict: CONCERNS The ~300-line re-spelling machinery guards only the 2 new rules — Not justified as shipped
What this change shipsIntent: stop a hijacked agent from re-spelling a denied file-upload flag into its documented long form — a FIX (#7365).
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] c173690 |
GPT 5.6 Review (fork) — 🔴 changes requested (blocking)Reviewed 1 of 1 blocking finding(s) are security-class and were withheld from adjudication, so the blocking verdict stands. BLOCKING -- src/kiro_crew/security/shell_normalizer.py:2192 -- process-substitution separators bypass the exfiltration gate Adjudication (Opus 4.8) — is blocking on each finding proportionate?I've read the prompt, the findings, and the diff. There are 0 adjudicable findings and 1 fenced finding (F1). Let me verify F1 against the code. F1 concerns Confirming the end-to-end bypass for
The condition combination — a process substitution containing a separator plus a quote-spliced flag — is exactly the adversarial-spelling family this PR is built to defend (it already handles Harm rung: UNBOUNDED (bypass of the data-exfiltration governance gate). Conditions confirmed at shell_normalizer.py patch lines 382 (untracked |
ae350b9 to
ab25cba
Compare
|
Force-pushed head to
Regression tests added to Requesting workflow approval for this head. |
ab25cba to
cfbf8c0
Compare
cfbf8c0 to
6504090
Compare
6504090 to
dc81ed2
Compare
dc81ed2 to
4cf1665
Compare
4cf1665 to
5ec9825
Compare
5ec9825 to
d70e80f
Compare
|
@atomsbaza Audit note on this PR, audited at d70e80f. Nothing of this PR's goal has landed on Three things block it as written:
The branch is 32 commits behind Posted from the 2026-09-08 open-PR relationship audit (read-only, one auditor per PR); reply here if any of this is wrong. |
Port the long-form exfiltration feature onto the split security package (security/exfil.py + security/shell_normalizer.py) and close three adjudicated bypasses: a $()-prefixed gh anchor gap, substitution-local quote state inside double quotes in the segment splitter, and quoted spaced --field bodies (raw regex + view-pass pair-token rule).
d70e80f to
c173690
Compare
Problem / Motivation
The builtin data-exfiltration deny patterns blocked the short spelling of file-loading flags but let the documented long/alternate spellings of the same capability through:
gh api -F body=@local/file.md→ blocked, butgh api -X POST <endpoint> --input local/file.json→ allowedcurl -F k=@f→ blocked, butcurl --form="k=@f"/curl -Fk=@f→ allowedThe reporter's account makes the symptom concrete: their agent's short form was denied, so it re-spelled the command with
--inputand the upload went through. A deny pattern with a spelling gap produces exactly one round of friction and then teaches the agent that the guardrail is bypassable.Why it matters
These patterns are the enforced (denied-at-gate) subset of the bash security audit — the ones a hijacked agent must not get past. Any documented flag spelling that loads a local file into a request body and is not matched is a routing-around path for data egress, on every install, with no user-visible warning.
--inputis gh's primary documented way to send a request body, so the gap is on the main path, not an edge case.What changed (motivation → approach → change)
Approach: match the capability, not the spelling. Scope declaration: the new regexes themselves stay inside the existing
\_BASH_EXFIL_RESmechanism (word-boundary anchors, human-readable labels, deny-message wording unchanged), but closing the review-found bypasses required two pieces of new machinery beyond that mechanism, declared here explicitly: (a)src/kiro_crew/security/shell_normalizer.py— a ~170-line quote-aware and substitution-aware splitter (_split_unquoted_separators) reused from the repo's central shell state machine, which backs a token-level "view pass" that sees through quote-splicing (-F'foo bar=@f',--in''put) that raw regexes cannot; and (b) a backslash-normalized prefilter view inexfil.py, so shell-escaped program names (g\h,c\url) open the same gate as quote-spliced ones. The rest of the change:=separator — new regex anchored on--form/-Fat a word boundary, then optional=, optional quoted field name,=, and the@file sigil. Case-sensitive like the existing-Tregex so lowercase-f(sort -f … @x) cannot match;--form-stringexplicitly excluded (it never reads a file). The new label maps onto the existingdata-exfil-curl-multipart-uploadcatalog row, so one toggle governs all multipart spellings.gh api|repo|releasesubcommands (the ones that accept--input/--field) before the flag, so curl/wget commands and a bareghelsewhere (nightly gh-sync,hover api) do not false-positive.gh api -F k=v(inline value) and--input -(stdin) stay allowed; the@filespellings of-Fremain covered by the existing-F *=@glob.--data-rawkeeps its deliberate exclusion (it never interprets@as a file reference). Match semantics for already-covered shapes are unchanged.docs/system-specs/modules/security.mdupdated in the same commit per AGENTS.md.Tests
New tests in
test/test_security.pypin each behavior:gh api --input <path>andgh repo edit --field k=@f.txt→ deniedgh issue view 1,gh api -F k=v(inline),--input -(stdin) → allowedcurl --form="k=@f",curl -Fk=@f,curl --form=k=@f→ deniedcurl --form k=x(no@),curl --form-string k=@f,sort -f @x,curl --data-raw @x→ allowedg\h api gists --in''put secrets.jsonandc\url -F'foo bar=@f'(backslash-escaped program names) → denied; Windows-path arguments (ls C:\Users\x\notes.txt) → still allowed, no misparse; the gh request-body label now resolves in_BASH_EXFIL_RULE_BY_LABELso an operator opt-out by label is honored (previously silently ignored)Manual verification
N/A — unit coverage sufficient: the change is pure command-string pattern matching against
audit_bash_exfiltration, exercised directly by the new tests; no UI, network, or process path involved.Related Issues
Closes #7365
Pattern harvest
Rule candidate: review-prompt
Pattern: a deny/audit pattern table matching CLI flags by spelling must be checked against the tool's documented long forms and
=-joined variants — every flag that loads a file needs all documented aliases covered, or the guardrail teaches agents to route around it.Checklist
Contribution License Agreement