import puppeteer, { type Browser } from 'puppeteer' /** * HTML → PDF via puppeteer. The browser is a lazy module-level singleton: * launch costs ~1s, so it starts on first render and is reused after. * Guarding with a promise (not the Browser) makes concurrent first calls * share one launch instead of racing two Chromes. */ let browserPromise: Promise | null = null function getBrowser(): Promise { // --no-sandbox + --disable-dev-shm-usage let Chromium launch inside a container // (root user, small /dev/shm); both are inert on a normal dev machine. We only ever // render our own self-contained letterhead HTML (no external fetches), so dropping // the sandbox here is not exposing us to third-party page content. browserPromise ??= puppeteer.launch({ args: ['--no-sandbox', '--disable-dev-shm-usage'] }) return browserPromise } export async function renderPdf(html: string): Promise { const page = await (await getBrowser()).newPage() try { // D24 (red-team): defense-in-depth for the public-share render. Block every network // request at the browser layer — the letterhead is fully self-contained (inline CSS, // data:-URI logo), so any http(s)/file fetch would only come from an injected payload // that slipped past esc(). Combined with the template CSP, an escaping miss becomes a // harmless rendering glitch instead of an SSRF/exfiltration vector. await page.setRequestInterception(true) page.on('request', (req) => { const url = req.url() if (url.startsWith('data:') || url === 'about:blank') void req.continue() else void req.abort() }) await page.setContent(html, { waitUntil: 'load' }) const bytes = await page.pdf({ format: 'A4', printBackground: true }) return Buffer.from(bytes) } finally { await page.close() } } /** For tests/scripts that must let the process exit; the server never calls it. */ export async function closePdfBrowser(): Promise { if (browserPromise === null) return const b = await browserPromise browserPromise = null await b.close() }