Please do NOT open a public issue for security vulnerabilities.
To report a security vulnerability, please use GitHub's private vulnerability reporting.
Alternatively, contact us at: security@pdfnative.dev
We will acknowledge receipt within 48 hours and aim to provide a fix within 7 days for critical issues.
| Version | Supported |
|---|---|
| 1.0.x | ✅ |
| < 1.0 | ❌ |
pdfnative is a pure TypeScript library with zero runtime dependencies. This significantly reduces supply-chain risk.
- AES-128 (V4/R4/AESV2) and AES-256 (V5/R6/AESV3) — pure TypeScript implementations
- Per-object encryption keys with random initialization vectors (AES-CBC)
- No ECB mode — all encryption uses CBC with PKCS7 padding
- Key derivation follows ISO 32000-1 (PDF 1.7) specification
- CMS/PKCS#7 detached signatures (ISO 32000-1 §12.8)
- RSA PKCS#1 v1.5 (SHA-256) and ECDSA P-256 — pure TypeScript, zero external dependencies
- X.509 DER certificate parsing for certificate chain embedding
/ByteRangeensures only the signature/Contentsis excluded from the signed digest
pdfnative ships pure-TypeScript implementations of RSA (PKCS#1 v1.5) and ECDSA (P-256) to uphold the zero-dependency contract and to support restricted runtimes (browsers, Deno, Bun, Web Workers) where native crypto may be unavailable.
RSA signature verification (rsaVerify) is designed to resist Bleichenbacher-style signature forgery attacks: rather than parsing the PKCS#1 v1.5 padding leniently, it reconstructs the full expected EM encoding and compares byte-by-byte using a constant-time XOR accumulator (diff |= em[i] ^ expected[i]). This eliminates the class of "lax parser" forgery attacks.
ECDSA P-256 uses RFC 6979 deterministic k generation (HMAC-DRBG over the private key and message hash), which eliminates nonce-reuse vulnerabilities inherent in CSPRNG-based k selection.
Timing-attack caveat: JavaScript's BigInt arithmetic (used throughout src/crypto/) does not execute in constant time in V8 or SpiderMonkey. This is a fundamental limitation of pure-JS big-integer arithmetic. The practical impact is:
- Low risk in typical usage: signing a PDF once per user action on a server is not meaningfully exploitable.
- Higher risk in high-frequency server-side pipelines: a backend signing thousands of PDFs per second with the same private key under adversarial timing observation could theoretically leak key material over many measurements.
Recommendation for high-security, high-frequency server pipelines: perform signing externally using Node.js native crypto.sign() / crypto.verify() or WebCrypto crypto.subtle.sign() / crypto.subtle.verify(), both of which provide hardware-backed constant-time operations. You can then inject the pre-computed CMS/PKCS#7 blob into the PDF via signPdfBytes(). This avoids the pure-JS BigInt arithmetic path entirely.
buildPDF()andbuildDocumentPDF()validate all inputs at the API boundary- URL validation (
validateURL()) blocksjavascript:,file:, anddata:URI schemes - PDF string escaping prevents injection via
\,(,)characters - CIDFont hex encoding eliminates string injection vectors
- TTF subsetting uses typed arrays with bounds checking
- Row/block count limits prevent resource exhaustion (100K max)
- PDF tokenizer validates all token types before parsing
- Cross-reference parser follows
/Prevchains with loop detection - Stream decompression uses bounded buffers to prevent zip-bomb attacks
- Object parser uses type guards for safe type narrowing (no
anycasts)
- No
eval(),Function(), or dynamic code execution - No external crypto dependencies
- Tree-shakeable (
sideEffects: false) — no module-level side effects - NPM provenance — signed builds via GitHub Actions OIDC
We follow coordinated disclosure. We ask that you:
- Report vulnerabilities privately (see above)
- Allow reasonable time for a fix before public disclosure
- Do not exploit the vulnerability beyond what is necessary to demonstrate it