Files
atomic-design-poc/docs/project/refactor-backlog-setup/refactor-backlog/implementation/rb-03.md
T
ehoandClaude Opus 5 487818e67a fix(privacy): mask the owner BSN on the cross-owner case lists (RB-03)
Mappers.ToAdminSummaryDto set Owner to the raw BSN. Both consumers are
cross-owner lists read by someone who is not the subject — GET /admin/cases
and GET /werkvoorraad — while GET /beoordeling/{id}, the detail view of the
same data, already masked it. The detail screen showed ******782 and the list
one click earlier showed the whole thing.

Masked in the mapper rather than at each endpoint, so a third cross-owner
list cannot be added that forgets to.

MaskTail moves out of Program.cs into Domain/People/Pii.cs: it now has
callers in Contracts, Program.cs and (once RB-04 lands) Data, and a second
hand-rolled copy is how one of them drifts into leaking. Documented as
idempotent, which is what lets /beoordeling/{id} keep its own call —
IZaakSource has a second implementation whose Owner is mapped from the
OpenZaak zaak identificatie, so that endpoint should not depend on which
source answered.

No frontend change: all three consumers display the value, and the parse
boundaries only require a non-empty string.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 10:52:29 +02:00

2.6 KiB

RB-03 — mask the owner BSN on the cross-owner case lists

Status: implemented · 2026-08-27 · Source findings: 07-bio2-compliance.md BIO-003 · 99-backlog.md RB-03

What was wrong

Mappers.ToAdminSummaryDto set Owner = a.Owner — the raw BSN. Two endpoints consume it, both cross-owner lists read by someone who is not the subject:

  • GET /admin/cases (cases:manage)
  • GET /werkvoorraad (aanvraag:beoordelen)

GET /beoordeling/{id} — the detail view of the same data — already masked. So the detail screen showed ******782 while the list one click earlier showed the whole BSN.

What changed

File Change
Domain/People/Pii.cs newPii.MaskTail, moved out of Program.cs
Contracts/Mappers.cs Owner = Pii.MaskTail(a.Owner, 3)
Program.cs local MaskTail deleted; two call sites point at Pii.MaskTail
AdminCasesTests.cs asserts the masked value and that DemoOwner does not appear
WerkvoorraadTests.cs same assertion, replacing the IsNullOrEmpty one

Masked in the mapper, not at the endpoints. The point of the ticket is that both lists inherit it, so a third cross-owner list cannot be added that forgets to mask.

MaskTail moved to Domain/People/Pii.cs because it now has three callers across three folders (Contracts, Program.cs, and Data once RB-04 lands), and a second hand-rolled copy is how one of them drifts into leaking. It is documented as idempotent, which is what lets /beoordeling/{id} keep its own call: IZaakSource has a second implementation (OpenZaakZaakSourceZgwZaakMapper, which maps Owner from the zaak identificatie), so that endpoint's guarantee should not depend on which source answered.

Blast radius on the frontend — none

Both consumers use the value for display only (admin-cases.page.ts:101, beoordeling-view.ts:40, werkvoorraad-item-view.ts:28); the parse* boundaries require a non-empty string, which a masked BSN still is. Nothing keys, filters or looks up by owner.

Verification

dotnet format --verify-no-changes clean. dotnet test: 251 passed, 1 failed — the pre-existing OpenZaakIntegrationTests.Admin_cases_…, which needs a live container.