fix(security): route the heartbeat-incomplete log line through the context redactor - #7761
Conversation
…ntext redactor `test_security_posture.py::TestGateSideLogRedactorSpelling:: test_no_new_gate_side_log_line_reads_the_baseline_redactor` is red on `main`: `slack/gateway.py` has 7 gate-side log sites reading the BASELINE redactor while `_BASELINE_LOG_SITE_CENSUS` records 6. Backend Tests (Windows) (3) therefore fails on the merge ref of every open PR. The 7th site is the heartbeat-incomplete `logger.info`, which #7424 gave `redact_and_truncate(task_text, 80)` -- a hand-rolled baseline pair plus a slice. The Slack gateway runs IN the composition process, so raising the census is the wrong branch of the test's own instruction: a companion's extra credential and cookie regexes are reachable here, and the baseline pass is a real downgrade. Routed it through `redact_log_via_context` instead, slicing AFTER redaction -- both the invariant #7424 established and the one that helper hands its callers by contract ("Callers keep their own truncation, and must apply it AFTER this returns"). The sibling two lines below stays on `redact_and_truncate` because it feeds a delivery rather than a log, and the two want different failure modes on a host that cannot compose. Census entry unchanged at 6: the count returns to what it records, so the two-way ratchet (`test_the_census_holds_no_slack`) stays satisfied too.
First Principles Review (Fable 5) — ✅ PASSPremise-level review of All claims verified: the census pins First-Principles-Verdict: PASS Unwedges a red required check on main by converting the one drifted site to the exact remedy the ratchet's own docstring prescribes — nothing rides along. What this change shipsIntent: make main's required security-posture check green again by fixing the one log site it flags. FIX.
No new surface: zero new symbols, keys, or parameters — the change consumes an existing helper ( [FIRST-PRINCIPLES-REVIEWED] a075ce6 |
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: |
Design Review (Fable 5) — ✅ PASSDesign-level review of The change checks out end-to-end: Design-Verdict: PASS Minimal one-line fix on the correct branch of the ratchet's own remedy: right helper for the sink kind, invariant preserved, unblocks a red main. [DESIGN-REVIEWED] a075ce6 |
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: |
The shard-3 failure (test_no_new_gate_side_log_line_reads_the_baseline_redactor) was upstream drift from kirodotdev#7424, fixed on main by kirodotdev#7761. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Problem / Motivation
test_security_posture.py::TestGateSideLogRedactorSpelling::test_no_new_gate_side_log_line_reads_the_baseline_redactoris red on
main:Backend Tests (Windows) (3)therefore fails on the merge ref of every open PR,whatever that PR touched.
Why it matters
Two costs, and the second is the real one:
is scanned with the OSS pass even on a host that has a companion loaded, so the
companion's extra credential and cookie regexes never apply to that text — which is
exactly the class
redact_log_via_contextand this census exist to close.What changed (motivation → approach → change)
Which site. The 7th is the heartbeat-incomplete
logger.infoin_on_heartbeat_task, which reached its text throughredact_and_truncate(task_text, 80)— a hand-rolledredact_exfiltration_urls/redact_credentialspair plus a slice, i.e. the baseline spelling the scanner islooking for.
Which branch of the fix. The test offers two: call
redact_log_via_context, orraise the census number and record which process makes the baseline correct there.
Raising it is wrong here — the Slack gateway runs in the composition process, so a
companion policy is genuinely reachable and the baseline is a real downgrade, not a
no-op.
The change. That one call site now reads
redact_log_via_context(task_text)[:80]. Truncation stays after redaction: that isboth the invariant #7424 established (a credential straddling the boundary would be cut
into an unmatchable fragment) and the contract
redact_log_via_contexthands itscallers in its own docstring — "Callers keep their own truncation, and must apply it
AFTER this returns." So the slice at the call site is the sanctioned shape, not a
regression away from a centralized helper.
What deliberately did not change. The sibling two lines below —
task_safe = redact_and_truncate(task_text, 100)— feeds_deliver_result, adelivery rather than a log, and the two spellings want different failure modes on a
host that cannot compose (a log line must not raise; an egress sink should). Left alone,
with a comment at the call site recording why the two now differ. Whether that delivery
path should move to the fail-closed
redact_via_contextis a separate question on theother axis (
NON_EGRESS_REDACTION_MODULES), which is green today; out of scope here.Census entry unchanged at 6. The count returns to what the census records, so the
two-way ratchet (
test_the_census_holds_no_slack, which forbids a census looser thanthe code) stays satisfied without editing the number.
Tests
No new test, and the reason is that the repo already owns this exact regression:
the scanner test is a property check, not a list, so if this site drifts back to a
baseline spelling the count goes 6 → 7 and
test_no_new_gate_side_log_line_reads_the_baseline_redactorfails again. That is theguard, and it is the one that caught this.
TestRedactLogViaContext.CONVERGED_LOG_SITESis deliberately not extended toslack/gateway.py: its second assertion requires the file to contain no directredact_credentials(/redact_exfiltration_urls(calls at all, and this module stillhas six other counted log sites plus its delivery paths on the baseline. Adding it
would fail for reasons this PR is not fixing.
Manual verification
Ran the census scanner against the two file versions to isolate the cause, using the
scanner from
main:slack/gateway.py7d05970c7^)7d05970c7)logger.infoAttribution checked rather than assumed: the census entry has read
6since #7278(
f1f7743bd) and was never edited, and re-running the pre-#7725 scanner againstthe post-#7424 file also counts 7 — so this is not an artifact of #7725 widening the
scan.
Gates:
flake8clean on the changed file (the oneB042is pre-existing, verified bystashing),
isortclean,mypy --platform linux src/kiro_crewat its 20-error / 9-filebaseline with 0 in this file, black and subprocess-encoding gates pass in scope.
Focused suites:
test_security_posture.py,test_platform_context.py,test_platform_cpp_seam_coverage.py, all 12test_heartbeat_*.py, andtest_slack_sessions_view.py— 334 passed.The full backend suite is not runnable on this host: an installed companion entry point
makes the public core's
resolve_profile()returnenterprise, which fail-closes withno booted companion. CI is the backstop for that.
Related Issues
no linked issue: this is a census entry that went stale when a call site changed; there
is no tracked issue for it.
Pattern harvest
Rule candidate: review-prompt
Pattern: a change that converts slice sites to a redaction helper picks the helper by
shape (
redact+ truncate) when the choice is actually governed by sink kind —a log line wants
redact_log_via_context(never raises, reaches the companion), anegress sink wants
redact_via_context(fail-closed). #7424 converted 12 modulescorrectly on the redact-before-bound axis and introduced one new baseline-spelled log
site while doing it, because nothing in the diff's own framing asks the second question.
A review prompt that asks "for each redaction call site this diff adds or moves: is the
consumer a log or an egress sink, and does the spelling match?" catches this class at
review time rather than at the ratchet.
Worth noting for whoever tunes the gate: the census entry has been
6since #7278, andthe count crossed to 7 at #7424 under the scanner in place at the time — so this
regression was mechanically detectable when it landed.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)