Contracts / Security Policy
At a glance
Section titled “At a glance”The security-policy domain defines three deny-by-default contracts: CryptoPolicyInterface gates algorithm and key choices, HtmlSecurityPolicyInterface constrains the Hypertext Markup Language (HTML) feature surface, and ExternalResourcePolicyInterface governs remote resource loading. Because each one is a contract, you can supply a stricter deployment policy without forking.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”CryptoPolicyInterface is the crypto gate. Core calls it before any signing, encryption, or hashing step. The check covers the hash, the signature object identifier (OID), the cipher, and the key strength. The contract also reports the minimum hash and a policy name for the audit log. Use it to apply a rule set such as Federal Information Processing Standards (FIPS) 140-3 or eIDAS. The signing and encryption code does not change. When no policy is set, every algorithm is allowed. A regulated site must set an explicit policy.
HtmlSecurityPolicyInterface works at the HTML parse layer. It runs before content reaches any renderer. It decides whether a tag, an attribute, a Cascading Style Sheets (CSS) property, or a uniform resource locator (URL) scheme is allowed. It also caps input size and nesting depth. It works with the per-renderer transport policies (Chrome, Cloudflare, Gotenberg), which set size limits and Content Security Policy (CSP) headers. The HTML policy reduces the parse-layer attack surface. A stripped tag never reaches layout. An injected element cannot change the output. When no policy is set, the default allows the full feature set.
ExternalResourcePolicyInterface decides whether the HTML pipeline may fetch an external font, stylesheet, or image. It also sets the limits for each fetch. Its default stance is deny-all. Every option is off until you turn it on. The contract follows least privilege. Untrusted HTML may point to an attacker-controlled URL. It gates @font-face fetches by scheme, size, and glyph count. It gates @import by scheme, depth, and total size. It gates background-image by a scheme list and an exact-match domain allowlist. It caps data-URI (Uniform Resource Identifier) size. It also gates Scalable Vector Graphics (SVG) external references. The contract states that production must always deny them. They enable request forgery and script injection. Open URL fetching is a server-side request forgery path. Access control is bypassed by changing the URL, as described by the Open Worldwide Application Security Project (OWASP) Top 10 2025. External component sourcing must be limited to official origins and secure transport.
API surface
Section titled “API surface”| Type | Kind | Key members | Stability | Since |
|---|---|---|---|---|
CryptoPolicyInterface | interface | isHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName() | stable | 1.9.0 |
HtmlSecurityPolicyInterface | interface | isTagAllowed(), isAttributeAllowed(), isCssPropertyAllowed(), isUrlSchemeAllowed(), getMaxInputSize(), getMaxNestingDepth(), getName() | stable | 3.1.0 |
ExternalResourcePolicyInterface | interface | isFontFaceAllowed(), getAllowedFontSchemes(), getMaxFontFileSize(), getMaxFontGlyphs(), isImportAllowed(), getMaxImportDepth(), isBackgroundImageAllowed(), getAllowedImageDomains(), getMaxDataUrlSize(), isSvgExternalReferenceAllowed() | stable | 4.0.0 |
ExternalResourcePolicyInterface returns typed limits: positive-int sizes, int<1, 100> import depth, and list<non-empty-string> scheme and domain lists. The default implementation denies every capability.
Code sample — Quick start
Section titled “Code sample — Quick start”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\HtmlSecurityPolicyInterface;
/** * Decide whether a tag survives the policy. * * @param HtmlSecurityPolicyInterface $policy A core or custom policy. */function tagSurvives(HtmlSecurityPolicyInterface $policy, string $tag): bool{ return $policy->isTagAllowed($tag);}The function depends only on the contract. Both a restrictive policy and the default policy satisfy it.
Code sample — Production
Section titled “Code sample — Production”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\CryptoPolicyInterface;use NextPDF\Contracts\ExternalResourcePolicyInterface;use NextPDF\Contracts\HtmlSecurityPolicyInterface;use Psr\Log\LoggerInterface;
final readonly class UntrustedHtmlGate{ public function __construct( private HtmlSecurityPolicyInterface $htmlPolicy, private ExternalResourcePolicyInterface $resourcePolicy, private CryptoPolicyInterface $cryptoPolicy, private LoggerInterface $logger, ) {}
/** * Reject input that exceeds the configured limits before rendering. * * @param string $html Untrusted HTML markup. */ public function assertAcceptable(string $html): void { $maxInput = $this->htmlPolicy->getMaxInputSize();
if ($maxInput > 0 && \strlen($html) > $maxInput) { $this->logger->warning('HTML rejected: input over limit', [ 'policy' => $this->htmlPolicy->getName(), 'limit' => $maxInput, ]);
throw new \LengthException('HTML input exceeds policy limit.'); }
if ($this->resourcePolicy->isSvgExternalReferenceAllowed()) { $this->logger->error('Unsafe policy: SVG external references enabled.');
throw new \LogicException('SVG external references must be denied in production.'); } }}The gate enforces the input cap and rejects an unsafe resource policy before the pipeline runs. It logs the policy name for audit and throws a specific exception.
Edge cases & gotchas
Section titled “Edge cases & gotchas”CryptoPolicyInterfaceallows every algorithm when no policy is set. The open default is a development convenience, not a production posture. Set an explicit policy in any regulated deployment.HtmlSecurityPolicyInterface::getMaxInputSize()returns0for unlimited. Treat0as “no policy limit”, not “reject all”. Apply a transport-layer cap as well.ExternalResourcePolicyInterfaceis deny-all by default. Enabling@font-faceorbackground-imagewithout setting a scheme list opens a request-forgery surface; set the allowlist at the same time.- An empty domain allowlist on
getAllowedImageDomains()means all domains are permitted once background images are enabled. An empty list is not a deny rule; supply explicit domains. isSvgExternalReferenceAllowed()should returnfalsein production. The contract documents this; a policy that returnstrueis a finding, not a configuration choice.
Performance
Section titled “Performance”A policy check is a predicate call: O(1), with no input-proportional cost. The policy is consulted per tag, per attribute, per CSS property, and per URL during parsing. A pathological document multiplies the call count, but each call remains constant time. The performance_budget of 1500 ms wall and 64 MB peak is dominated by parsing and rendering, not by policy evaluation. The input-size and nesting-depth caps bound the parser’s own cost. A strict policy improves worst-case performance by rejecting an oversized or deeply nested document before layout.
Security notes
Section titled “Security notes”These contracts form the engine’s defensive perimeter, so the threat model is explicit. CryptoPolicyInterface mitigates algorithm downgrade by blocking weak hashes and short keys before any operation. HtmlSecurityPolicyInterface mitigates cross-site scripting that affects Portable Document Format (PDF) output and content injection by stripping disallowed tags, attributes, and CSS at the parse layer before the renderer runs. ExternalResourcePolicyInterface mitigates server-side request forgery, decompression bombs, and cumulative-size bombs by defaulting to deny-all and bounding every fetch by scheme, size, depth, and domain. Input-size, nesting-depth, font-glyph, and import-depth caps mitigate resource exhaustion. Because each policy is a contract, you can harden the perimeter without forking the engine, and the policy name is exposed for audit logging. Treat all HTML, all URLs, and all font and image bytes as hostile. This page is marked export_control_class: legal-review-required because the contracts govern cryptographic policy; the prose paraphrases all normative sources and quotes none.
Conformance
Section titled “Conformance”| Claim | Standard | Clause | Evidence |
|---|---|---|---|
| Unconstrained URL handling lets URL changes bypass access control, which the external-resource policy mitigates with deny-all defaults and an exact-match domain allowlist. | OWASP Top 10 2025 | A01 | |
| External component sourcing must be limited to official origins and secure transport, which the policy supports through scheme allowlists. | OWASP Top 10 2025 | Software supply chain |
Both points paraphrase OWASP guidance. The page references OWASP material by clause; the engine does not reproduce its text.
Commercial context
Section titled “Commercial context”Core defines and freezes the three policy contracts. It ships permissive defaults for development and strict defaults for the deny-all resource policy. The Enterprise edition ships a FIPS 140-3 profile behind CryptoPolicyInterface, so a regulated deployment gains a validated algorithm posture without changing the signing or encryption code. The contract surface is identical across editions. The difference is the policy implementation you inject.
See also
Section titled “See also”- Contracts: 41 public interfaces (service provider interface, SPI) — overview and stability tiers.
- Contracts / Signing —
CryptoPolicyInterfaceapplied to signing. - Contracts / Document —
writeHtml()andimage()entry points these policies gate. - Security — the encryption surface the crypto policy constrains.
- HTML — the parse pipeline the HTML and resource policies guard.
- Audit — policy-name audit logging.