Skip to content

fix(security): route the heartbeat-incomplete log line through the context redactor - #7761

Merged
bolichen97 merged 1 commit into
mainfrom
fix/gateway-log-redactor-census
Sep 2, 2026
Merged

fix(security): route the heartbeat-incomplete log line through the context redactor#7761
bolichen97 merged 1 commit into
mainfrom
fix/gateway-log-redactor-census

Conversation

@bolichen97

Copy link
Copy Markdown
Collaborator

Problem / Motivation

test_security_posture.py::TestGateSideLogRedactorSpelling::test_no_new_gate_side_log_line_reads_the_baseline_redactor
is red on main:

AssertionError: New gate-side log/audit line(s) reading the BASELINE redactor
slack/gateway.py: 7 sites, census says 6

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:

  1. A required check is red for every contributor for a reason unrelated to their change.
  2. The site itself is a genuine gap. A gate-side log line reading the baseline redactor
    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_context and this census exist to close.

What changed (motivation → approach → change)

Which site. The 7th is the heartbeat-incomplete logger.info in
_on_heartbeat_task, which reached its text through
redact_and_truncate(task_text, 80) — a hand-rolled redact_exfiltration_urls /
redact_credentials pair plus a slice, i.e. the baseline spelling the scanner is
looking for.

Which branch of the fix. The test offers two: call redact_log_via_context, or
raise 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 is
both the invariant #7424 established (a credential straddling the boundary would be cut
into an unmatchable fragment) and the contract redact_log_via_context hands its
callers 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, a
delivery 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_context is a separate question on the
other 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 than
the 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_redactor fails again. That is the
guard, and it is the one that caught this.

TestRedactLogViaContext.CONVERGED_LOG_SITES is deliberately not extended to
slack/gateway.py: its second assertion requires the file to contain no direct
redact_credentials( / redact_exfiltration_urls( calls at all, and this module still
has 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.py counted sites
before #7424 (7d05970c7^) 6
at #7424 (7d05970c7) 7 — new: the heartbeat logger.info
this branch 6

Attribution checked rather than assumed: the census entry has read 6 since #7278
(f1f7743bd) and was never edited, and re-running the pre-#7725 scanner against
the post-#7424 file also counts 7 — so this is not an artifact of #7725 widening the
scan.

Gates: flake8 clean on the changed file (the one B042 is pre-existing, verified by
stashing), isort clean, mypy --platform linux src/kiro_crew at its 20-error / 9-file
baseline 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 12 test_heartbeat_*.py, and
test_slack_sessions_view.py334 passed.

The full backend suite is not runnable on this host: an installed companion entry point
makes the public core's resolve_profile() return enterprise, which fail-closes with
no 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), an
egress sink wants redact_via_context (fail-closed). #7424 converted 12 modules
correctly 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 6 since #7278, and
the count crossed to 7 at #7424 under the scanner in place at the time — so this
regression was mechanically detectable when it landed.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

…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.
@bolichen97
bolichen97 requested a review from a team as a code owner September 1, 2026 23:19
@bolichen97
bolichen97 requested a review from smeyffret September 1, 2026 23:19
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Premise-level review of a075ce6df48e7c41e87b2640e56dca69efd4af07 — 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 claims verified: the census pins slack/gateway.py at 6 (test/test_security_posture.py:255), the helper's docstring carries the truncate-after contract (platform/context.py:788), and the diff is one call-site conversion plus its import and an explanatory comment. Both spellings redact before truncating, so the only behavioral delta is exactly the declared one: the log line now reaches a loaded companion's redaction pass (and withholds text when composition fails, per the helper's documented contract).

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 ships

Intent: make main's required security-posture check green again by fixing the one log site it flags. FIX.

  1. Heartbeat-incomplete log text is now scanned with the companion-aware redaction pass — justified (closes the gap the ratchet exists to catch).
  2. The census scanner test stops failing on every open PR — justified (the reported defect; unwedges a red build).
  3. On a host whose companion fails to compose, that line's text is withheld rather than baseline-redacted — justified (inherited from the helper's documented fail-safe contract, declared in the description).
  4. Call-site comment recording why the sibling delivery line keeps the other spelling — justified (the asymmetry would otherwise read as an oversight and get "fixed").

No new surface: zero new symbols, keys, or parameters — the change consumes an existing helper (redact_log_via_context, platform/context.py:737), which is the delete-option answer, not a second spelling. The six remaining baseline sites in this module are census-recorded debt the repo has already ratcheted and documented as "not a to-do list" — a decision this repository recorded, not one to relitigate here.

[FIRST-PRINCIPLES-REVIEWED] a075ce6

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

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

Review details

No findings.

[OPUS-REVIEWED] a075ce6

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

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

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

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

The change checks out end-to-end: redact_log_via_context is deliberately excluded from _BASELINE_REDACTORS (test/test_security_posture.py:59), so the converted site drops the slack/gateway.py count back to the census value of 6, and the [:80] slice after redaction preserves the redact-before-truncate invariant that redact_and_truncate itself documents (src/kiro_crew/security.py:12370). The chosen fix branch (convert, not raise the census) is right since the gateway runs in the composition process where a companion policy is reachable, and the sibling delivery path is correctly left on the egress axis.

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

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

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

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] a075ce6

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

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 2, 2026
@bolichen97
bolichen97 merged commit 74ffc28 into main Sep 2, 2026
68 checks passed
@bolichen97
bolichen97 deleted the fix/gateway-log-redactor-census branch September 2, 2026 00:11
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 2, 2026
7487 added a commit to 7487/KiroCrew that referenced this pull request Sep 4, 2026
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>
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.

2 participants