GitHub Advisoryadvisory · npm

GHSA-pgx6-7jcq-2qff PDFME has SSRF via Unvalidated URL Fetch in `getB64BasePdf` When `basePdf` Is Attacker-Controlled

MEDIUM Published 2026-03-20 Modified 2026-03-20 Official page ↗
Ecosystem
npm
Packages
@pdfme/common
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N
Weaknesses
CWE-918
Reviewed
2026-03-20

Affected packages

• npm @pdfme/common: < 5.5.10 → fixed in 5.5.10

Description

Summary The `getB64BasePdf` function in `@pdfme/common` fetches arbitrary URLs via `fetch()` without any validation when `basePdf` is a non-data-URI string and `window` is defined. An attacker who can control the `basePdf` field of a template (e.g., through a web application that accepts user-supplied templates) can force the server or client to make requests to arbitrary internal or external endpoints, enabling Server-Side Request Forgery (SSRF) in SSR contexts or blind request forgery in browser contexts. Details The vulnerability exists in `packages/common/src/helper.ts:130-141`. When `getB64BasePdf` receives a string that does not start with `data:application/pdf;`, and `window` is defined, it passes the string directly to `fetch()`: ```typescript // packages/common/src/helper.ts:130-141 export const getB64BasePdf = async ( customPdf: ArrayBuffer | Uint8Array | string, ): Promise => { if ( typeof customPdf === 'string' && !customPdf.startsWith('data:application/pdf;') && typeof window !== 'undefined' ) { const response = await fetch(customPdf); // .attacker.com` to leak information even when responses are not readable. Recommended Fix Add URL validation in `getB64BasePdf` before calling `fetch()`. At minimum, restrict to HTTPS and block private/reserved IP ranges: ```typescript // packages/common/src/helper.ts const BLOCKED_HOSTNAME_PATTERNS = [ /^localhost$/i, /^127\./, /^10\./, /^172\.(1[6-9]|2\d|3[01])\./, /^192\.168\./, /^169\.254\./, /^0\./, /^\[::1\]/, /^\[fc/i, /^\[fd/i, /^\[fe80:/i, ]; function validatePdfUrl(urlString: string): void { let parsed: URL; try { parsed = new URL(urlString); } catch { throw new Error(`Invalid basePdf URL: ${urlString}`); } if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') { throw new Error(`basePdf URL must use http or https protocol, got: ${parsed.protocol}`); } const hostname = parsed.hostname; for (const pattern of BLOCKED_HOSTNAME_PATTERNS) { if (pattern.test(hostname)) { throw new Error(`basePdf URL must not point to private/reserved addresses`); } } } export const getB64BasePdf = async ( customPdf: ArrayBuffer | Uint8Array | string, ): Promise => { if ( typeof customPdf === 'string' && !customPdf.startsWith('data:application/pdf;') && typeof window !== 'undefined' ) { validatePdfUrl(customPdf); // <-- Add validation before fetch const response = await fetch(customPdf); const blob = await response.blob(); return blob2Base64Pdf(blob); } // ... }; ``` Additionally, consider documenting the security implications of passing user-controlled data as `basePdf` and providing an option for applications to supply their own URL validator or allowlist.

Related

Weakness · CWE (1)

  • CWE-918 Server-Side Request Forgery weakness

References