From ec3ed2c62d531e691142bfaa092f2ef60b378ac9 Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Fri, 2 Oct 2026 09:06:14 +0200 Subject: [PATCH] feat(domain): refuse non-PDF, infected or unscannable diplomas before storing (refs #192) Co-Authored-By: Claude Opus 5.5 (1M context) --- services/domain/Big.Application/ProvideDocuments.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/services/domain/Big.Application/ProvideDocuments.cs b/services/domain/Big.Application/ProvideDocuments.cs index 6b5b8f0..ea56df9 100644 --- a/services/domain/Big.Application/ProvideDocuments.cs +++ b/services/domain/Big.Application/ProvideDocuments.cs @@ -31,7 +31,7 @@ public enum ProvideDocumentsOutcome /// /// The provide-documents use case (S-10a/S-10b): a zorgprofessional uploads the diploma their -/// registration is parked waiting for. The document is stored in ZGW via the ACL (§8.1), then the +/// registration is parked waiting for. Only a PDF that scans clean is accepted (S-29, ADR-0036). The document is stored in ZGW via the ACL (§8.1), then the /// WachtOpDocumenten task is completed so the registratie process leaves the 30-day wait and continues /// to beoordeling (ADR-0017). Owner-scoped by bsn. Both steps are best-effort about missing preconditions /// (mirroring ): storage needs an opened zaak, and completion needs a @@ -49,6 +49,17 @@ public sealed class ProvideDocuments(IRegistrationStore store, IWorkflowClient w if (registration is null || registration.Bsn != command.Bsn) return ProvideDocumentsOutcome.NotFound; + // Only a clean PDF goes any further (S-29, ADR-0036): checked after ownership, so a stranger + // learns nothing about the file, and before anything is stored or the wait is completed. + if (!command.Content.AsSpan().StartsWith("%PDF-"u8)) + return ProvideDocumentsOutcome.NotAPdf; + + switch (await scanner.ScanAsync(command.Content, ct)) + { + case ScanVerdict.Infected: return ProvideDocumentsOutcome.Infected; + case ScanVerdict.Unavailable: return ProvideDocumentsOutcome.ScannerUnavailable; + } + // Store the diploma against the zaak (once it is opened) — the ACL is the only ZGW caller (§8.1). if (registration.ZaakUrl is not null) await acl.StoreDiplomaAsync(