Join our Newsletter — 33% off our NHI Course
Home› Guides› Server Side PDF Generation Security Guide: Stopping SSRF…
Guide Application Security

Server Side PDF Generation Security Guide: Stopping SSRF and File Read in HTML to PDF Converters

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 14 November 2026 14 min read
On this page

A server that turns HTML into PDF is a server-side browser, and when user-controlled content reaches it, that browser can be made to fetch internal URLs, read local files and embed the results in a document the attacker downloads. Invoices, reports, certificates, tickets and exported dashboards are often rendered this way, usually by a component added late and reviewed as "just a library". This guide is for application security engineers, developers and platform teams who run HTML to PDF conversion. It covers how the risk arises, the settings each common engine exposes, isolation and egress design, testing, detection and a phased rollout. It goes deeper on renderers than the SSRF Prevention Guide, and it is about generating PDFs, not opening malicious ones, which the PDF Security Guide covers. Engines are listed alphabetically; order is not a ranking.

Key takeaways

  • The renderer is a fetcher. Every image, stylesheet, font, iframe, SVG reference and script in the HTML is a request made from your server, with your server's network position.
  • Template data is input. A customer name, address or note inserted unescaped into the HTML template is enough to inject markup that the renderer will act on.
  • Control resource loading in the engine. Each engine has its own URL fetcher, allow and deny lists or request interception; set them so only the resources you intend can load.
  • Isolate and constrain egress. Run the renderer as an unprivileged, sandboxed, resource-limited service with network access only to the hosts it needs, never to metadata endpoints or internal admin services.
  • Treat the output as sensitive. A PDF can carry leaked file contents, attachments and system details, so check what the generator embeds before the file leaves the service.

How PDF generation becomes SSRF and file read

HTML to PDF engines either drive a real browser engine (Chromium through Puppeteer, Playwright or a service such as Gotenberg; the QtWebKit engine inside wkhtmltopdf) or implement their own HTML and CSS layout (dompdf, WeasyPrint). Either way, the engine resolves references while it lays out the page. If an attacker can place markup in the HTML, the engine makes requests on their behalf. That is server-side request forgery (CAPEC-664) delivered through a document.

Typical injection points are fields inserted into a template without output encoding, rich text editors whose HTML is rendered as is, "export this page as PDF" features that render a user-supplied URL, and Markdown or CSS customisation features. The impact depends on what the renderer can reach and what it does with the response.

OutcomeHow it happensWhat the attacker gains
Internal request (blind SSRF)An injected image or stylesheet reference points to an internal hostPort and service discovery, requests to unauthenticated internal APIs, side effects on internal services
Response embedded in the PDFAn iframe, object or script fetches a URL and the rendered page includes the contentInternal page contents, including cloud metadata responses, visible in the downloaded document
Local file readA file:// reference resolves on the serverConfiguration files, keys and environment details embedded in the PDF
Attachment of filesEngines that support PDF attachments embed a referenced fileWhole files carried inside the PDF, not just rendered text
Denial of serviceEndless rendering, huge CSS values, hanging network requests or special filesExhausted CPU and memory on the rendering service
Script execution in the rendererEngines with JavaScript enabled run injected script in the pageProgrammable requests, reading responses and writing them into the page

WeasyPrint's own security documentation lists most of these: long and endless renderings, network requests that hang, huge CSS values, local file access through file:// URIs (including special files that cause infinite rendering), attachment of any reachable file, leakage of installed fonts, network configuration and library versions, and the same risks through SVG images. The same categories apply to every engine.

Why cloud credentials make this urgent

On cloud hosts the most valuable internal URL is the instance metadata service. The OWASP SSRF Prevention Cheat Sheet lists 169.254.169.254 (used by AWS, Azure and Google Cloud), metadata.google.internal and the AWS IPv6 address fd00:ec2::254 as addresses to block, along with link-local ranges that cover container credential endpoints. A renderer that can reach these and embed the response hands out the workload's role credentials (ATT&CK T1552.005 Cloud Instance Metadata API). The Capital One Breach 2019 shows the pattern: an SSRF reached the metadata service and returned temporary role credentials that could read far more data than the component needed.

On AWS, IMDSv2 requires a session token obtained with a PUT request, rejects PUT requests carrying an X-Forwarded-For header and by default limits the token response to a hop limit of 1. An injected HTML reference makes GET requests, so requiring IMDSv2 (HttpTokens set to required) removes the simplest route. AWS notes that containers may need a hop limit of 2, which weakens the protection slightly, so keep the smallest value that works. See the Cloud Instance Metadata Services Compared Guide for the equivalent settings on each cloud.

Engine settings that control resource loading

Each engine exposes its own controls. The table lists the settings and guidance each project documents; confirm them against the current documentation for your version.

EngineResource loading controls (from project docs)Other documented guidance
dompdfRemote resources load only when the isRemoteEnabled option is true; local files must sit inside the paths set in the chroot optionEmbedded PHP is disabled by default and should stay off; never let users change Options values; keep dompdf outside the web root; validate external resource references when users supply HTML
Gotenberg (Chromium and LibreOffice)--chromium-deny-list defaults to blocking file:// URLs outside /tmp; --chromium-allow-list; --chromium-deny-private-ips (default false); --chromium-disable-javascript; equivalent allow, deny and private IP flags for webhooks, downloadFrom and LibreOffice--api-timeout defaults to 30s; basic or OIDC authentication can be enabled; LibreOffice macros are off by default; keep --chromium-disable-web-security off
Puppeteer (Chromium)page.setRequestInterception(true) with a request handler that calls abort, continue or respond for every requestRun Chrome with its sandbox; the project strongly discourages --no-sandbox and allows it only for content you absolutely trust; its Docker example adds an unprivileged user so the sandbox works
WeasyPrintA custom URL fetcher that blocks file:// or filters by protocol and path; URLFetcher timeout settingRun as a non-root user with limited file system, network and memory access, ideally in a container; limit rendering time and memory per process
wkhtmltopdfProcess-level controls: sanitise HTML before rendering and confine the process with a mandatory access control system such as AppArmor or SELinuxThe maintainer's status page says not to use it with any untrusted HTML, and suggests WeasyPrint or Prince for HTML you control and Puppeteer for JavaScript-heavy pages

Availability: Gotenberg flag names and defaults are from its configuration documentation as of November 2026. Puppeteer interception is a page-level API, so apply it to every page the service opens. WeasyPrint's URL fetcher is also used for SVG images, so a custom fetcher covers those too. Settings move between versions; record the version you validated.

Whatever the engine, the pattern is the same: deny by default, allow the specific scheme and host that serve your own assets (ideally a local asset directory or one internal static host), and refuse file:// except a dedicated, empty-by-default template directory.

Designing a safe rendering service

  • Separate service: run rendering as its own service or container, not inside the main application process, so a compromise of the renderer does not reach application secrets or the database connection.
  • No credentials in reach: give the renderer no cloud role, or a role with no permissions, and no mounted secrets. A renderer needs fonts, templates and the HTML, nothing else. Where the platform injects credentials by default, turn that off for this workload.
  • Egress allow-list: at the network layer, allow only the asset host and nothing else. Block metadata addresses and private ranges at the host or network policy even if the engine also blocks them. See Cloud Egress Filtering.
  • Self-contained input: embed images and fonts as data URIs or local files resolved by your code before rendering, and turn off remote loading in the engine. This removes most of the fetch surface.
  • Encode template data: insert user data into templates with HTML output encoding; where rich text is required, sanitise it with an allow-list sanitiser first. See the HTML Sanitisation Guide.
  • JavaScript off unless needed: most document templates need no script. Disable it in the engine where the option exists, or add a Content Security Policy in the template that blocks script and restricts img-src and style-src.
  • URL rendering features: if users can ask the service to render a URL, apply the full SSRF controls to that URL: allow-listed hosts, resolved IP checks, no redirects to other hosts.
  • Resource limits: cap rendering time, memory, page count and output size per request, and kill processes that exceed them.
  • Authentication on the service: rendering APIs such as Gotenberg should not be reachable without authentication or from outside the application network.

Decision guidance

SituationApproachTrade-off
Fixed templates, simple layout, data fields onlyLayout engine with remote loading off and local assets, or a non-HTML PDF library that draws text directlyLeast attack surface; less layout flexibility
Complex layout needing modern CSS or JavaScript chartsHeadless Chromium in a sandboxed, unprivileged container with request interception or engine allow-lists and an egress allow-listLarger runtime to patch and harden; strongest rendering fidelity
Users can supply rich HTML or MarkdownSanitise with an allow-list, remove external references, then render with remote loading offSome formatting is lost; sanitiser needs maintenance
Users can supply a URL to renderDedicated renderer with no internal network access at all, plus SSRF validation on the URLSeparate network segment and operations cost
Small team, low volumeOne hardened container with remote loading off, JavaScript off and a strict egress policySimple to run; review on each engine upgrade
Large platform, many teamsShared internal rendering service with a fixed security configuration, authentication and central loggingTeams lose per-service tuning; one place to fix and monitor

Engines that are no longer actively developed carry more risk with untrusted input because browser engine flaws go unfixed. Where an engine's own maintainers advise against untrusted HTML, plan a migration, and until then render only fully trusted, server-built HTML.

Implementation roadmap

  1. Find every renderer: search code and dependency manifests for PDF and headless browser libraries, container images that bundle them and internal rendering APIs. Forgotten renderers in reporting and export features are common.
  2. Map the data flow: for each, record which fields reach the HTML, whether users can supply HTML or URLs, and what the process can reach on the network and file system.
  3. Quick wins first: require IMDSv2 or the equivalent on renderer hosts, block metadata and private ranges in network policy, and turn off remote loading where the templates do not need it.
  4. Fix encoding: add output encoding to template fields and sanitise any rich text.
  5. Isolate: move renderers into separate, unprivileged, sandboxed containers with no credentials and resource limits.
  6. Test in a pilot: deploy the restricted configuration to one document type, compare output against the previous version and watch blocked request logs for assets you missed.
  7. Roll out and roll back safely: keep the previous configuration deployable behind a flag; if documents break, add the missing asset to the allow-list rather than reopening network access.

Testing

Include rendering features in application security testing and penetration test scope explicitly, because generic scanners rarely reach them. Assessors should confirm, using harmless markers, whether template fields accept markup, whether the renderer contacts an external collaborator host, whether file:// and private address references are refused, and whether script runs. Add regression tests that render a template containing an external reference and a file reference and assert that the output contains neither. Check generated PDFs for unexpected attachments and embedded content. The File Upload Security Guide covers the related case where users upload HTML or documents for conversion.

Detection and measurement

What to log and watch:

  • Renderer egress: every outbound connection from rendering hosts, with destination; alert on anything outside the allow-list, especially metadata addresses and private ranges.
  • Blocked requests: engine-level aborts from interception handlers, URL fetchers or deny lists, logged with the document type and request ID.
  • Metadata access: cloud audit events showing use of the renderer's credentials, which should be none.
  • Resource exhaustion: timeouts, killed processes and oversized outputs, which can indicate probing.
  • Template input: markup characters arriving in fields that should be plain text.

Metrics that show the programme is working:

  • Percentage of renderers running isolated with no credentials and an egress allow-list (target 100%).
  • Percentage of renderers with remote resource loading disabled or allow-listed.
  • Count of renderer egress connections to non-allow-listed destinations per month (target zero after tuning).
  • Rendering features covered by regression tests for external and file references.
  • Median age of the rendering engine version against the latest release.

Framework mapping

FrameworkReferenceHow it applies
OWASP ASVS 5.0V1.3.6Protect against SSRF by validating untrusted data against an allow-list of protocols, domains, paths and ports
CAPECCAPEC-664 Server Side Request ForgeryThe attack pattern renderers are exposed to
MITRE ATT&CK v20T1190 Exploit Public-Facing Application; T1552.005 Cloud Instance Metadata APIEntry through the rendering feature; credential theft from metadata
NIST SP 800-53 Rev 5SI-10 Information Input Validation; SC-7 Boundary Protection; SC-39 Process Isolation; CM-7 Least Functionality; SI-4 System MonitoringEncoding and sanitising input; egress control; isolated renderer; disable remote loading and script; monitor egress
NIST CSF 2.0PR.PS-04, DE.CM-09Logging from the rendering service and monitoring of its runtime
CIS Controls v8.1Control 16 Application Software Security; Control 13 Network Monitoring and DefenseSecure development of rendering features; egress monitoring
ISO/IEC 27001:2022 Annex AA.8.28 Secure coding; A.8.22 Segregation of networks; A.8.16 Monitoring activitiesEncoding and testing; isolated network segment; renderer monitoring

Where identity fits

The renderer's workload identity is the prize in most SSRF chains. Give rendering services their own identity with no permissions, never reuse the application's role, and alert on any use of it. The EC2 Instance Metadata Service Hardening Guide covers the AWS side in depth.

Common failure patterns

  • Trusting the template: teams secure the URL feature but forget that a customer name or address inserted into an invoice template is also HTML input.
  • Renderer inside the main app: the PDF library runs in the application process with its role, secrets and database access, so file read or SSRF reaches everything.
  • Deny-lists only: blocking 169.254.169.254 as text while allowing redirects, alternative encodings or IPv6 forms; OWASP warns deny-lists are bypass-prone and redirects should be disabled.
  • Sandbox switched off: Chromium started with --no-sandbox to make a container work, against the Puppeteer project's guidance, removing a key layer for untrusted content.
  • Unauthenticated rendering API: an internal conversion service reachable by anything on the network, which turns it into a general-purpose fetch proxy.
  • Stale engine: an unmaintained engine kept for layout compatibility and fed customer-supplied HTML.

Practitioner checklist

  • Inventory every HTML to PDF renderer, including those inside reporting and export features.
  • Encode all template fields and sanitise any rich text before rendering.
  • Disable remote resource loading or set an engine allow-list; refuse file:// outside a template directory.
  • Disable JavaScript where templates do not need it.
  • Run renderers as unprivileged, sandboxed, resource-limited services with no credentials.
  • Enforce an egress allow-list and block metadata addresses and private ranges at the network layer.
  • Require IMDSv2 or the equivalent metadata protection on renderer hosts.
  • Authenticate access to any internal rendering API.
  • Add regression tests for external and file references and include rendering in pen test scope.
  • Log and alert on renderer egress outside the allow-list.

Standards and references

Related NHI Mgmt Group resources: SSRF Prevention Guide · PDF Security Guide · HTML Sanitisation Guide · Cloud Instance Metadata Services Compared Guide · Container Security Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 14 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org