Redact secrets in denial alert URLs via the summarizer - #38
Conversation
|
| Filename | Overview |
|---|---|
| internal/alerting/summarizer.go | Core change: extends Summarize to return JSON with redacted URLs via a single LLM call; adds promptLineSanitizer for URL and Reason fields; count-mismatch guard prevents leaking unredacted URLs. d.Method is not sanitized in the same numbered-list lines. |
| internal/alerting/alerting.go | Wires the new two-return Summarize interface; drops URL list on error to prevent credential leakage; switches Slack message to use BlockedCount for display — looks correct. |
| internal/alerting/sender.go | Adds BlockedCount to Message; conditionally renders the 'Requests blocked:' list only when redacted URLs are present — correct behaviour. |
| internal/alerting/summarizer_test.go | Good coverage: happy path, count mismatch, adapter failure, nil adapter, newline injection, and code-fence stripping all tested. |
| internal/admin/notification_integration_test.go | Integration test mock updated to match new Summarize signature; mock returns denials as-is, which is fine for plumbing tests that don't exercise redaction logic. |
Reviews (4): Last reviewed commit: "fix(alerting): sanitize newlines in summ..." | Re-trigger Greptile
The Slack denial summary could leak credentials passed as URL query-string parameters (e.g. api_key=) — both in the LLM summary and in the raw "Requests blocked:" list. Rather than a separate deterministic redaction module, fold redaction into the alert summarizer: a single LLM pass now returns both the summary and the redacted form of each denied URL, and the Slack sender renders only those redacted URLs. If the LLM is unavailable or returns untrustworthy output, the URL list is omitted (count + generic summary only) instead of leaking raw URLs. Upstream proxying is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
21ecc67 to
c89651f
Compare
The notification integration test's mockSummarizer still implemented the old two-return Summarize signature, breaking `go vet ./...` in CI lint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile flagged a prompt-injection vector: d.URL and d.Reason were embedded verbatim into the numbered-list prompt the redaction LLM maps its output against. A literal newline (especially in the judge-generated Reason) could inject a fake numbered entry and desync the index->URL mapping while still passing the URL-count check. Collapse CR/LF/tab to spaces in both fields before building the prompt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @greptile — addressing the review in 1706999. P1 — Prompt injection via newlines in URL/Reason (fixed). Good catch, especially since Summary field lacks programmatic redaction verification (acknowledged, by design). The URL slots can be hard-checked because they're a fixed-length array we map by index; free-form summary text can't be verified the same way without a deterministic secret-detector — which is exactly the static-redaction layer we deliberately chose not to build here. The summary is redacted by the same LLM pass, and the newline sanitization above also reduces the injection surface for it. If the LLM fails or returns untrusted output we fall back to a count-only generic summary with no URLs, so the failure mode is safe. I'd treat hardening the summary further (or dropping URL detail from it entirely) as a follow-up if we want a stronger guarantee. |
|
The fixes look solid. Both points are addressed well: P1 (prompt injection via newlines): The Summary field acknowledgment: The reasoning is sound. The URL slots get a structural integrity check (exact count match, index-aligned); free-form prose can't get the same treatment without a deterministic secret-detector. The failure mode (LLM down → no URLs, generic count summary) is appropriately conservative. If summary-level guarantee needs hardening later, dropping URLs from the summary field entirely or running a lightweight regex scrub as a backstop would be the natural next steps — but neither is needed to ship this safely. One minor thing to double-check: LGTM overall — nice clean fix. Tip: You can customize Greptile's behavior for this repo with |
Summary
The Slack denial summary could surface credentials passed as URL query-string parameters (e.g.
api_key=) — both in the LLM-generated summary text and in the raw "Requests blocked:" URL list. CrabTrap already redacted sensitive headers, but not query-string credentials in this notification.This folds redaction into the existing alert summarizer instead of adding a separate redaction module:
[REDACTED]), in order.Notes
Test plan
go test ./internal/alerting/[REDACTED], not the raw secret🤖 Generated with Claude Code