fix(security): redact before slicing at every bounded-logging site - #5603
fix(security): redact before slicing at every bounded-logging site#5603aniruddhaadak80 wants to merge 2 commits into
Conversation
|
👋 Hi! This PR is currently in draft status. Workflow runs won't be auto-approved until it's marked as ready for review. When you're ready, click "Ready for review" and the workflows will be approved on the next cycle automatically. |
4f42576 to
5b1267c
Compare
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsFINDING -- src/kiro_crew/cron_script.py:983 -- |
First Principles Review (Fable 5.1, fork) — 🟡 CONCERNSPremise-level review of The change is a security fix at First-Principles-Verdict: CONCERNS Sound redact-before-slice fix, but the description states a 64× larger ceiling (4 MiB) than the 64 KiB actually shipped, changing how much stderr is withheld. What this change shipsIntent: stop a credential straddling the tail cut from leaking as an unmatchable fragment in cron stderr diagnostics (#5582). This is a FIX.
Watch
[FIRST-PRINCIPLES-REVIEWED] ff33372 |
Design Review (Fable 5.1, fork) — ✅ PASSDesign-level review of Design-Verdict: PASS Redact-then-tail-slice is the correct fix for the straddling-secret leak and matches the codebase's established redact-before-bound convention; the 64 KiB ceiling with a withhold-marker is a proportionate DoS/coverage trade-off. [DESIGN-REVIEWED] ff33372 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed |
1342839 to
a706caa
Compare
|
Reopening - this was closed during a bad window on the branch, not because of the change itself. While rebasing onto main locally, a commit-object write failure briefly left the branch pointing at main (empty diff), which is likely what triggered the closure. The branch has since been fixed properly:
Head is now |
7edc2a7 to
fd17c5e
Compare
fd17c5e to
808e8a6
Compare
|
Rebased onto current main ( Main independently landed equivalent-or-stricter spellings for most of the sites this branch originally covered, so the diff narrowed to two pieces (PR description updated to match): On the two blocking findings from the previous review round:
|
808e8a6 to
a38e4d3
Compare
|
Both blocking verdicts on Opus — quadratic redaction stall (BLOCKING). Agreed in full: full-buffer redaction turned every First Principles — two riders.
Verified locally: all 114 tests in |
|
Full-diff/commit-containment audit (current head Please keep #5603 as the single owner of the bounded-logging redaction change. Downstream PRs should either retarget to the actual preceding branch if an explicit stack is intended, or rebase on current |
Audit note — this PR and #6031 are one branch stack, not duplicatesA duplicate-detection sweep flagged these two as near-identical because they share a large block of added lines. They are not duplicates: one branch contains the other's commits, so the shared lines are inherited, and the review diff overstates what this PR actually authored. #6031's branch literally contains #5603's sole commit as its parent: What is genuinely each PR's ownIf #5603 lands, #6031 still carries real, wanted work: the one-line Suggested actionThis is the base of the stack — it can go first on its own. #6031 should rebase after it lands. From a repository-wide duplicate/overlap audit of every pull request open against |
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
|
Rebased onto current main in f92534d (was CONFLICTING). Resolution: the Bad-output site now takes upstream's grant-scrubbed spelling redact(_scrub_grant_values(...))[:200], which keeps the redact-before-slice ordering and adds grant-value scrubbing; the _stderr_tail bounded-span delta and its tests are intact. No local pytest run (no Python on this box) — relying on CI as proof. |
Upstream landed the canonical spelling, security.redact_and_truncate (redact the FULL text, then cut), and converted most of the sites this branch originally covered; the rebased delta carries what remains: - cron_script._stderr_tail redacts a bounded span (the last _STDERR_TAIL_REDACT_SPAN bytes) BEFORE tail-slicing, so a key straddling the old seek-to-size-minus-limit window start no longer loses its anchoring prefix, while redaction stays O(span) per call: an adversarial subprocess cannot stall the worker by stuffing its stderr with credential-shaped tokens. Captures beyond _STDERR_FULL_REDACT_MAX are withheld behind a fixed marker instead. Tests pin the behaviors: a straddling key is scrubbed within budget, an oversized capture is withheld behind the marker, and the text handed to redact never exceeds the span regardless of capture size.
|
Diagnosis + fix in 651436f: Opus was right — redacting up to 4 MiB in one pass feeds attacker-shaped stderr into the per-chunk credential scan. The read is now bounded to the 1 KiB tail window plus a 4 KiB overlap (clears the JWT ceiling the streaming redactor already uses), keeping redact-before-slice ordering; oversized captures still get the fixed marker. The straddle test is re-sized so it genuinely pins the fix (verified the old window leaks a 10-digit fragment there), the duplicate ordering test and the stale docstring edit are dropped, and the description's spec_builder claim is corrected to tests-only. The remaining Backend Tests (3.12, 4) failure is a test_snapshot.py copy/append timing race in a file this diff does not touch — treating as flaky, watching the rerun (Coverage Gate clears with it). |
|
Diagnosis plus fix in a22be8d. The backend failures are flakes outside this diff: a gw2 worker crash (test_source_link_urls_spans_slots_and_caps_at_serialized_count) and a timing race (test_a_note_delivered_during_the_copy_survives_the_READER); neither touches cron/redact paths, and this push re-runs them. The GPT/Design BLOCKs are addressed as specified: _stderr_tail now redacts the whole under-ceiling capture before tail-slicing (window/overlap/grow-race code deleted). New regression test test_stderr_tail_redacts_url_longer_than_any_window fails on the old read and passes on the new one; PR description updated to match. Cost note: bounded by the 4 MiB ceiling on the disconnect-only error path. |
|
Update: the new head workflows sit at action_required (fork approval gate), so CI has not started on a22be8d yet. The description gate is green again after the re-save, so the approval cron should pick it up; otherwise this needs a maintainer Approve-and-run to validate the fix. |
|
CI diagnosis (run 34045252207) + review fix in 4226aee:
No local pytest on this box, so CI is the proof. The 2 PR-Readiness blockers are exactly these two items. |
|
Follow-up on ff33372 (squashed a22be8d+4226aee39 to satisfy the 2-commit hygiene gate; content unchanged):
|
Problem / Motivation
Sites that slice text to a logging budget before redacting let a credential straddling the cut survive as a fragment no regex matches ΓÇö (?:AKIA|ASIA)[A-Z0-9]{16}\ no longer matches half a key. Upstream has since landed the canonical spelling, \security.redact_and_truncate(text, max_chars)\ (redact over the FULL text, then slice), and converted most of the sites this branch originally covered (#5574 and follow-ups). This PR now carries the two remaining pieces (fixes #5582).
Why it matters
These are exactly the surfaces an operator reads when investigating a failure: cron failure diagnostics and SEL audit trails. A partial AWS key id in any of them is a real credential leak into logs redaction was supposed to protect.
What changed (motivation → approach → change)
*\cron_script._stderr_tail* had the harder variant: it seeked to \size - limit\ and redacted only what it read, so a key spanning the window START lost its anchoring prefix. Now:
spec_builder adds coverage pinning the already-shipped _redact_and_truncate\ helper (redact-then-truncate ordering, fail-closed fallback, non-string pass-through) ΓÇö this half is tests-only; the helper itself lives on main.
Everything else this branch once touched (subagent summaries, project descriptions, the rejected-hook SEL audit, spec-slot-name audits) was absorbed by main with equivalent or stricter spellings; the rebase keeps those untouched.
Tests
Manual verification
Windows 11 / Python 3.12: \pytest test/test_cron_script.py test/test_spec_builder_routes_coverage.py test/test_error_code_contract.py\ ΓÇö green (three sandboxed-subprocess tests are load-sensitive locally and pass serially; CI's Linux shards run them under normal load).
Related Issues
Fixes #5582
Screenshots / video
N/A ΓÇö backend-only security fix, no UI.
Pattern harvest
Rule candidate: semgrep
Pattern: redact-then-truncate ordering at every bounded-logging site ΓÇö a slice taken before redaction lets a credential straddling the cut survive as an unmatchable fragment.
Checklist
Contribution License Agreement