Skip to content

fix(dashboard): warn when redaction rewrites a URL in chat text - #8291

Merged
bolichen97 merged 1 commit into
mainfrom
fix/exfil-url-redaction-notice-8132
Sep 8, 2026
Merged

fix(dashboard): warn when redaction rewrites a URL in chat text#8291
bolichen97 merged 1 commit into
mainfrom
fix/exfil-url-redaction-notice-8132

Conversation

@dwu96

@dwu96 dwu96 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #8132

Symptom

When the dashboard finalizes an assistant chat segment, _flush_segment runs two independent redactors over the pasteable text. Since #8109 (merged), the credential redactor appends a user-visible notice row. The exfiltration-URL redactor, called a few lines earlier in the same function, rewrites a URL to [REDACTED: suspicious URL to <domain>] and reported that only to the server log — a user copying a command out of chat got silently altered text and found out when the paste failed. Same root cause as #6189, different rewriter.

Root cause

The #8109 notice counts CREDENTIAL_REDACTION_TAGS exactly — a deliberate, documented credentials-only scope. The URL tag interpolates the domain, so it has no constant form to equality-compare and could not join that tuple; and the #6189 notice wording ("a credential … supply the secret yourself") would name the wrong remedy for a rewritten URL. The in-place SCOPE: comment recorded the case as tracked-separately; this PR is that fix.

Fix

Mirrors #8109's own notice path — no new shape. (Originally written against the monolithic security.py; rebased over the #9183 package split and the #8318 notice-helper hoist, so the same change now lands in the package modules and the shared helper.)

  • security/exfil.py: export the URL tag's stable prefix as EXFILTRATION_REDACTION_TAG_PREFIX and build the substitution in redact_exfiltration_urls from that constant, so the two can never drift. Docstring states why it is a prefix and not a member of CREDENTIAL_REDACTION_TAGS; that tuple's docstring (security/redaction.py) now points at the prefix constant instead of describing the URL case as invisible to consumers. The constant is registered on the facade (security/__init__.py) and in the frozen export manifest (security/_exports.py), so from kiro_crew.security import EXFILTRATION_REDACTION_TAG_PREFIX resolves like every other export.
  • chat_runner.py: prefix-count the constant in the persisted redacted text beside the existing credential-tag sum, inside _append_redaction_notice — the shared helper fix(chat): fire redaction notice on the 7 exception-path assistant persists #8318 extracted (closing Redaction notice does not fire on the 7 exception-path assistant persists #8311), called by _flush_segment and the seven exception/teardown persists, so the URL notice fires everywhere the credential notice does. Extend _redaction_notice(cred_count, url_count) to word the notice by kind — credentials only (byte-identical to fix(dashboard): warn when redaction rewrites a credential in chat text #8109's string, regression-guarded), URLs only (re-check the URL remedy), or both. The notice stays unconditional w.r.t. quiet_persist, per the existing rationale. Counting reads the TAG in the persisted text, not exfil_warnings — same reason as the credential path: on the streaming path chunks are redacted before they reach assistant_text, so the warning list is almost always empty here while the tag is present. Following the list would ship a notice that never fires.
  • docs/system-specs/modules/security.md: same-commit spec update for the substitution/prefix contract and the by-kind notice, stating the helper-shared coverage (post-fix(chat): fire redaction notice on the 7 exception-path assistant persists #8318) rather than the seven-branch gap the pre-rebase body declared.

Deliberately NOT changed: redact_exfiltration_urls itself — the redaction still happens; this is a notice, not a relaxation (the issue explicitly forbids keeping the URL pasteable). Display-string redaction call sites (titles, tool names, feed strings, stashed variants) stay notice-free, deliberately — they do not produce text the user copies commands from. The seven exception/teardown persists, declared as a shared gap in the pre-rebase body and tracked in #8311, are no longer a gap: main's #8318 routed them through _append_redaction_notice, and this PR's URL counting rides that helper — exactly the fix direction the First Principles disposition recorded. Locale ripple: none — the notice is a server-built English string appearing in zero locale files.

Issue-body corrections

Verification

  • Red before green on unmodified main: 4 new tests failed (URL-only notice, both-kinds wording, and the two prefix-constant pins), 1 passed by design (the credential-only regression guard pins existing fix(dashboard): warn when redaction rewrites a credential in chat text #8109 behavior). All green after the fix.
  • Three cases covered: URL only, credential only (regression guard on fix(dashboard): warn when redaction rewrites a credential in chat text #8109), both in one segment — in test_chat_runner_coverage.py::TestFlushSegment (extended in place) and test_security.py::TestRedactExfiltrationUrls.
  • Mutation checks, both directions (mutant applied → named test red; restored → suite green):
    • (a) prefix count reverted to exact-match compare → test_a_redacted_url_warns_the_user + both-kinds test red
    • (b) URL count dropped from the notice condition → test_a_redacted_url_warns_the_user red
    • (c) wording collapsed to the credential-only string → both-kinds + URL-only tests red
    • (d) security.py substitution rebuilt from a diverged literal instead of the constant → prefix-pin test + chat-runner URL test red
  • Gates: scripts/local-gate.py --base origin/main (backend-only diff plan: full backend + 206 frontend guard specs; the backend stage reports the environmental baseline as rc=1, so the guard stage was run explicitly with the gate's own spec selection — results above). black gate (pinned 26.3.1), isort, flake8, mypy all clean on touched files.
  • Zero-regression proof: affected suites (test_chat_runner_coverage.py + test_security.py) run on the branch (1565 passed, 1 skipped, 0 failed) and on a git worktree at origin/main (1560 passed, 1 skipped, 0 failed); sorted failing-id sets byte-identical both directions (both empty). Full backend suite (pytest -q -n auto --dist loadgroup, testpaths test/ + src/kiro_crew/apps/builtins) both directions: branch 223 failed + 2 errors / 83,887 passed; pristine origin/main worktree 223 failed + 3 errors / 83,883 passed — the sorted failing-id sets are identical after resolving two xdist log-interleaving artifacts (each apparent delta re-run in isolation fails identically on both trees: ops_mission_control/tests/test_slack_out.py::…::test_ampersand_is_escaped_first and the connections/tool_test.py collection error). All failures are the known pre-existing environmental baseline; none involve the touched files.
  • Frontend cross-surface guards (the gate's 206-spec selection for this backend-only diff): 208 files, 5,682/5,682 passed under node 22 + vitest.
  • Screenshot evidence: not applicable — .github/workflows/screenshot-evidence.yml watches website/src/{components,pages,apps}/** and this diff touches no website/ file; the notice renders through the pre-existing msg msg-info row shipped and evidenced by fix(dashboard): warn when redaction rewrites a credential in chat text #8109.

Rebase re-verification (2026-09-07, rebased onto main 56f67aa43): the original verification above ran against the monolithic security.py. After porting over the #9183 package split and the #8318 helper hoist: isort / flake8 / mypy clean; test/test_security.py + test/test_chat_runner_coverage.py 1,178 passed 0 failed (includes all 5 tests this PR adds), test_security_facade.py frozen-manifest guards 10 passed; full backend suite re-run on the rebased branch — result posted in the PR thread with the push.

Composition hazards checked before branching

All 37 open PRs touching dashboard/chat_runner.py or security.py were scanned for the spans this PR edits. None rewrites _redaction_notice or the tag registry. Notable adjacency: #7666 rewrites the same substitution line in redact_exfiltration_urls (replace → span splice) but keeps the identical tag text — a textual conflict at most; whichever lands second rebases, and the prefix constant composes with the span-splice shape unchanged. #7626 removes nearby composition lines in chat_runner.py (adjacent-hunk conflict at most). The rest only add call sites elsewhere.

Pattern harvest

Rule candidate: when a redactor substitutes a variable placeholder (interpolated domain, path, name), export its stable prefix as a module constant beside the rewriter, build the substitution from that constant, and have user-notice consumers prefix-count it — an exact-match registry silently under-reports every variable tag. Knowingly out-of-scope sibling sites: the non-pasteable redact_exfiltration_urls call sites in chat_runner.py (titles/tool names/feed strings) and every other module that discards exfil_warnings; they redact display strings, not text a user copies commands from.

@dwu96
dwu96 requested a review from a team as a code owner September 3, 2026 22:25
@dwu96
dwu96 requested a review from CrysisDeu September 3, 2026 22:25
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of 7b48fa5b2d7e0472cb9190a6196a7255bcc7eb7a — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Notice gap closed at the right seam: prefix constant owned beside the rewriter, counted in the shared helper — no drift, no new shape.

Suggestions

  • If a third body rewriter ever appears, _redaction_notice's hand-built by-kind wording (subject join, verb, lead, three remedy branches) becomes combinatorial — a kinds list with per-kind remedy strings would keep the next addition additive rather than another branch matrix. Follow-up only; not needed for two kinds.

[DESIGN-REVIEWED] 7b48fa5

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 7b48fa5b2d7e0472cb9190a6196a7255bcc7eb7a — this comment is updated in place on each push.

Review details

The diff is a small, well-contained change: it exports EXFILTRATION_REDACTION_TAG_PREFIX, builds the redaction substitution from it, and extends the dashboard chat notice to count URL redactions in addition to credential ones. I verified the sole caller of _redaction_notice (line 3312) is gated by if cred_count or url_count, so the subject[0].upper() on an empty string is unreachable; the verb/subject/remedy branches are internally consistent; the prefix-count cannot collide with CREDENTIAL_REDACTION_TAGS; and the redaction egress behaviour itself is unchanged. No candidates were supplied and I found no grounded defect at the required bar.

No findings.

[OPUS-REVIEWED] 7b48fa5

Verdict parsed from the review's SHA-scoped output markers for commit 7b48fa5b2d7e0472cb9190a6196a7255bcc7eb7a.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 7b48fa5b2d7e0472cb9190a6196a7255bcc7eb7a: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of 7b48fa5b2d7e0472cb9190a6196a7255bcc7eb7a — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All evidence gathered and counted. Emitting the review.

First-Principles-Verdict: CONCERNS

The dashboard fix is real and cause-level, but the same silent-URL-rewrite sits unfixed at three channel surfaces that already post the credential notice — and the description never says so.

What this change ships

Intent: a user who copies chat text whose URL was redacted must be told, not find out at paste time — a FIX (issue #8132).

  1. Chat appends a notice row when a suspicious URL was rewritten — justified
  2. Notice wording now varies by kind (credential / URL / both) — justified, remedies differ
  3. Credential-only wording stays byte-identical — justified, regression-guarded
  4. URL substitution now built from a new exported prefix constant — justified, removes the drift cause
  5. Constant added to the security facade and frozen export manifest — derived (_exports.py "only import path" invariant)
  6. security.md spec paragraph — mandated same-commit rule
  7. CREDENTIAL_REDACTION_TAGS docstring re-pointed at the prefix — rides along, harmless

Watch

  • Point patch with 3 counted unfixed siblings. Grepping CREDENTIAL_REDACTION_TAGS counting sites: slack/handler.py:4025 (per-turn notice over clean_text, which redact_exfiltration_urls also rewrites), imessage/renderer.py:185 (delivered text passes through _default_redactorredact_exfiltration_urls at renderer.py:63), slack/interactions.py:3270 (approve path runs redact_exfiltration_urls(draft) at :3258, then counts credential tags only). Each posts the credential notice and stays silent on the URL rewrite — the exact defect Exfiltration-URL redaction silently rewrites pasteable dashboard chat text #8132 reports, on copyable bodies, not the "display-string call sites" the description dismisses. The general fix rides messaging/renderer.py's credential_redaction_notice and is genuinely larger — deferring is fine, but the description claims coverage ("fires everywhere the credential notice does") that is true only of the dashboard.
  • EXFILTRATION_REDACTION_TAG_PREFIX has exactly 1 real consumer (chat_runner.py:3310); the three siblings above are its natural next consumers — worth landing before the constant fossilizes as dashboard-only.

[FIRST-PRINCIPLES-REVIEWED] 7b48fa5

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 7b48fa5b2d7e0472cb9190a6196a7255bcc7eb7a and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 7b48fa5

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 7b48fa5b2d7e0472cb9190a6196a7255bcc7eb7a: <one-sentence reason>

@dwu96

dwu96 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Disposition — First Principles 🟡 CONCERNS (on bbbf77567)

Both items verified against source and adopted on the new head 85c3432c7:

Watch — 7 undeclared exception-path persists of the pasteable body. Verified real: all seven cited sites (chat_runner.py:10410, 10429, 10443, 10483, 10559, 10830, 10871) persist the assistant body via direct slot.append("assistant", …) after redaction, bypassing _flush_segment, so a tag written per-chunk lands there with no notice. Accepted-and-deferred as the review itself reads it (the gap is pre-existing and shared byte-for-byte with #8109's credential notice), and now declared instead of denied: the PR body's scope paragraph names the seven sites explicitly and distinguishes them from the display-string call sites; the in-code SCOPE: comment does the same; filed #8311 to track the gap with a suggested fix direction (extract _flush_segment's notice tail into a helper shared by all eight persists). Not widened into this PR: each exception branch has its own teardown ordering (stop cards, error rows), so notice placement there is a per-branch decision, not a mechanical redirect — and the spec pipeline's smallest-change constraint applies.

Subtraction — overclaiming security.md sentence. Adopted verbatim: "a rewrite of the pasteable chat body is never silent" now claims only the _flush_segment path, and the same sentence names the seven bypassing branches as a known gap tracked in #8311.

No production-code change in this round — the diff delta is the security.md sentence, the SCOPE: comment, and the PR body. Head also rebased onto current main (9848dd476).

@bolichen97

Copy link
Copy Markdown
Collaborator

Open PR relationship audit

This 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

  • PR #7666 is OVERLAPPING relative to this PR. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7666: CONTINUE_DEVELOPMENT. Same statement in redact_exfiltration_urls, different goals. Agree a landing order and make the second PR re-apply the first's change rather than resolving the conflict by dropping half of it. Files: src/kiro_crew/security.py, website/src/utils/sanitize.ts.
  • PR #7739 is OVERLAPPING relative to this PR. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7739: REBASE. Complementary legibility fixes on two distinct surfaces (OAuth banner vs persisted assistant body); no code or test collision. Files: src/kiro_crew/dashboard/chat_runner.py. The two independent directions used different labels; the matrix conservatively retains OVERLAPPING for coordination.
  • This PR is OVERLAPPING with PR #8136. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #8291: MERGE_DISCUSSION. Sibling application of the same notice pattern on a different surface and a different rewriter; no coverage of PR #8291's behavior and no code contact. Files: src/kiro_crew/messaging/renderer.py. The two independent directions used different labels; the matrix conservatively retains OVERLAPPING for coordination.
  • This PR is OVERLAPPING with PR #8318. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #8291: MERGE_DISCUSSION. Complementary halves of one fix that collide textually. Landing PR #8318 first and then re-applying PR #8291's by-kind wording inside the shared _append_redaction_notice helper gives URL notices on all eight persists at once; landing PR #8291 first forces PR #8318 to rebase and leaves the URL kind confined to _flush_segment until someone widens it again. Same author, so the sequencing is decidable rather than contested. Files: src/kiro_crew/dashboard/chat_runner.py.

No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit.

@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Sep 4, 2026
@NicholasRBowers NicholasRBowers added needs-pr-triage PR scanner: awaiting automated triage drive-to-green PR claimed by drive-to-green pipeline and removed needs-pr-triage PR scanner: awaiting automated triage labels Sep 7, 2026
@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: This PR has been inactive for 7+ days with failing CI. I've assessed the blockers and they appear resolvable — I'll push fixes directly to this branch as a co-author.

Assessment: All four AI review lanes are green on head 04a78cf. The two blockers are mechanical: a merge conflict against current main (rebase + resolve, guided by the PR's clear intent of surfacing the exfil-URL redaction notice in chat text), and a Dependency Audit red that is an npm-audit 120s infra timeout ("audit failed closed: npm audit timed out"), not a vulnerable dependency — a rebase push re-fires it.

If you'd prefer I don't touch this PR, add the pr-no-autofix label.

@NicholasRBowers
NicholasRBowers force-pushed the fix/exfil-url-redaction-notice-8132 branch from 04a78cf to a2e2ea2 Compare September 7, 2026 19:34
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 7, 2026
@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Pushed the rebase + conflict resolution as announced. Head is now a2e2ea2c1 (was 04a78cf04). dwu96's authorship is preserved on the commit; Kiro Crew is a co-author trailer.

What changed and why — every change, itemized:

  1. Rebased onto current main (489 commits of drift). The PR's diff intent is unchanged — git patch-id of the resolved commit is stable across the final rebase.
  2. src/kiro_crew/security.py → security package port. Main's refactor(security): split security.py into a package and drop path regex #9183 split the monolith into a package, deleting the file this PR patched. The PR's two edits were relocated to their new owners: EXFILTRATION_REDACTION_TAG_PREFIX + the substitution-built-from-it now live in security/exfil.py (beside redact_exfiltration_urls), and the CREDENTIAL_REDACTION_TAGS docstring pointer in security/redaction.py. The constant is registered in the facade import block (security/__init__.py) and the frozen export manifest (security/_exports.py), per the package's export contract — test_security_facade.py's manifest guards pass.
  3. chat_runner.py conflict resolution — kept main's structure. Main's fix(chat): fire redaction notice on the 7 exception-path assistant persists #8318 hoisted the notice into _append_redaction_notice, shared by _flush_segment and the seven exception/teardown persists (closing Redaction notice does not fire on the 7 exception-path assistant persists #8311 — the exact gap this PR's First Principles disposition deferred, in the exact fix direction it suggested). The URL prefix-count now lives in that helper, so the URL notice fires at all eight persist sites, same as the credential notice. The PR's two-arg _redaction_notice(cred_count, url_count) by-kind wording had auto-merged cleanly and is untouched.
  4. docs/system-specs/modules/security.md — the PR's doc line claimed the seven-branch gap was still open and tracked in Redaction notice does not fire on the 7 exception-path assistant persists #8311; that is stale post-fix(chat): fire redaction notice on the 7 exception-path assistant persists #8318, so the line now states the helper-shared coverage instead.
  5. PR body reconciled to the rebased diff (Fix section names the package files and the helper; a rebase re-verification paragraph added under Verification). Also reworded one Issue-body-corrections line whose "does not close Credential redaction corrupts commands meant to be pasted into a terminal (silent, no warning) #6189" phrasing GitHub was parsing as a closing keyword — closingIssuesReferences wrongly listed Credential redaction corrupts commands meant to be pasted into a terminal (silent, no warning) #6189; it now lists only Exfiltration-URL redaction silently rewrites pasteable dashboard chat text #8132.

Not changed: the redaction itself, the notice wording (credential-only string stays byte-identical to #8109's), the PR's five tests (they auto-merged and pass unmodified), and dwu96's First Principles disposition — the two adopted Watch items survive the rebase (the seven-site declaration graduated from "declared gap" to "covered via #8318's helper").

Verification on the pushed head: isort / flake8 / mypy clean; test_security.py + test_chat_runner_coverage.py + test_security_facade.py = 1,188 passed 0 failed (includes the PR's 5 tests, red-before-green pedigree unchanged); full backend suite 89,225 passed / 279 failed — the failing set is the known pre-existing host-env baseline (session-storage/home-symlink class), byte-identical to pristine main's, zero failures in any file this PR touches. Two independent model-pinned local reviews (GPT 5.6, Opus 4.8) on the resolved commit: no findings.

The Dependency Audit lane was a 120s npm-audit infra timeout on the old head; this push re-fires it — if it times out again I'll rerun only that job.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running labels Sep 7, 2026
@NicholasRBowers
NicholasRBowers force-pushed the fix/exfil-url-redaction-notice-8132 branch from a2e2ea2 to 41a884b Compare September 7, 2026 20:05
@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Round 1 fix pushed — head a2e2ea2c141a884b00.

Finding: Backend Lint & Type Check (3.12) red: the black gate flagged src/kiro_crew/security/exfil.py as a new offender ("not black-formatted"). My conflict-resolution edit there had wrapped the substitution call across three lines; black (pinned 26.3.1, --target-version py310) collapses it to one. Formatting-only — zero semantic delta (the reformat is 3 lines → 1 line of the identical expression).

Fix: ran the pinned black on the file and amended into the single commit. Re-verified: black/isort/flake8/mypy clean, 1,188 targeted tests pass. The other lanes showing cancelled on the previous head were concurrency-group supersession from the push+body-edit sequence, not verdicts; they re-fire on this head.

@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 7, 2026
The exfiltration-URL redactor rewrites a URL in the pasteable assistant
chat body to [REDACTED: suspicious URL to <domain>] and reported it only
to the server log, so a user copying a command out of chat got silently
altered text. Same root cause as #6189 (fixed for credentials in #8109),
different rewriter.

Export the URL tag's stable prefix as EXFILTRATION_REDACTION_TAG_PREFIX
in the security package (exfil.py, where the rewriter now lives after
the #9183 package split) and build the substitution from it; register it
on the facade and in the frozen export manifest. Count it in
_append_redaction_notice beside the credential-tag sum -- main hoisted
the notice into that shared helper (#8311), so every persist site that
carries the credential notice now carries the URL notice too. Extend
_redaction_notice to word the notice by kind, because the remedies
differ (re-enter the secret vs re-check the URL). The credential-only
wording is byte-identical to #8109's. The redaction itself is unchanged.

Original change by dwu96; rebased over the security-package split
(#9183) and the notice-helper hoist by Kiro Crew.

Fixes #8132

Co-authored-by: Kiro Crew <noreply@kirodotdev.github.io>
@NicholasRBowers
NicholasRBowers force-pushed the fix/exfil-url-redaction-notice-8132 branch from 41a884b to 7b48fa5 Compare September 7, 2026 20:56
@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Round 2 pushed — head 41a884b007b48fa5b2.

Why: main's #9189 ("never waive exfil redaction for model-authored URLs") landed after this PR's last rebase and rewrote the same docs/system-specs/modules/security.md region, making the PR CONFLICTING again. Docs-only conflict; no code overlap.

Resolution: kept this PR's redact_exfiltration_urls bullet (the EXFILTRATION_REDACTION_TAG_PREFIX contract + by-kind notice), and took main's rewritten Frontend mirror bullet plus its new "Why there is no backend twin" bullet verbatim — my branch's copy still described the isPrefilledIssueUrl() length waiver that #9189 deliberately withdrew, so keeping it would have re-documented a removed security carve-out.

Verification on this head: rebased twice (main advanced mid-rebase); git patch-id of the commit is byte-identical to the previously reviewed diff apart from the resolved doc region; black/isort/flake8/mypy clean; 1,204 targeted tests pass including #9189's own test_redaction_mirror_parity.py (which pins the waiver's absence tree-wide — this PR adds no waiver identifiers, so it passes untouched).

Also for the record: the Backend Tests (Windows) (3) red on the previous head was a runner-load flake — test_playwright_cli_installer.py's PowerShell tests hit the POSIX-only os.killpg in their timeout-cleanup path, code this PR does not touch and which is green on main; this push supersedes the rerun I had queued.

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running labels Sep 7, 2026
@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca] — Disposition: accepted-and-deferred (Watch item 1: three channel surfaces post the credential notice but stay silent on a URL rewrite)

  • Point patch with 3 counted unfixed siblings (slack/handler.py:4025, imessage/renderer.py:185, slack/interactions.py:3270)

Finding verified against source and accepted: all three sites run redact_exfiltration_urls over a copyable delivered body and then count CREDENTIAL_REDACTION_TAGS only — the same silent-rewrite defect #8132 reports, at channel egress. Deferred rather than widened in, as the review itself allows: the general fix rides messaging/renderer.py's localized credential_redaction_notice key, so covering the three sites is a cross-module, locale-touching change, out of proportion for this dashboard-scoped PR. Tracked in #9313 (deferred-finding, auto-fixable, assigned, Due 2026-09-21) with per-site fix direction and acceptance criteria. This ruling covers findings that ask channel-egress notice parity to land inside THIS PR, wherever the site.
The description-overclaim half: the "fires everywhere the credential notice does" sentence is written inside the chat_runner bullet and scoped by its own clause ("the shared helper ... called by _flush_segment and the seven exception/teardown persists"), so it claims dashboard persist-site coverage, not channel egress. THIS comment and #9313 are the durable record naming the three channel surfaces as out of scope; the body is deliberately not edited on a clean head, because a body edit re-triggers the full review round (repo-known behavior) for zero diff change.

self-added: no

@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca] — Disposition: accepted-and-deferred (Watch item 2: EXFILTRATION_REDACTION_TAG_PREFIX has exactly one real consumer)

  • Prefix constant should gain its three natural channel consumers before it fossilizes as dashboard-only

Accepted, and folded into the same tracked work as Watch item 1: issue #9313's fix direction is exactly "count EXFILTRATION_REDACTION_TAG_PREFIX by prefix beside the CREDENTIAL_REDACTION_TAGS sum at each of the three sites" — the constant's docstring already advertises it as the detection contract for any consumer, so the three channel sites adopt it rather than re-deriving the tag shape. One consumer today is the intended minimal state for this PR (issue #8132 is dashboard-scoped); #9313 (deferred-finding, assigned, Due 2026-09-21) is where the consumer count grows. This ruling covers findings premised on the constant's consumer count while #9313 is open.

self-added: no

@NicholasRBowers

Copy link
Copy Markdown
Contributor

🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Review-ready at head 7b48fa5b2.

  • PR Readiness: passed · 64/64 checks green · mergeable · 0 unresolved threads
  • All five AI review lanes complete on this head: GPT 5.6 pass, Opus 4.8 pass, Design PASS, UX PASS, First Principles 🟡 CONCERNS (advisory) — both Watch items answered above as accepted-and-deferred, tracked in URL-rewrite notice missing at 3 channel surfaces that already post the credential notice #9313 (URL-notice parity at the 3 channel surfaces, the prefix constant's next consumers)
  • dwu96's original design, tests, and First Principles disposition are intact through the rebase; authorship preserved with a Kiro Crew co-author trailer

Remaining gate is human maintainer review. Auto-merge is deliberately NOT armed.

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tech Lead review — approved.

Additive-only to the redaction pipeline, verified at source rather than from the description. redact_exfiltration_urls still redacts exactly what it redacted before: the substitution changed from the inline literal f"[REDACTED: suspicious URL to {domain}]" to f"{EXFILTRATION_REDACTION_TAG_PREFIX}{domain}]" with the constant defined as "[REDACTED: suspicious URL to " — byte-identical output. security/exfil.py is +14/-1 and touches neither _exfil_url_warning, _URL_RE, the heuristic tiers, _HARD_CREDENTIAL_RE, nor any carve-out. No detection path, no threshold, no exemption is altered. This adds a notice; it relaxes nothing.

The notice cannot leak what was redacted. Every branch of _redaction_notice composes from counts and fixed remedy prose. The redacted domain is never interpolated, and by construction the notice is built after the tag has already replaced the bytes. Prefix-counting is also fail-safe in the only direction it can be wrong: assistant text containing the literal prefix over-warns, never under-warns.

Scope is proportionate. 8 files, +207/-52, of which 108 lines are tests and the docs change is a single spec bullet. The behavioral surface is one prefix constant, one substitution rebuilt from it, and by-kind wording in one existing helper — no new shape, and the credential-only string stays byte-identical to #8109 under a regression guard.

i18n: no obligation added. The notice is a server-built English string rendered through #8109's pre-existing msg msg-info row; zero locale files are touched. The three channel surfaces that route through the localized credential_redaction_notice key are correctly left alone and tracked in #9313 rather than half-migrated here.

Findings: none blocking. First Principles 🟡 CONCERNS is advisory, and both Watch items are dispositioned accepted-and-deferred with a tracking issue. Design's suggestion — that a third body rewriter would make the hand-built by-kind branch matrix combinatorial — is correct and worth doing at that point, not at two kinds.

@bolichen97
bolichen97 merged commit ad26e02 into main Sep 8, 2026
64 checks passed
@bolichen97
bolichen97 deleted the fix/exfil-url-redaction-notice-8132 branch September 8, 2026 00:47
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 8, 2026
@chenmingwei23 chenmingwei23 removed the drive-to-green PR claimed by drive-to-green pipeline label Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exfiltration-URL redaction silently rewrites pasteable dashboard chat text

4 participants