Tiny Deno HTTP microservice that turns a public URL into a PDF using puppeteer-core and a system Chromium.
- Single endpoint:
GET /pdf?url=... - Query params for paper size, margins, media type, waitUntil lifecycle
- Health check:
GET /healthz(launches Chromium and returnsokif successful) - Minimal permissions / runs in container (Chromium installed via Alpine packages)
All parameters are optional unless noted. Booleans are enabled by explicitly passing true (any other value or absence leaves the default). Unknown / invalid enum values are ignored and the default is used.
| Name | Default | Allowed / Format | Notes |
|---|---|---|---|
| url | about:blank | Any reachable HTTP/HTTPS URL | Target page (must succeed with a HEAD request unless disable-head-check=true) |
| format | letter | letter, legal, tabloid, ledger, a0, a1, a2, a3, a4, a5, a6 | Paper / page format |
| media | print, screen | Emulated CSS media type | |
| wait | networkidle0 | load, domcontentloaded, networkidle0, networkidle2 | Puppeteer lifecycle event to await before PDF |
| margin-top | 0.4in | CSS length (e.g. 0, 10mm, 0.5in) |
Top margin |
| margin-right | 0.4in | CSS length | Right margin |
| margin-bottom | 0.4in | CSS length | Bottom margin |
| margin-left | 0.4in | CSS length | Left margin |
| print-background | false | true | When true, prints CSS backgrounds / colors |
| include-header-footer | false | true | When true, enables (currently empty) header & footer support (placeholder for future templates) |
| font-render-hinting | medium | none, slight, medium, full, max | Chromium font rendering hinting |
| disable-head-check | false | true | Skip initial HEAD reachability check (NOT recommended in production) |
| dumpio | false | true | When true, Chromium stdio is piped through for debugging |
Example with several overrides:
/pdf?url=https://example.com&format=a4&wait=networkidle2&print-background=true&font-render-hinting=full# Run with live reload
deno task dev
# Run once (production style)
deno task start
# Run tests
deno task testThen request a PDF:
GET http://localhost:3000/pdf?url=https://example.com&format=a4&wait=networkidle2Build and run:
docker build -t url2pdf-deno .
docker run -p 3000:3000 url2pdf-denoGET /healthz -> okThis performs a lightweight Chromium launch.
| Name | Description | Default |
|---|---|---|
| PORT | Server listen port | 3000 |
| CHROMIUM_PATH | Override Chromium executable | Auto-detected per OS |
- Uses system Chromium (no bundled download) to keep image small.
--no-sandboxflags are enabled for container compatibility; evaluate removing if you can run with proper user namespaces.- HEAD reachability check prevents long hangs on dead hosts.
This is a Deno application packaged for optional use inside Docker. A primary security concern for any "URL to PDF" style service is Server-Side Request Forgery (SSRF): an attacker could try to render internal network resources (e.g. metadata services, 169.254.169.254, localhost-only admin panels, cluster internal hostnames) if unrestricted outbound networking is allowed.
Recommended hardening steps:
- Restrict network permissions: Instead of the broad
--allow-netused for convenience in local development, supply an allow list of domains / hosts you explicitly trust. Example:
deno run --allow-net=example.com,api.example.com --allow-env --allow-run --allow-read --allow-write --allow-sys server.tsInside Docker you can do the same in the CMD or ENTRYPOINT. This prevents the process from initiating connections to arbitrary hosts even if a crafted url parameter is supplied.
-
Validate / sanitize the
urlparameter at the application layer (currently it only performs a HEAD reachability check). Consider enforcing:- Protocol must be
http:orhttps: - Reject IP literals or private / loopback / link-local ranges (10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, fc00::/7, ::1, etc.)
- Optional length limit
- Protocol must be
-
Treat the
disable-head-checkflag as an internal / debugging tool. In production either remove it or gate it behind authentication / environment toggle to reduce bypass of basic liveness screening. -
Run as a non-root user in the container (adjust Dockerfile) and avoid mounting sensitive host paths. Puppeteer is launched with
--no-sandboxfor portability; if your environment allows, prefer enabling a sandbox (user namespaces / seccomp) for stronger isolation. -
Consider adding basic auth / auth proxy in front of the endpoint if exposure is not intended to be public.
-
Rate limit requests or queue them if you introduce browser reuse to avoid resource exhaustion.
-
Log and monitor rejected
urlvalues to identify probing attempts.
These measures complement Deno's capability-based security model. Start by narrowing --allow-net to an explicit allow list—it provides a strong baseline SSRF mitigation with minimal code changes.
- Add optional basic auth
- Add PDF filename param
- Cache rendered PDFs with ETag
- Support custom page viewport
MIT