Security Best Practices
This guide provides actionable security recommendations for deploying TCPDF-Next in production environments. Following these practices ensures that your PDF generation pipeline meets enterprise security standards.
Input Validation (HTML Sanitization Before writeHtml)
When generating PDFs from user-supplied HTML, always sanitize the input before passing it to the HTML renderer. TCPDF-Next's HtmlRenderer parses and renders HTML faithfully, which means malicious markup can be exploited if not sanitized.
use YeeeFang\TcpdfNext\Html\HtmlRenderer;
// DANGEROUS: Never pass raw user input directly
// $renderer->writeHtml($userInput);
// SAFE: Sanitize first with a dedicated library
$clean = \HTMLPurifier::getInstance()->purify($userInput);
$renderer->writeHtml($clean);Key rules:
- Strip
<script>,<iframe>,<object>,<embed>, and<link>tags before rendering. - Remove
javascript:anddata:URI schemes fromhrefandsrcattributes. - Limit allowed CSS properties to those needed for layout (no
position: fixed, nourl()in CSS values). - Validate character encoding — ensure input is valid UTF-8 before passing to the renderer.
Certificate Management (Secure Storage and Rotation)
Storage Hierarchy
| Method | Security Level | Use Case |
|---|---|---|
| Hardware Security Module (HSM) | Highest | Enterprise, regulated industries |
| Cloud KMS (AWS KMS, Azure Key Vault, GCP KMS) | High | Cloud-native deployments |
| PKCS#12 file with strong passphrase | Medium | Small deployments |
| PEM file (encrypted) | Medium-Low | Development, testing |
| PEM file (unencrypted) | Lowest | Never in production |
Rotation Policy
- Renew certificates at least 30 days before expiration.
- Monitor expiration with automated alerts at 30, 14, 7, and 1 day thresholds.
- Revoke compromised certificates immediately via the issuing CA.
- Maintain a signed audit log of all certificate lifecycle operations.
use YeeeFang\TcpdfNext\Certificate\CertificateStore;
$store = new CertificateStore();
$store->loadFromDirectory('/etc/tcpdf-next/certs/', '*.pem');
$activeCert = $store->getActiveCertificate('document-signing');
if ($activeCert->getExpirationDate() < new \DateTimeImmutable('+30 days')) {
$logger->warning('Signing certificate expires soon', [
'subject' => $activeCert->getSubject(),
'expires' => $activeCert->getExpirationDate()->format('Y-m-d'),
]);
}DANGER
Never store private keys in source code repositories, unencrypted files on shared filesystems, database columns without encryption at rest, or log files.
Password Handling (SASLprep and Strong Passwords)
When setting PDF encryption passwords, apply Unicode normalization via SASLprep (RFC 4013) to ensure consistent password processing across platforms:
// TCPDF-Next automatically applies SASLprep to passwords
$pdf->setEncryption()
->setAlgorithm(EncryptionAlgorithm::AES256)
->setUserPassword('pässwörd-with-ünïcöde') // SASLprep normalized internally
->setOwnerPassword($strongOwnerPassword)
->apply();Password policy recommendations:
- Minimum 12 characters for user passwords, 20 characters for owner passwords.
- Use a cryptographically secure random generator for owner passwords (
random_bytes()). - Never hardcode passwords in source code — load from environment variables or secret managers.
- Clear passwords from memory after use with
sodium_memzero().
SSRF Prevention (URL Validation for Images, TSA, OCSP)
TCPDF-Next blocks SSRF by default, but you must configure allowlists for legitimate external resources:
use YeeeFang\TcpdfNext\Security\NetworkPolicy;
$networkPolicy = NetworkPolicy::create()
->denyPrivateNetworks() // Block 10.x, 172.16.x, 192.168.x
->denyLoopback() // Block 127.0.0.1
->denyLinkLocal() // Block 169.254.x
->allowDomain('cdn.yourcompany.com') // Images
->allowDomain('timestamp.digicert.com') // TSA
->allowDomain('ocsp.digicert.com') // OCSP
->setMaxRedirects(3)
->setRequestTimeout(10);
$pdf = PdfDocument::create()
->setNetworkPolicy($networkPolicy)
->build();Checklist:
- Validate all URLs before fetching (scheme, host, port).
- Explicitly allowlist TSA and OCSP responder domains.
- Block
file://,gopher://,ftp://, and other non-HTTP(S) schemes. - Log all blocked requests for security monitoring.
File Path Validation (Prevent Path Traversal)
When accepting user-supplied file paths (e.g., for font files, images, or output destinations):
use YeeeFang\TcpdfNext\Security\ResourcePolicy;
$resourcePolicy = ResourcePolicy::strict()
->allowLocalDirectory('/app/public/assets/')
->allowLocalDirectory('/app/storage/fonts/')
->denyAllRemote();
$pdf = PdfDocument::create()
->setResourcePolicy($resourcePolicy)
->build();Rules:
- Never concatenate user input directly into file paths.
- Resolve paths to absolute canonical form and validate against allowed directories.
- Reject paths containing
.., null bytes, or non-printable characters. - Use
ResourcePolicy::strict()in production — it denies all access by default.
Deployment Security (Docker and File Permissions)
Docker Configuration
FROM php:8.5-fpm-alpine
# Run as non-root user
RUN addgroup -S tcpdf && adduser -S tcpdf -G tcpdf
USER tcpdf
# Disable dangerous PHP functions
RUN echo "disable_functions = exec,passthru,shell_exec,system,proc_open,popen" \
>> /usr/local/etc/php/conf.d/security.ini
# Read-only filesystem (mount writable volumes explicitly)
# docker run --read-only --tmpfs /tmp ...File Permissions
# Certificate directory: readable only by the web server user
chown -R www-data:www-data /etc/tcpdf-next/certs/
chmod 700 /etc/tcpdf-next/certs/
chmod 600 /etc/tcpdf-next/certs/*.p12
chmod 600 /etc/tcpdf-next/certs/*.pem
# Output directory: writable only by the web server user
chown -R www-data:www-data /var/lib/tcpdf-next/output/
chmod 700 /var/lib/tcpdf-next/output/
# Temporary directory: writable, not world-readable
chown -R www-data:www-data /tmp/tcpdf-next/
chmod 700 /tmp/tcpdf-next/Content Security Policy for Browser-Displayed PDFs
When serving PDFs inline in the browser, set appropriate HTTP headers to prevent embedding attacks:
return response($pdf->toString(), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="document.pdf"',
'Content-Security-Policy' => "default-src 'none'; plugin-types application/pdf",
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'DENY',
'Cache-Control' => 'no-store, no-cache, must-revalidate',
]);For PDFs containing sensitive data, prefer Content-Disposition: attachment to force download instead of in-browser rendering.
Audit Logging Recommendations
Configure comprehensive audit logging for all security-sensitive PDF operations:
use YeeeFang\TcpdfNext\Security\AuditLogger;
AuditLogger::configure([
'channel' => 'tcpdf-security',
'log_signing' => true,
'log_encryption' => true,
'log_validation' => true,
'log_key_access' => true,
'log_tsa_requests' => true,
'log_resource_access' => true, // Log image/font loading
'log_blocked_requests' => true, // Log SSRF blocks
'redact_sensitive' => true, // Redact passwords/keys from logs
]);Monitor for:
- Failed signature validation attempts (possible document tampering).
- Certificate expiration warnings.
- TSA communication failures.
- Unusual signing volume (possible key compromise).
- Blocked SSRF attempts (possible attack probing).
- Resource loading from unexpected paths.
Principle of Least Privilege for PDF Permissions
When setting PDF document permissions, grant only the minimum access required:
use YeeeFang\TcpdfNext\Encryption\Permissions;
// RESTRICTIVE: Read-only document
$pdf->setEncryption()
->setPermissions(Permissions::ACCESSIBILITY) // Only screen reader access
->setUserPassword('reader')
->setOwnerPassword($strongOwnerPassword)
->apply();
// MODERATE: Printable document
$pdf->setEncryption()
->setPermissions(
Permissions::PRINT_HIGH_QUALITY
| Permissions::ACCESSIBILITY
)
->apply();
// AVOID: Granting all permissions defeats the purpose of encryption
// Permissions::ALL is available but should rarely be usedPermission guidelines:
- Never grant
MODIFY_CONTENTSunless the recipient needs to edit the document. - Always grant
ACCESSIBILITYfor screen reader compatibility (legal requirement in many jurisdictions). - Use
PRINT_HIGH_QUALITYinstead ofPRINT_LOW_QUALITYunless you have a specific reason. - Document the rationale for each granted permission in your code.
Further Reading
- Security Overview — Security architecture and design philosophy
- PAdES B-LTA — Signature levels and implementation
- Advanced Encryption — Algorithm requirements