Release v0.83.0 — Post-audit remediation (SSRF, editor safety, CI evidence)
This release works through the adverse findings of the second post-hardening quality audit. It centralises and fixes the SSRF host blocklist (closing an any-address bypass), makes the editor’s property-panel field staging uniformly undo-safe, adds review-time CI signals without inflating the GitHub Actions budget, fixes two turbo cache-input gaps, and converts the renderer coverage debt into a tracked initiative. Security commits are self-contained and lead the branch so they can be cherry-picked to a patch if needed.
Security
- SSRF host blocklist centralised; any-address
0.0.0.0/::now blocked (F1/F2). Three hand-maintained private-host blocklists had drifted — the PDF render network guard, the HTML image-srcguard, and the HTML font-import guard — and the image guard had lost the link-local (fe80::/10) and IPv4-mapped (::ffff:) IPv6 patterns. All three also missed the IPv4/IPv6 any-address forms (0.0.0.0,::,::0), which route to loopback on many systems and so bypassed the loopback block the guard advertises. The lists are replaced by one canonical source in@pulp-engine/renderer-common(private-hosts.ts) that includes the any-address forms; the three call sites import it, so they can never drift again.packages/pdf-renderer/src/network-guard.tsstays a thin compatibility wrapper (itsisBlockedHostname/isBlockedIpAddress/resolveHostnameIpsexports are unchanged), so the API’s outboundsafeFetchand webhook SSRF guards — which import from it — inherit the fix automatically. - Two additional SSRF edge cases closed (F1). A protocol-relative image
srcsuch as//169.254.169.254/x.pngcarries no:and was previously treated as a safe relative path; it is now resolved against an explicit base and run through the same host check. The PDF render guard’s numeric-IP fast path now re-checks the literal IP withisBlockedIpAddressand aborts, instead of trusting the synchronous URL check to have caught every form.isSafeFontUrlalready rejected protocol-relative URLs (new URL()with no base throws) — a regression test now guards that. - Scope: these are defence-in-depth guards within the product’s trusted-tenant model; remote
resource loading is opt-in (
BLOCK_REMOTE_RESOURCES), and hardened production already blocks all remote fetches. The fix removes a real hole in a shipped control regardless.
Editor
- Uniform, undo-safe property-panel field staging (F3).
HeadingNodePropsandSectionNodePropspreviously hand-rolleduseState+ auseEffectthat reset local state on every external change, so an undo/redo while editing could clobber an in-progress edit. Both now use the shared, dirty-guardeduseStagedField/useImmediateFieldhooks (the same onesTextNodePropsuses), so external changes are suppressed while a field is dirty and reset only on node switch. New unit tests cover commit-on-blur/Enter, same-node sync, dirty-preservation, and node-switch reset. The two remainingreact-hooks/exhaustive-depssuppressions flagged by the audit (BlockPalette,DockedPreview) were reviewed and documented as intentional rather than churned.
Operability & CI
- Lighter PR-time signal +
run-full-ciopt-in (F4). Pull requests now run two cheap review-time jobs —test-sqlserver-smoke(apply migrations to a fresh mssql, the highest-signal-per-minute check for DDL regressions, plus the core store smoke) andtest-e2e-smoke(one core editor spec). The fulltest-sqlserver/test-e2e/test-e2e-authsuites, Docker build+smoke, and the two Windows jobs stay gated to push/release to conserve Actions minutes. To run the whole matrix on a specific PR, add therun-full-cilabel (thepull_requesttrigger now listens forlabeled), at which point the two smokes skip to avoid double-spending. - Turbo cache-input correctness (F6).
schema.prismais now abuildinput, so a Prisma schema-only change invalidates the API build cache (it still requiresdb:generateto regenerate the client). The SDK’s out-of-src__tests__/directory is now in thetestinput set. Andopenapi.jsonis a scoped build input for@pulp-engine/sdk(via a package-levelturbo.json), so a spec-only change can no longer FULL-TURBO cache-hit and ship stale generated SDK types — without invalidating every other package’s build.
Evidence & coverage
- Renderer coverage debt is now tracked (F5).
docs/initiatives/coverage-ratchet.mdrecords the current floors (API 73/63, html-renderer 57/53, pdf-renderer 80/72), the API 74/72 north-star, the cheap-first paydown approach (html-renderer node renderers — explicitly not the type-onlydispatcher.tsor the IPC-entrypointworker.ts), and the rule that floors are ratcheted up in a separate follow-up commit from the CI-Linux number. A first paydown adds direct tests for the previously-0%-covered structural renderers (spacer/page-break/container/columns). - Vitest
dist-exclude parity (P4).apps/websiteandpackages/sdk-typescriptgain the explicitexclude: ['**/node_modules/**','**/dist/**']the other 22 configs already carry, without narrowing test discovery (the SDK’s__tests__/smoke.test.tsstill runs).
Documentation
- Support policy (P1): “Last reviewed” date refreshed and cross-referenced to the v0.82.0 security release; the support terms are unchanged by it.
- Evaluator guide (P2): AI template generation is flagged as requiring
ANTHROPIC_API_KEY, withPOST /templates/generatereturning503when unset — “unconfigured”, not “broken”. - Runbook (P3): a new “Upgrading to v0.82.0 — OIDC
id_tokenBearer deprecation” section documents what breaks, the401operators will see, how to spot raw-id_tokencallers in the logs, and the/auth/oidc/exchangemigration (body fieldoidcToken). - Deployment guide (P5): the multi-tenant section now prominently notes that isolation is
row/path-level, not compute-level (a shared Chromium pool in the default
child-processrender mode), and directs untrusted/semi-trusted tenants toRENDER_MODE=containerorsocket.
Validation
CI-verified — the release commit is gated by release.yml’s check-ci job, which refuses to
build or publish any artifact unless a successful push-event ci.yml run exists for the exact tagged
commit on main. That run is the full matrix: ci (lint, typecheck, build, unit + coverage gate),
test-file-mode, test-sqlserver, test-e2e, test-e2e-auth, and docker-build-smoke (plus the
two Windows jobs). On the pre-merge PR (#76) the light review set was already green: ci,
test-file-mode, and the two new PR smokes test-sqlserver-smoke (1m59s) and test-e2e-smoke
(2m17s); the run-full-ci opt-in was exercised and correctly flipped the matrix on/off.
Locally verified (by the release author, outside CI):
pnpm build/pnpm typecheck(46/46) /pnpm lint(22/22) — green.- Per-package suites: renderer-common 113, pdf-renderer 340, html-renderer 344, editor 1405 (2 skipped), website 60, sdk 8; API safe-fetch/webhook guards 28 — all green.
- Renderer coverage above floor: html-renderer 63.40% lines / 56.52% branches (floor 57/53), pdf-renderer 86.89% / 73.99% (floor 80/72), renderer-common 95.29% / 77.36%.
pnpm extract-openapi --check—openapi.jsonfresh after the 0.83.0 stamp;pnpm version:checkpasses across all 7 lockstep surfaces.- CI-Linux API coverage on the PR run: 75.24% lines / 65.44% branches (floor 73/63).
Not verified — full SQL Server suite locally (no local SQL Server instance — CI-covered by the
test-sqlserver job), the Playwright E2E suites locally (CI-only), and a deployment rehearsal (not
performed). PyPI / Go / .NET SDK publishing remains deferred (TypeScript SDK + GHCR images only); see
the SDK publishing notes for prior releases.
Upgrade notes
- No schema changes, no new required environment variables, no API contract changes. This release is drop-in over v0.82.0.
- Operators: to run the full CI matrix on a specific PR, apply the
run-full-cilabel. - If you maintain a fork or downstream renderer: the private-host SSRF patterns now live in
@pulp-engine/renderer-common(PRIVATE_HOST_PATTERNS/isPrivateHostname/isPrivateIpAddress). The@pulp-engine/pdf-renderer/network-guardexport surface is unchanged.