Files
atomic-design-poc/docs/project/refactor-backlog-setup/refactor-backlog/implementation/rb-04.md
T
ehoandClaude Opus 5 fbd27ed641 fix(privacy): mask the BSN recorded as the document audit Actor (RB-04)
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>
2026-08-27 10:54:07 +02:00

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.