feat(publisher): site-level CSP allowlist for third-party script origins - #513
Draft
mjennings3 wants to merge 1 commit into
Draft
feat(publisher): site-level CSP allowlist for third-party script origins#513mjennings3 wants to merge 1 commit into
mjennings3 wants to merge 1 commit into
Conversation
Published pages lock `script-src` to `'self'`, and nothing could lift it: plugin `frontend.assets[]` are same-origin only and `networkAllowedHosts` reaches `connect-src`, not `script-src`. Any site that needs a Google Analytics / Tag Manager loader, the Meta pixel, or a chat widget therefore ships with those tags silently blocked (CoreBunch#227). Add `site.settings.csp` — an owner-controlled allowlist of exact HTTPS origins: `scriptOrigins` union into `script-src`, `connectOrigins` union into `connect-src` together with `'self'` (the base plan has no `connect-src`, so creating one without `'self'` would cut same-origin fetches off from their `default-src` fallback). Entries are validated by `isCspOrigin` (`https://host[:port]`, optional `*.` label; never a path, `http://`, or a CSP keyword) and `parseSiteSettings` drops invalid entries one by one instead of failing the settings object. The plugin relaxation pass now unions `'self'` / `'unsafe-inline'` into `script-src` and `style-src` instead of replacing the directive, so a plugin tracker no longer strips the importmap sha256 hash or the site allowlist that the publisher already emitted. The write policy classifies `settings.csp` as structural. Editing surface: Settings → Publishing → Content Security Policy, two textareas (one origin per line, commit on blur, invalid lines reported field-locally and never persisted). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Would be awesome. I can't embed fonts from adobe or submit my forms to self-hosted n8n because of this issue. |
|
Also sould we please expand Third-party hosted fonts like Adobe Fonts (Typekit) cannot be self-hosted due to encrypted/dynamic font streaming and licensing terms. To support them without violating CSP, we need:
This would allow configuring:
|
|
Hey, I don't require this anymore, I wrote a plugin to serve the fonts locally from the domain. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Adds
site.settings.csp, an owner-controlled allowlist of exact HTTPS origins that the publisher merges into the published-page Content-Security-Policy:scriptOrigins→ unioned intoscript-srcconnectOrigins→ unioned intoconnect-srctogether with'self'(the base plan has noconnect-src; creating one without'self'would cut same-origin fetches for forms, holes and loops off from theirdefault-srcfallback)Entries are validated by
isCspOrigin:https://host[:port], optionally a leading*.wildcard label. Paths,http://, credentials and CSP keywords ('unsafe-inline',*) are rejected.parseSiteSettingsdrops invalid entries one at a time instead of failing the settings object, and an empty allowlist never persists.Editing surface: Settings → Publishing → Content Security Policy, two textareas (one origin per line, commit on blur, invalid lines reported field-locally with
role="alert"and never persisted). Uses the existingTextareaprimitive and tokens only.Two related fixes in the same path:
relaxCspForPlan(plugin frontend assets) now unions'self'/'unsafe-inline'intoscript-srcandstyle-srcinstead of replacing the directive. Before, any plugin shipping a frontend script silently stripped the importmapsha256-…hash the publisher had emitted (and would have stripped this allowlist).siteDiff.ts) classifiessettings.cspas a structural setting.Why
Published pages lock
script-srcto'self', and nothing could lift it: pluginfrontend.assets[]are same-origin only andnetworkAllowedHostsreachesconnect-src, notscript-src. Any site that needs a Google Analytics / Tag Manager loader, the Meta pixel, or a chat widget ships with those tags blocked, with the only symptom being a console CSP violation. This is the gap described in #227.A per-site allowlist keeps the intent of the strict default (visitor pages run no host-supplied JS unless the owner says so) while giving the owner one explicit, auditable place to say so. It deliberately does not add a manifest field for plugins: which third parties may run on a site is the owner's decision, not a plugin's.
Impact
SiteSettings.csp?: SiteCspSettings(optional, additive JSON shape, no migration). New barrel exports from@core/page-tree:SiteCspSettingsSchema,CSP_ORIGIN_PATTERN,isCspOrigin,parseCspOriginList,parseSiteSettings. Docs updated inpublisher.md(CSP section),site-shell.md(SiteSettings),plugin-system.md,editor.md.Verification
New tests:
page-tree/siteSettings.test.ts(origin validation, tolerant parse),publisher/cspPlan.test.ts(allowlist throughpublishPage, survives the plugin pass),publisher/frontendInjections.test.ts(union keeps importmap hash + allowlist),settings/settingsSections.test.tsx(UI commit / invalid-line reporting / clearing).Closes #227.
🤖 Generated with Claude Code