Security and operations for the NextPDF Laravel package
At a glance
Section titled “At a glance”The package sets fixed Portable Document Format (PDF) response headers, sanitizes download filenames, validates queue output paths on the worker, and routes Hypertext Transfer Protocol (HTTP) calls to the timestamp authority through a request-forgery-aware client. This page explains the threat model and the deployment configuration each control requires.
Install
Section titled “Install”composer require nextpdf/laravelphp artisan vendor:publish --tag=nextpdf-configConceptual overview
Section titled “Conceptual overview”The package adapts a PDF engine for a web framework. The HTTP request and queue transport define the trust boundary. These controls cover response handling, deserialized job payloads, and outbound HTTP to a timestamp authority.
API surface — threat model
Section titled “API surface — threat model”| Asset | Threat | Control in this package | Deployment configuration required |
|---|---|---|---|
| PDF HTTP response | Content-type sniffing, clickjacking, indexing | Fixed header set on every PdfResponse factory | None; headers are not configurable |
| Download filename | Header injection, path traversal in Content-Disposition | Filename sanitizer strips separators, control characters, null bytes | None; sanitizer always runs |
| Queue job output path | Arbitrary file write via tampered serialized payload | Path validated in handle() on the worker | Route output to a controlled storage path |
| Outbound timestamp authority (TSA) HTTP | Server-side request forgery, plaintext tampering | Request-forgery-aware HTTP client; HTTPS enforced unless explicitly relaxed | Keep tsa.allow_insecure_http = false; pin Subject Public Key Info (SPKI) |
| Shared worker state | Cross-request state leakage in long-lived workers | Locked font registry; bounded image cache; factory-bound document | Set preload_fonts; bound memory at the container |
Response hardening
Section titled “Response hardening”Every PdfResponse factory sets a fixed header set:
Cache-Control: private, max-age=0, must-revalidatePragma: publicX-Content-Type-Options: nosniffX-Frame-Options: DENYContent-Security-Policy: default-src 'none'X-Robots-Tag: noindex, nofollowReferrer-Policy: no-referrer
These values are constants in PdfResponse. They are not configurable.
The package test suite checks each header on every factory method,
including streamed variants.
The download filename passes through a sanitizer before it reaches the
Content-Disposition header. The sanitizer removes path separators,
control characters, and null bytes, and emits a Request for Comments
(RFC) 5987 filename*= parameter for non-ASCII names. An empty filename
becomes document.pdf.
Queue payload validation
Section titled “Queue payload validation”GeneratePdfJob serializes a closure onto the queue transport. The
worker validates the output path inside handle(), not at dispatch. The
validation rejects:
- null bytes in the path,
- stream-wrapper schemes (for example
php://), ..path-traversal segments,- any path that does not end in
.pdf(case-insensitive).
Each rejection raises InvalidArgumentException. Validation runs when
the worker consumes the job. A serialized payload on a Redis or database
transport could be changed before the worker reads it. Route the output
path to a controlled storage directory; do not derive it from unvalidated
request input.
Outbound HTTP to a timestamp authority
Section titled “Outbound HTTP to a timestamp authority”When a timestamp authority is configured, the package binds a PHP
Standard Recommendation (PSR)-18
Psr\Http\Client\ClientInterface. A PSR-18 client sends a PSR-7 request
and returns a PSR-7 response (PSR-18 §2).
The bound client wraps a curl-based client with a request-forgery-aware
layer. It enforces HTTPS unless tsa.allow_insecure_http is explicitly
true.
The timestamp authority is a Premium-tier capability. The Core package
documented here binds the HTTP client and timestamp client wiring;
signing itself requires nextpdf/premium. This page does not document
PDF Advanced Electronic Signatures (PAdES) baseline behavior beyond B-B;
higher baselines are out of scope.
Operational guidance for the timestamp authority:
- Keep
tsa.allow_insecure_httpset tofalsein production. - Set
tsa.pinned_public_keysto the base64 SHA-256 SPKI hashes of the timestamp authority certificate (RFC 7469 form). - Keep
tsa.warn_on_key_rotationset totrueso a changed SPKI is logged before the pinned certificate expires. - Source
tsa.urlfrom trusted configuration only. If an operator can set it from an admin surface, apply an egress firewall or DNS policy to reduce request-forgery exposure.
Logging
Section titled “Logging”Use Psr\Log\LoggerInterface for diagnostics. Pass structured context,
not interpolated strings. PSR-3 leaves placeholder escaping to the logger
implementation and instructs callers not to pre-escape context values
(PSR-3 §1.2).
Log the exception class, not the message or trace, to reduce internal
detail in logs.
Code sample — Production
Section titled “Code sample — Production”<?php
declare(strict_types=1);
// .env — production timestamp-authority hardening// NEXTPDF_TSA_URL=https://tsa.example.test// NEXTPDF_TSA_ALLOW_INSECURE_HTTP=false// NEXTPDF_TSA_WARN_ROTATION=true
return [ 'tsa' => [ 'url' => env('NEXTPDF_TSA_URL'), 'allow_insecure_http' => env('NEXTPDF_TSA_ALLOW_INSECURE_HTTP', false), 'warn_on_key_rotation' => env('NEXTPDF_TSA_WARN_ROTATION', true), 'pinned_public_keys' => [ // base64 SHA-256 SPKI hashes of the TSA certificate ], ],];Edge cases & gotchas
Section titled “Edge cases & gotchas”- The response header set is fixed. Applications that need a different Content Security Policy (CSP) must post-process the response after the factory returns it.
- Path validation runs on the worker. A bad path passes
dispatch()and fails only when the job executes. tsa.allow_insecure_http = trueremoves the HTTPS enforcement and weakens timestamp trust. Restrict it to local development.- The font registry is locked after warmup; the package rejects runtime attempts to register a font in a long-lived worker by design.
Performance
Section titled “Performance”The security controls use constant-time string and array operations and add no measurable per-request cost. Font parsing on first use is the dominant operational cost; preload fonts at worker boot to avoid first-request latency.
Security notes
Section titled “Security notes”This page is the threat-model reference for the package. Source code enforces these controls, and the test suite asserts them. The threat-model table and timestamp-authority steps call out the deployment configuration the operator must supply.
Conformance
Section titled “Conformance”| Claim | Source | Clause | reference_id |
|---|---|---|---|
| PSR-18 client sends PSR-7 request, returns PSR-7 response | PSR-18 HTTP Client | §2 | |
| Caller passes unescaped structured log context | PSR-3 Logger | §1.2 |
RFC 7469 SPKI pinning names the form used by the
tsa.pinned_public_keys configuration key. The package consumes
operator-supplied pin values and does not implement the RFC itself.
Commercial context
Section titled “Commercial context”PAdES B-B signing and timestamp-authority integration require nextpdf/premium. This optional Enterprise capability needs no code change in the Core package documented here. See https://nextpdf.dev/get-license/?intent=laravel-signing.
See also
Section titled “See also”- /integrations/laravel/configuration/ — every TSA, signature, and queue key
- /integrations/laravel/production-usage/ — dependency injection (DI) and error-handling patterns
- /integrations/laravel/troubleshooting/ — why the path checks reject input
- /integrations/laravel/boot-and-discovery/ — binding lifetimes in long-lived workers