Pulp Engine v0.45.0 — Release Notes
This release hardens the hostile-content trust boundary across four renderer layers: IPv6
link-local and IPv4-mapped IPv6 addresses are now blocked in the PDF renderer’s network guard and
the html-renderer’s font-import validator, CSS font import URLs are percent-encoded to prevent
string breakout and escape injection, the PDF renderer’s Puppeteer request guard now uses an
explicit allowlist for non-HTTP(S) schemes (blocking file: and other filesystem access vectors),
and all rendered HTML pages include a Content-Security-Policy meta tag that blocks JavaScript
execution even if a sanitizer bypass occurred. Together these materially improve the
hostile-content trust boundary. Pulp Engine remains designed for trusted-team use and is not safe to
market as fully hostile multi-tenant authoring.
Also included: Docker Compose evaluator setup files, Prisma migration command corrections, editor component refactors (no functional change), and documentation updates.
What changed
1. IPv6 blocking — network guard and html-renderer font-import validator
Prior to this release, the network guard in packages/pdf-renderer/src/network-guard.ts blocked
private IPv4 ranges and ULA IPv6 (fc00::/7) but did not block IPv6 link-local addresses
(fe80::/10) or IPv4-mapped IPv6 addresses (::ffff:-prefixed). A template with a font import
URL of http://[fe80::1]/... or http://[::ffff:127.0.0.1]/... could route requests to private
infrastructure that the IPv4-range guards were intended to block.
packages/pdf-renderer/src/network-guard.ts:
Two new patterns added to BLOCKED_IP_PATTERNS:
/^fe[89ab][0-9a-f]:/i— blocks IPv6 link-local (fe80::/10). The bracket-stripped host starts withfe80:,fe90:,fea0:, orfeb0:, all of which fall in thefe80::/10range./^::ffff:/i— blocks IPv4-mapped IPv6 (::ffff:prefix). Addresses such as::ffff:127.0.0.1or::ffff:192.168.1.1match unconditionally.
Both checks run in isBlockedIpAddress() (synchronous fast path for literal IPs) and in the
async DNS-resolution path via resolveHostnameIps().
packages/html-renderer/src/renderer.ts:
The same two blocking patterns are duplicated in PRIVATE_HOST_PATTERNS for fontImports
validation. The packages are intentionally independent (auditable in isolation), so the patterns
are not shared via a common dependency.
2. CSP meta tag in rendered HTML
buildHead() in packages/html-renderer/src/renderer.ts now injects:
<meta http-equiv="Content-Security-Policy" content="script-src 'none'; object-src 'none';" />
before the <style> block in every HTML page it produces. This ensures that even if an
application-layer sanitizer bypass allowed malicious script content to reach the rendered page,
the browser would refuse to execute it. Plugin objects are similarly blocked by object-src 'none'.
Scope limitation: This CSP applies to the HTML document that Pulp Engine builds and passes to
Puppeteer. It does not apply to Puppeteer’s headerTemplate or footerTemplate contexts,
which run in a separate browser frame managed by Puppeteer itself.
3. Font URL CSS escaping
Font import URLs in fontImports are now percent-encoded before being emitted into @import
rules:
- Backslash (
\) →%5C - Single quote (
') →%27
Without this encoding, a font URL containing ') could break out of the CSS string context and
inject arbitrary @import or other CSS rules. The encoding is applied unconditionally to all
fontImports values before the @font-face block is written.
4. Explicit non-HTTP(S) scheme blocking (PDF renderer)
installRequestGuard() in packages/pdf-renderer/src/renderer.ts has three layers. Layer 3
previously fell through for all non-HTTP(S) schemes, which meant file: URLs were passed to
req.continue() and could potentially access the local filesystem from within the Puppeteer
renderer process.
Layer 3 now uses an explicit allowlist:
data:andblob:— passed through (legitimate inline-resource schemes)- Any other scheme (including
file:,ftp:, etc.) — aborted withblockedbyclient
The PDF renderer has no legitimate need for local filesystem access.
5. Tests
packages/html-renderer/src/node-renderers/renderers.test.ts:
- CSP meta tag is present in
buildHead()output - CSP meta tag precedes the
<style>block script-src 'none'andobject-src 'none'directives are both present- Backslash in font URL is encoded to
%5C - Single quote in font URL is encoded to
%27
packages/pdf-renderer/src/network-guard.test.ts:
- IPv6 link-local address (
fe80::1) is blocked byisBlockedIpAddress() - IPv4-mapped IPv6 address (
::ffff:192.168.1.1) is blocked byisBlockedIpAddress() - DNS rebinding via IPv6 link-local hostname resolution caught in async path
- DNS rebinding via IPv4-mapped IPv6 hostname resolution caught in async path
packages/pdf-renderer/src/renderer.test.ts:
file:///etc/passwdURL is aborted byinstallRequestGuard()- IPv6 link-local address blocked at synchronous fast path
- IPv4-mapped IPv6 address blocked at synchronous fast path
6. Documentation
docs/api-guide.md updated to reflect:
fontImportsvalidation now blocks IPv6 link-local (fe80::/10), IPv4-mapped IPv6 (::ffff:), and ULA (fc00::/7) in addition to existing IPv4 rangescustomCsssafe-mode behaviour clarified:@importandurl()are stripped unconditionally- New “Browser-layer backstop” section documents the CSP meta tag and its scope
docs/editor-guide.md gains a “Rendered-page security posture” section documenting the CSP meta
tag, network-guard blocking ranges, and font URL escaping, with a trust-model note that these
are defences for trusted-team use only.
7. Docker Compose evaluator setup
compose.yaml and compose.postgres.yaml added for turnkey evaluation:
compose.yaml— file-mode, no external database required; templates and assets stored in named volumes; includes health checkscompose.postgres.yaml— PostgreSQL mode; service dependency chain:postgres→migrate→pulp-engine; handles schema migrations automatically
.env.file-mode.example and .env.postgres.example added alongside as starter environment
files.
README.md updated with explicit version-pinning guidance (vX.Y.Z tags rather than latest)
and a “Deployment model — trusted authors” note explaining the intended security model.
8. Prisma migration command corrections
Dockerfile, docs/deployment-guide.md, and docs/release-checklist.md corrected to use the
--entrypoint flag syntax for Prisma migration commands. The previous examples used an incorrect
invocation form that would fail in the published Docker image.
9. Editor component refactors (no functional change)
PreviewPanel.tsx:
Capability state machine (fetch, TTL, visibility-change re-check) extracted to
apps/editor/src/hooks/use-preview-capability.ts. Preview content rendering (status panes, idle
prompt, HTML iframe, PDF object) extracted to
apps/editor/src/components/preview/PreviewContentArea.tsx. OutputState interface now
exported. No behaviour change; identical logic in new locations.
EditorShell.tsx:
Duplicate asset-fetch effects consolidated and extracted to
apps/editor/src/hooks/use-asset-fetching.ts. Issues badge and popover panel extracted to
apps/editor/src/components/shell/IssuesPanel.tsx. No behaviour change.
RenderConfigEditor.tsx and TemplatePickerDialog.tsx: eslint-disable suppression comments
removed (underlying lint issues resolved).
Validation
pnpm --filter @pulp-engine/html-renderer test— 159 passed, 0 failedpnpm --filter @pulp-engine/pdf-renderer test— 157 passed, 0 failedpnpm --filter @pulp-engine/editor lint— 0 errorspnpm run version:check— passed
Upgrade
No breaking changes. All env var names, API shapes, and database schemas are unchanged. No migrations required.
The CSP meta tag in rendered HTML (script-src 'none'; object-src 'none') is a new addition to
every rendered document. In normal Pulp Engine use this has no impact: rendered documents are
document-layout output, not interactive web pages. Any deployment relying on JavaScript execution
within rendered HTML documents should note the change.
Residual risk:
- The DNS-rebinding TOCTOU window is materially reduced but not eliminated: IP addresses are checked at request time, but a sufficiently low DNS TTL could allow a rebinding attack to succeed between the check and the subsequent request.
- The CSP meta tag does not cover Puppeteer
headerTemplate/footerTemplatecontexts. fontImportsURL escaping covers backslash and single-quote breakout vectors;fontImportsremain operator-supplied and should be treated as trusted input only.- Pulp Engine is still not designed for environments where template authors are untrusted or adversarial. These protections harden the boundary for trusted-team deployments; they do not elevate Pulp Engine to a hostile multi-tenant authoring platform.
docker pull ghcr.io/OWNER/pulp-engine:v0.45.0