Skip to content

Security and operations for the NextPDF Laravel package

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.

Terminal window
composer require nextpdf/laravel
php artisan vendor:publish --tag=nextpdf-config

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.

AssetThreatControl in this packageDeployment configuration required
PDF HTTP responseContent-type sniffing, clickjacking, indexingFixed header set on every PdfResponse factoryNone; headers are not configurable
Download filenameHeader injection, path traversal in Content-DispositionFilename sanitizer strips separators, control characters, null bytesNone; sanitizer always runs
Queue job output pathArbitrary file write via tampered serialized payloadPath validated in handle() on the workerRoute output to a controlled storage path
Outbound timestamp authority (TSA) HTTPServer-side request forgery, plaintext tamperingRequest-forgery-aware HTTP client; HTTPS enforced unless explicitly relaxedKeep tsa.allow_insecure_http = false; pin Subject Public Key Info (SPKI)
Shared worker stateCross-request state leakage in long-lived workersLocked font registry; bounded image cache; factory-bound documentSet preload_fonts; bound memory at the container

Every PdfResponse factory sets a fixed header set:

  • Cache-Control: private, max-age=0, must-revalidate
  • Pragma: public
  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Content-Security-Policy: default-src 'none'
  • X-Robots-Tag: noindex, nofollow
  • Referrer-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.

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.

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:

  1. Keep tsa.allow_insecure_http set to false in production.
  2. Set tsa.pinned_public_keys to the base64 SHA-256 SPKI hashes of the timestamp authority certificate (RFC 7469 form).
  3. Keep tsa.warn_on_key_rotation set to true so a changed SPKI is logged before the pinned certificate expires.
  4. Source tsa.url from 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.

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.

resource: config/nextpdf.php (tsa hardening) + src/Laravel/NextPdfServiceProvider.php
<?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
],
],
];
  • 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 = true removes 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.

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.

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.

ClaimSourceClausereference_id
PSR-18 client sends PSR-7 request, returns PSR-7 responsePSR-18 HTTP Client§2
Caller passes unescaped structured log contextPSR-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.

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.

  • /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