# RB-01 — authorize `GET /uploads/{id}/content` and `/uploads/status` Status: **implemented** · 2026-08-27 · Source findings: `07-bio2-compliance.md` BIO-004 · `99-backlog.md` RB-01 ## What was wrong `GET /uploads/{documentId}/content` took `(string documentId)` — no `HttpContext`, so no authorization was possible at all. It streams diploma and identity scans; the only protection was the unguessability of the document GUID. `DELETE` on the same resource has been owner-scoped (`DocumentStore.DeleteOwned`) since it was written. `GET /uploads/status?localIds=` had the same shape, and leaks less but still confirms whether a given client-chosen `localId` exists anywhere in the store, plus its documentId. ## What changed | File | Change | | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `Program.cs` `/uploads/{documentId}/content` | takes `HttpContext`; allowed for the owning `ZorgverlenerCaller` or a caller passing `Authz.CanBeoordelen`; else `404` | | `Program.cs` `/uploads/status` | takes `HttpContext`; scoped to `ctx.Zorgverlener().Bsn` | | `Data/DocumentStore.cs` `ByLocalIds` | second parameter `owner`; filters on it (the only call site is the endpoint above) | | `tests/BigRegister.Tests/UploadAccessTests.cs` | **new** — 5 cases | The two actor kinds are matched, not branched on a boolean, because `ctx.Zorgverlener()` **throws** for a `MedewerkerCaller` — a behandelaar reading an aanvraag's linked documents (`beoordeling-documenten.component.ts`) is a legitimate caller here: ```csharp var allowed = ctx.Caller() switch { ZorgverlenerCaller z => doc?.Owner == z.Bsn, var caller => Authz.CanBeoordelen(caller), }; ``` **404, not 403**, per the ticket: a foreign document id must not be distinguishable from one that never existed. `doc is null || !allowed` collapses both to the same answer, and `/uploads/status` reports a foreign `localId` as `"unknown"` — the same word an id that never existed gets. ## Known residual — this endpoint is reached without identity headers Both callers link to the URL directly (`` in `beoordeling-documenten.component.ts`, `previewUrl` in `libs/shared/src/upload/upload.adapter.ts`), so the request is a plain browser navigation that carries **no** `X-Medewerker` / `X-Subject` header and never passes through an Angular interceptor. `StubIdentityProvider` therefore resolves it to the seeded citizen, which owns every document in the POC, so both links keep working — by coincidence, not by authorization. That coincidence **is** BIO-002, and it is fixed by **RB-09** (making `IIdentityProvider` able to express "no identity"), not here. RB-09 will need this endpoint to receive a real credential — a signed URL or a cookie — rather than the ambient default. ## Verification `dotnet build` clean. `dotnet test`: **250 passed, 1 failed** — the failure is `OpenZaakIntegrationTests.Admin_cases_returns_the_seeded_zaak_mapped_through_real_HTTP_and_JWT`, which needs a live OpenZaak container and **fails identically on a stashed tree**, i.e. it pre-dates this change.