Skip to content

Tiny Deno microservice that renders any reachable URL to PDF via puppeteer-core + system Chromium. One /pdf endpoint, rich query params, health check, container-ready.

License

Notifications You must be signed in to change notification settings

mkwired/url2pdf-deno

Repository files navigation

url2pdf-deno

Tiny Deno HTTP microservice that turns a public URL into a PDF using puppeteer-core and a system Chromium.

Features

  • Single endpoint: GET /pdf?url=...
  • Query params for paper size, margins, media type, waitUntil lifecycle
  • Health check: GET /healthz (launches Chromium and returns ok if successful)
  • Minimal permissions / runs in container (Chromium installed via Alpine packages)

Query Parameters

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 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

Local Development

# Run with live reload
 deno task dev

# Run once (production style)
 deno task start

# Run tests
 deno task test

Then request a PDF:

GET http://localhost:3000/pdf?url=https://example.com&format=a4&wait=networkidle2

Docker

Build and run:

 docker build -t url2pdf-deno .
 docker run -p 3000:3000 url2pdf-deno

Health Check

GET /healthz -> ok

This performs a lightweight Chromium launch.

Environment Variables

Name Description Default
PORT Server listen port 3000
CHROMIUM_PATH Override Chromium executable Auto-detected per OS

Production Notes

  • Uses system Chromium (no bundled download) to keep image small.
  • --no-sandbox flags are enabled for container compatibility; evaluate removing if you can run with proper user namespaces.
  • HEAD reachability check prevents long hangs on dead hosts.

Security

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:

  1. Restrict network permissions: Instead of the broad --allow-net used 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.ts

Inside 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.

  1. Validate / sanitize the url parameter at the application layer (currently it only performs a HEAD reachability check). Consider enforcing:

    • Protocol must be http: or https:
    • 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
  2. Treat the disable-head-check flag as an internal / debugging tool. In production either remove it or gate it behind authentication / environment toggle to reduce bypass of basic liveness screening.

  3. Run as a non-root user in the container (adjust Dockerfile) and avoid mounting sensitive host paths. Puppeteer is launched with --no-sandbox for portability; if your environment allows, prefer enabling a sandbox (user namespaces / seccomp) for stronger isolation.

  4. Consider adding basic auth / auth proxy in front of the endpoint if exposure is not intended to be public.

  5. Rate limit requests or queue them if you introduce browser reuse to avoid resource exhaustion.

  6. Log and monitor rejected url values 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.

Future Ideas

  • Add optional basic auth
  • Add PDF filename param
  • Cache rendered PDFs with ETag
  • Support custom page viewport

License

MIT

About

Tiny Deno microservice that renders any reachable URL to PDF via puppeteer-core + system Chromium. One /pdf endpoint, rich query params, health check, container-ready.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published