Skip to content

feat(admin/security): strict CSP with Trusted Types + DOMPurify in editor - #469

Merged
tayebmokni merged 3 commits into
mainfrom
feat/admin-strict-csp-tt
May 25, 2026
Merged

feat(admin/security): strict CSP with Trusted Types + DOMPurify in editor#469
tayebmokni merged 3 commits into
mainfrom
feat/admin-strict-csp-tt

Conversation

@tayebmokni

@tayebmokni tayebmokni commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #59, #90.

Delivers the strict admin CSP and the DOMPurify-backed Trusted Types layer:

  • AdminStrictPolicy preset (packages/go/middleware/csp/preset.go) emits script-src 'self' 'nonce-…' 'strict-dynamic', require-trusted-types-for 'script', and trusted-types gn-admin gn-editor 'allow-duplicates'. Mirrors CSP3 §6.1's strict pattern; strict-dynamic closes the host-allowlist trap by transitively trusting nonced bundle loads.
  • Next.js middleware (apps/admin/middleware.ts) emits the exact same shape so the dev path matches prod byte-for-byte. New negative assertions in middleware.test.ts lock the absence of gn-plugin / nextjs#bundler / default from the trusted-types directive.
  • Trusted Types policies (apps/admin/src/lib/trusted-types.ts, packages/ts/blocks-editor/src/trusted-types.ts) register gn-admin (strict admin chrome) and gn-editor (block editor; permits inline SVG). Both route input through DOMPurify before minting TrustedHTML. SSR / non-TT environments fall through to a shim that still sanitises so unit tests observe identical behaviour.
  • HelperssetHTML(el, html), setURL(el, attr, url), <SafeHTML> (declarative React wrapper), and sanitizeBlockIcon(svg) for the editor's icon path.
  • DOMPurify added to apps/admin/package.json; isomorphic-dompurify added to packages/ts/blocks-editor/package.json (SSR-safe path for blocks-editor consumers).
  • Sweep — every dangerouslySetInnerHTML callsite under apps/admin/src and packages/ts/blocks-editor/ converted: /search page and <GlobalSearch> (admin) now use <SafeHTML>; <BlockTile> icon (blocks-editor) wraps through sanitizeBlockIcon.
  • ESLint guardapps/admin/.eslintrc.json gains react/no-danger: error so any future regression fails CI.

Test plan

  • pnpm vitest run in apps/admin — 68 files, 470 tests pass (+20 new specs).
  • pnpm vitest run in packages/ts/blocks-editor — 24 files, 236 tests pass (+8 new specs).
  • pnpm typecheck + pnpm lint clean in both packages.
  • pnpm build (Next.js prod build) succeeds.
  • go test ./packages/go/middleware/csp/... ./apps/api/... green.
  • Manually load /search?q=… in dev — confirm no Trusted Types violations and <mark> highlights still render.
  • Manually load /posts/new (block editor) — confirm registered SVG block icons render under the strict CSP.
  • Inspect the Content-Security-Policy response header in dev — confirm strict-dynamic, the gn-admin/gn-editor policy names, and absence of unsafe-inline / unsafe-eval.

tib0o0o and others added 3 commits May 25, 2026 23:17
Adds AdminStrictPolicy() which tightens AdminPolicy() in three ways
required by the strict admin CSP shape:

- script-src always includes 'strict-dynamic' so the per-request nonce
  transitively authorizes the Next.js runtime's dynamic chunks without
  a per-host allowlist.
- require-trusted-types-for 'script' forced on.
- trusted-types defaults to "gn-admin gn-editor 'allow-duplicates'" —
  the policy names the admin and block editor mint at runtime.

Caller overrides (TrustedTypePolicies, IncludeStrictDynamic) still win
so a hardened deployment can drop 'allow-duplicates' or constrain the
list further.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
Adds the `gn-admin` and `gn-editor` Trusted Types policies plus the
`setHTML(el, str)` / `setURL(el, attr, str)` helpers in
apps/admin/src/lib/trusted-types.ts. Both policies route their input
through DOMPurify (admin: strict profile; editor: strict + inline-SVG
profile) before minting the TrustedHTML value, so admin code that
previously assigned to `innerHTML` directly now satisfies the strict
admin CSP `require-trusted-types-for 'script'` directive.

A companion `<SafeHTML>` component wraps the imperative setter for
React render-side use. Every prior `dangerouslySetInnerHTML` callsite
in the admin (`/search` page, `<GlobalSearch>`) now uses `<SafeHTML>`;
the block inserter's icon path routes through the new
`sanitizeBlockIcon` helper in `@gonext/blocks-editor`, which uses
the SSR-safe `isomorphic-dompurify` build so SSR + Trusted Types are
both honoured.

Tests: 18 specs for `apps/admin/src/lib/trusted-types.ts` covering
policy registration, idempotency, SSR fallback, script-vector
sanitization, URL pseudo-scheme rejection, and the React escape
hatch. 8 specs for `packages/ts/blocks-editor/src/trusted-types.ts`
covering icon sanitization (SVG preservation, foreignObject removal,
onerror stripping). All 68 existing admin suites (468 tests) and 24
blocks-editor suites (236 tests) remain green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
Switches apps/admin/middleware.ts to emit the AdminStrictPolicy shape:

- script-src 'self' 'nonce-…' 'strict-dynamic'  (transitive nonce trust)
- trusted-types gn-admin gn-editor 'allow-duplicates'

The TRUSTED_TYPES_POLICIES list is now exactly { gn-admin, gn-editor,
'allow-duplicates' } — dropping the legacy gn-plugin / nextjs#bundler /
default entries that defeated the point of 'strict-dynamic' (any
listed bundler policy can mint scripts without nonce review). Plugin-
contributed HTML must round-trip through gn-editor (DOMPurify) instead.

Adds react/no-danger as an ESLint error in apps/admin/.eslintrc.json so
any new dangerouslySetInnerHTML usage fails CI; the existing call sites
were converted to <SafeHTML> / sanitizeBlockIcon in commit 051ea51.

Middleware tests updated: new assertions for 'strict-dynamic', the
allow-duplicates keyword, and explicit denials for the dropped policy
names. Tiny TS fix in packages/ts/blocks-editor/src/trusted-types.ts
to keep the DOMPurify Config object mutable (Config has string[] field
types).

Closes #59.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
@tayebmokni
tayebmokni enabled auto-merge (squash) May 25, 2026 21:27
@github-actions

Copy link
Copy Markdown

Heads up — this PR touches strings that often signal a security disclosure (vulnerab, CVE-, exploit, bypass, or auth bypass).

If this PR fixes or describes a real vulnerability that has not yet been publicly disclosed, please stop and use the private path:

  1. Open a private security advisory, or
  2. Email security@gonext.io with subject [SECURITY] GoNext - <summary>.

See /SECURITY.md for the full disclosure flow and /docs/16-bug-bounty.md for bounty terms.

If this is a false positive (test fixture, doc update, release notes, etc.) please ignore this comment — the check is advisory only and does not block the PR.

Matched files:

  • apps/admin/middleware.ts
  • apps/admin/src/lib/trusted-types.test.ts
  • apps/admin/src/lib/trusted-types.ts

@tayebmokni
tayebmokni merged commit 9a31406 into main May 25, 2026
13 of 17 checks passed
@tayebmokni
tayebmokni deleted the feat/admin-strict-csp-tt branch May 25, 2026 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Author strict admin CSP with Trusted Types

2 participants