DocumentStore wrote one audit row per upload and per user delete carrying the
acting citizen's raw BSN as AuditEntry.Actor, persisted to SQLite — on a
store whose own doc comment says it holds metadata only, never file content
"or other PII". Same shape as RB-02, in a second store.
Masked at the two citizen call sites rather than inside Audit, because the
third actor is the literal "admin" and MaskTail("admin", 3) is "**min";
masking centrally would mean guessing which actors are BSNs and which are
role names. Audit's doc comment now states that actors arrive redacted.
StoredDocument.Owner is untouched: it is the authorization key that
DeleteOwned, ForeignIds and RB-01's content check all compare against, so the
BSN stays where it is load-bearing and leaves the trail where it was only
decoration. No endpoint exposes AuditLog, so no response shape changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2.1 KiB
RB-04 — mask the BSN recorded as AuditEntry.Actor
Status: implemented · 2026-08-27 · Source findings: 07-bio2-compliance.md BIO-005 · 99-backlog.md RB-04
What was wrong
DocumentStore writes one audit row per upload and per user delete, with the acting
citizen's raw BSN as AuditEntry.Actor, persisted to SQLite. The class's own doc comment
says "The audit log holds metadata only (never file content or other PII)" — a BSN in
every row is precisely other PII. Same failure shape as RB-02, in a second store.
What changed
| File | Change |
|---|---|
Data/DocumentStore.cs Add |
Audit("upload", …, Pii.MaskTail(owner, 3)) |
Data/DocumentStore.cs DeleteOwned |
Audit("delete-user", …, Pii.MaskTail(owner, 3)) |
Data/DocumentStore.cs Audit |
doc comment: actors arrive already redacted |
UploadAccessTests.cs |
new The_document_audit_trail_records_a_masked_actor |
Masked at the two call sites, not inside Audit — unlike RB-03, where masking in the
mapper was the point. Audit's third actor is the literal "admin" (from AdminDelete),
and MaskTail("admin", 3) is "**min": masking centrally would mean guessing which
actors are BSNs and which are role names. The contract is stated on Audit instead.
StoredDocument.Owner is untouched, per the ticket. It is the authorization key —
DeleteOwned, ForeignIds and now the RB-01 content check all compare against it — so it
has to stay whole. The BSN remains where it is load-bearing and leaves the trail where it
was only decoration.
Nothing reads DocumentStore.AuditLog today (no endpoint exposes it), so this is a
data-at-rest fix with no response-shape change.
Verification
dotnet format --verify-no-changes clean. dotnet test: 252 passed, 1 failed — the
pre-existing OpenZaakIntegrationTests.Admin_cases_…, which needs a live container.