Added PDF generation service for management reports as per Spec 378, including Gotenberg integration in docker-compose and configuration updates. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #449
88 lines
5.4 KiB
Markdown
88 lines
5.4 KiB
Markdown
# Spec 378 Gotenberg Security Controls
|
|
|
|
Date: 2026-06-14
|
|
Decision: Gotenberg 8 Chromium is approved with controls as an internal production PDF rendering service for report-style documents.
|
|
|
|
These are required future implementation controls. This artifact does not add runtime config and does not claim that Gotenberg is already deployed in the repo.
|
|
|
|
## Required Runtime Boundary
|
|
|
|
- Gotenberg runs as a separate internal Docker service.
|
|
- The Laravel app and queue containers do not install or execute Node, Puppeteer, Chrome, Chromium, or a browser binary for production PDF rendering.
|
|
- Laravel accesses the service only through a narrow `PdfRenderingGateway` / `PdfRendererClient`.
|
|
- The gateway exposes only server-generated HTML-to-PDF rendering required by report generators.
|
|
- No user-provided URL rendering is allowed in Spec 378 v1.
|
|
- No public port is exposed for the renderer in Sail or Dokploy.
|
|
- The service is reachable only over the internal Docker/Dokploy network.
|
|
|
|
## Required Image And Deployment Controls
|
|
|
|
- Use a pinned Gotenberg 8 Chromium image version or immutable digest; never `latest`.
|
|
- Prefer the Chromium-only Gotenberg 8 image variant for v1 unless office conversion is explicitly approved later.
|
|
- Record the exact image tag/digest in the implementation PR.
|
|
- Add a health check using Gotenberg `/health`.
|
|
- Configure startup/readiness behavior so unavailable renderer state fails generation before artifact exposure.
|
|
- Configure resource limits appropriate for PDF generation workers.
|
|
- Configure request timeout (`API_TIMEOUT`) and Chromium startup timeout (`CHROMIUM_START_TIMEOUT`) explicitly.
|
|
- Configure multipart request size limit (`API_BODY_LIMIT`) explicitly.
|
|
- Configure Chromium queue/concurrency limits (`CHROMIUM_MAX_QUEUE_SIZE`, `CHROMIUM_MAX_CONCURRENCY`) explicitly.
|
|
- Disable unused webhook behavior unless a future spec explicitly approves it.
|
|
- Disable or do not use `downloadFrom` in v1.
|
|
|
|
## Required Request Controls
|
|
|
|
- Send HTML as a server-generated `index.html` multipart payload.
|
|
- Send only local bundled assets alongside the HTML payload.
|
|
- Reference assets by relative filename only.
|
|
- Do not send signed URLs inside HTML.
|
|
- Do not send access tokens, refresh tokens, client secrets, provider credentials, raw provider payloads, SQL errors, stack traces, serialized job context, or internal MSP-only notes inside HTML.
|
|
- Do not render arbitrary operator-provided HTML.
|
|
- Do not render arbitrary user-provided remote URLs.
|
|
- Apply max HTML payload size, max asset size, and max PDF output size in the Laravel gateway before calling Gotenberg.
|
|
- Add a correlation id / OperationRun id to request metadata, using the configured Gotenberg correlation header.
|
|
|
|
## Required Network And File-Access Controls
|
|
|
|
- Keep Gotenberg internal-only with no public listener.
|
|
- No direct user-provided URL rendering in v1.
|
|
- Deny or tightly restrict outbound URL access.
|
|
- For Spec 378, default to no external outbound fetches: use bundled local assets only.
|
|
- Set Chromium outbound restrictions to reject public and private external HTTP destinations unless a later approved asset host allow-list is introduced.
|
|
- Do not enable file access beyond Gotenberg's per-request working directory defaults.
|
|
- Keep Chromium file access from files disabled.
|
|
- Do not mount application storage, secrets, `.env`, or host paths into the Gotenberg container.
|
|
- If custom fonts are required later, use a dedicated reviewed font image or readonly font mount with no secrets.
|
|
|
|
## Required Error And Audit Controls
|
|
|
|
- Renderer unavailable must map to OperationRun failed/blocked with safe reason.
|
|
- Renderer timeout must map to OperationRun failed/blocked with safe reason.
|
|
- Renderer 400/503 responses must map to structured internal error classes.
|
|
- Do not expose raw renderer errors to customers.
|
|
- No ghost report: failed generation must not expose a ready artifact, stale download URL, or partially written PDF.
|
|
- Renderer logs must not contain secrets because request HTML and metadata must be pre-sanitized.
|
|
- Audit generation attempts and successful downloads with safe metadata only.
|
|
- Log correlation id, source review/pack id, operation run id, profile, format, status, and safe renderer outcome.
|
|
|
|
## Required Testing And Validation Controls
|
|
|
|
- Unit coverage for renderer client request building and size-limit rejection.
|
|
- Feature coverage for renderer unavailable, timeout, invalid payload, and successful artifact response mapping.
|
|
- Feature coverage proving no ready artifact is exposed on renderer/storage failure.
|
|
- Feature coverage proving denied generation never calls the renderer.
|
|
- Browser/content smoke only after runtime service and generation flow are implemented.
|
|
- Deployment validation must include health check behavior and queue worker failure handling.
|
|
|
|
## Explicit Non-Approval
|
|
|
|
Gotenberg is approved as shared internal PDF rendering infrastructure for report-style documents.
|
|
|
|
This approval does not approve legal invoice generation, German B2B e-invoicing, XRechnung, ZUGFeRD/Factur-X, GoBD archival, tax calculation, invoice numbering, or billing compliance.
|
|
|
|
## Source Notes
|
|
|
|
- Gotenberg supports Docker and Docker Compose service use, with other services able to reach it on the compose network.
|
|
- Gotenberg exposes a Chromium HTML-to-PDF route accepting `index.html` plus optional assets.
|
|
- Gotenberg exposes `/health` for liveness/monitoring.
|
|
- Gotenberg has timeout, body limit, correlation id, Chromium concurrency, queue, file access, and outbound URL filtering settings that must be set deliberately.
|