Redact secret-shaped fields by default, countable per field path
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

AUDIT-WP-0004-T04, closing the workplan.

Decision (Bernd): default to redaction, allow rejection per sender. Losing an
audit record over one field is worse than storing it masked, but a
higher-assurance channel must be able to refuse rather than mask. secret_policy
is set per sender identity in AUDIT_CORE_SENDERS and defaults to redact.

Detection now covers the whole payload at any depth, including lists, rather
than only the top level of data. Under redaction the value is masked and the
key is preserved: dropping the key would hide that the sender transmitted the
field at all, which is exactly what an operator needs in order to stop it. The
stored record carries details.redaction with policy and affected paths, so a
reader never has to infer whether what they see is what was sent.

Idempotency is unaffected - the payload hash is taken over the original request
body, so redaction is deterministic and a resubmission still reconciles as a
duplicate.

Both outcomes are counted durably by sender, source, action and field path,
exposed at GET /v1/secret-findings. Per-path aggregation is the point: the
actionable unit is "stop emitting data.auth.token on membership.added", not
"there were 47 redactions". Counters survive restart because the fix they drive
lives in another service.

Contract doc updated to match. Tests 46 -> 50. WP-0004 is finished.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-10 16:02:22 +02:00
parent 0ad526c2d8
commit 576caa2665
8 changed files with 441 additions and 29 deletions

View file

@ -4,7 +4,7 @@ type: workplan
title: "Make the event receiver correct and operable under load"
domain: infotech
repo: audit-core
status: proposed
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-08-10"
@ -150,7 +150,7 @@ grants every tenant and says so.
```task
id: AUDIT-WP-0004-T04
status: todo
status: done
priority: medium
state_hub_task_id: "5cf5c412-965a-4a56-aeef-e965f2861c51"
```
@ -172,6 +172,30 @@ agree with the code.
Done when the redaction rule is explicit, applied to the whole payload, and
consistent between the contract and the implementation.
Decided 2026-08-10 (Bernd): **default to redaction, allow rejection per
sender.** Losing an audit record over one field is worse than storing it
masked, but a higher-assurance channel must be able to refuse rather than
mask. `secret_policy` is set per sender identity in `AUDIT_CORE_SENDERS`,
defaulting to `redact`.
Done 2026-08-10: `audit_core.redaction` detects secret-shaped keys at any
depth across the whole payload, including lists. Under redaction the value is
masked and the **key is preserved** — dropping it would hide that the sender
transmitted the field at all, which is what the operator needs in order to
stop it. The stored record carries `details.redaction` with the policy and the
affected paths, so a reader never has to infer whether what they see is what
was sent. Idempotency is unaffected: the payload hash is over the original
body, so redaction is deterministic and replay still reconciles.
Both outcomes are counted durably by sender, source, action and **field
path**, exposed at `GET /v1/secret-findings`. Per-path aggregation is the
point: the actionable unit is "stop emitting `data.auth.token` on
`membership.added`", not "there were 47 redactions". Counters survive restart
because the fix they drive lives in another service. A non-empty list is a
backlog item for the sender, not a steady state.
Contract updated to match; tests 46 -> 50.
## T05 - Provide the operator read surface
```task