Skip to content

Security follow-up #343: state-validatie, image-allowlist & readOnlyRootFilesystem#347

Open
robbertbos wants to merge 1 commit into
experimenteel/samenwerkenfrom
worktree-info-hardening
Open

Security follow-up #343: state-validatie, image-allowlist & readOnlyRootFilesystem#347
robbertbos wants to merge 1 commit into
experimenteel/samenwerkenfrom
worktree-info-hardening

Conversation

@robbertbos

Copy link
Copy Markdown
Member

Volgt #343 (security-audit-follow-up, resterende low/info-hardening) op. Drie defense-in-depth-maatregelen op de boekhouding-backend, plus de client-aanpassingen die nodig zijn om strikte validatie veilig te kunnen aanzetten zonder legitieme saves te breken.

Wat & waarom

F8 — server-side state-validatie (strikt)

PUT /assessments/:id valideert de volledige state tegen schemas/assessment-output.v2.schema.json (ajv, fail-fast) en weigert met 400 bij afwijking. Het schema blijft één bron van waarheid in de repo-root en wordt bij startup gelezen + via de Containerfile in de image gekopieerd.

Omdat strikt valideren een legitieme save zou kunnen weigeren (data-verlies in een overheidsformulier), sturen alle client-paden nu een canonieke state ($schema + metadata.urn):

  • clearSavedState (formulier resetten)
  • handleRestore (volledige versie herstellen)
  • handleFieldRestore (veld-niveau herstellen) — incl. fallback-$schema voor legacy-data van vóór commit c8a08af

Aanbeveling #6data:-URI-allowlist voor afbeeldingen

Een recursieve scan weigert embedded afbeeldingen die niet aan de raster-allowlist voldoen: ^data:image/(webp|png|jpe?g|gif);base64,... (volledig geankerd). Dit blokkeert o.a. data:image/svg+xml — de XSS-vector die de losse ^data:image/-pattern in het schema zou doorlaten. De check draait op zowel save (PUT) als aanmaak (POST), zodat er geen create-bypass is, en heeft een dieptegrens tegen stack-overflow op diep geneste payloads.

Aanbeveling #3 (deel 2) — readOnlyRootFilesystem

De backend draait al non-root (#332) en schrijft niets naar het root-filesystem (logs → stdout, migraties → Postgres, schema read-only). docs/deployment.md beschrijft de ZAD-runtime-config (readOnlyRootFilesystem: true + writable /tmp); de Containerfile is hierop voorbereid. De feitelijke toggle staat in de ZAD Operations Manager (buiten deze repo).

Adversariële review verwerkt

Een 4-agent review (security / NL-standaarden / correctheid / tests) draaide over de diff; de bevindingen zijn verwerkt:

  • DoS (medium): ongebonden recursie in de image-scan → dieptegrens; ajv allErrors → fail-fast (geen event-loop-blokkade / multi-MB logregel).
  • Correctheid (high): create-route omzeilde de image-check → opgelost; handleFieldRestore miste $schema → opgelost.
  • Defense-in-depth (low): image-regex volledig geankerd + base64-charset.
  • Tests: accept-pad assert nu het DB-effect, reject-pad assert dat er niets wordt geschreven; conformance-drift-guard toegevoegd.

Standaarden

  • NeRDS richtlijn 6 (veilige systemen): integriteit (state-validatie), vertrouwelijkheid (anti-XSS), least-privilege (readOnlyRootFilesystem), en beschikbaarheid (client-fixes voorkomen kapotte saves).
  • AVG/dataminimalisatie: validatie-foutlogging bevat enkel schema-paden/-keys, geen antwoordwaarden (empirisch gecontroleerd).

Testen

  • 100% dekking behouden in alle workspaces (backend, frontend, standalone, core).
  • tsc + beide vue-tsc schoon; pnpm audit --prod --audit-level high schoon (ajv/ajv-formats geen kwetsbaarheden).
  • Productie-build geverifieerd onder echte Node ESM (ajv-imports + schema-load uit dist/).

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

Your changes have been deployed to a preview environment:

api: https://pr-347-asses-k2n.rig.prd1.gn2.quattro.rijksapps.nl

frontend: https://pr-347-asses-k2n.rig.prd1.gn2.quattro.rijksapps.nl

This deployment will be automatically cleaned up when the PR is closed.

…lyRootFilesystem (#343)

Volgt de security-audit-follow-up #343 (resterende low/info-hardening) op met
drie defense-in-depth-maatregelen op de boekhouding-backend.

F8 — opaak state-schema: PUT /assessments/:id valideert de volledige state
server-side tegen schemas/assessment-output.v2.schema.json (ajv, fail-fast).
Het schema wordt bij startup gelezen en via de Containerfile in de image
gekopieerd (één bron van waarheid). De client-paden die state opslaan
(clearSavedState en beide versie-herstel-paden) sturen nu een canonieke state
($schema + metadata.urn), zodat strikte validatie geen legitieme save weigert.

Aanbeveling #6 — data:-URI-validatie: een recursieve scan weigert embedded
afbeeldingen die niet aan de raster-allowlist voldoen
(^data:image/(webp|png|jpe?g|gif);base64,...), inclusief data:image/svg+xml
(XSS-vector die de losse ^data:image/ schema-pattern zou doorlaten). De check
draait op zowel de save (PUT) als de aanmaak (POST — geen create-bypass), met
een volledig geankerde regex en een dieptegrens tegen stack-overflow.

Aanbeveling #3 (deel 2) — readOnlyRootFilesystem: de backend draait non-root en
schrijft niets naar het root-filesystem (logs → stdout, migraties → Postgres).
docs/deployment.md beschrijft de ZAD-runtime-config (readOnlyRootFilesystem:
true + expliciete writable /tmp); de Containerfile is hierop voorbereid.

100% testdekking behouden in alle workspaces.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant