GitHub Advisoryadvisory · npm
GHSA-pgx6-7jcq-2qff PDFME has SSRF via Unvalidated URL Fetch in `getB64BasePdf` When `basePdf` Is Attacker-Controlled
MEDIUM
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