Hardening checklist
Work through this before exposing PDF Server. Items are grouped by what they defend against; see Proved issues for the underlying findings.
Network & access
- Put an authenticating reverse proxy in front of the service (no route has built-in auth).
- Review the in-app request body-size limit (
--server-max-body-size, default 1 MiB) and tighten at the proxy if needed. - Review the in-app read-header/read/idle timeouts (
--server-*-timeout) and add write/total-request limits at the proxy. - Add rate limiting at the proxy.
- Do not expose the port directly to untrusted networks.
Egress (defends SSRF)
-
Apply an egress network policy that blocks:
- link-local
169.254.0.0/16(cloud metadata169.254.169.254), - private ranges
10/8,172.16/12,192.168/16, - anything the container does not legitimately need.
This is the control for the browser's own fetches (injected JS can load arbitrary URLs); the in-app guard below does not cover them.
- link-local
-
Remote-template fetches are guarded in-app by fixed secure defaults: a fetch timeout, a response-size cap, and a block on loopback/private/ link-local targets that cannot be disabled process-wide. This is defense in depth — still build their requests only from trusted data.
Browser sandbox (compensates for --no-sandbox)
- Run the container with a restrictive seccomp profile.
- Prefer user-namespace isolation or a stronger sandbox (gVisor).
- Run as non-root (the image already does).
- Set CPU and memory limits.
Template & data hygiene (defends XSS-into-PDF)
- Never pass untrusted data to raw-HTML sinks:
{{{triple}}}, themarkdownhelper, orembed_text. - Sanitize untrusted Markdown before rendering it.
- Validate untrusted data with a
schema.json; for compositions, validate at the composition boundary (child schemas are not re-validated). - Avoid placing untrusted data directly into
src/href/URL positions; validate/allowlist if you must.
Availability
- Raise
RENDER_POOL_MAX_TOTALabove the default of 1 for real load. - Bound total per-request time at the proxy.
- Monitor
/_metadatafor liveness (cheap) and/_statusfor pool health.
Data exposure
- Treat document/asset contents on the internal render server as visible to
the rendering page; do not ship secrets in
assets/.