"csp": {
"mode": "nonce",
"reportOnly": true,
"directives": {
"default-src": "'none'",
"script-src": "'self'",
"style-src": "'self'",
"img-src": ["'self'", "data:"],
"font-src": "'self'",
"connect-src": ["'self'", "https://auth.cladone.com"],
"worker-src": "'self'",
"frame-ancestors": "'none'",
"object-src": "'none'",
"base-uri": "'none'",
"form-action": "'self'"
}
}Strict Content Security Policy
Do I need this guide?
Gate One does not add a Content Security Policy unless a route
declares a csp block. If you only proxy a page through Gate One,
with no csp block and no custom injection, there is nothing to do:
the page’s own policy (if any) passes through unchanged.
| your situation | what to do |
|---|---|
page proxied by Gate One, no | nothing: Gate One adds no CSP. |
page proxied, but the page already sends a CSP | the browser
enforces it. The guard’s inline script/style must be allowed by that
policy, or the guard is blocked. Configure Gate One |
you enable | this guide applies. |
you inject custom HTML/CSS/JS ( | this guide applies: your injected content must satisfy the policy. |
you proxy an external site that sends a CSP | both policies are
enforced; the origin’s policy can block the guard. See “External sites
and |
You need this guide in the last four cases.
What Gate One injects
For a frontend route with the guard active, Gate One adds:
an inline
<script>(the guard: session probe, login recovery, UI gating, user badge);a
<style>element created at runtime by the guard, carrying the CSP nonce of the response;optionally PWA wiring (manifest link, service worker registration) and custom injections from the route bundle.
Since the guard’s UI is entirely class-based, it does not set
style="…" attributes and does not need 'unsafe-inline'. The one
runtime <style> is nonced from document.currentScript.nonce.
The four modes
| mode | behaviour |
|---|---|
| Nothing is emitted or amended. The origin’s policy (if any) is the only one. |
| The origin policy is preserved; the gateway only appends
the hashes of its own injected scripts and of the guard stylesheet to
|
| Gate One emits its own policy; the guard script and the
guard stylesheet are allow-listed by SHA-256 hashes, and explicit
|
| Gate One emits its own policy with a per-response nonce and
stamps inline |
own: true removes the origin’s Content-Security-Policy (and
-Report-Only) headers and lets the gateway’s policy be the only one.
It is opt-in, logged, and only valid with mode: "nonce" or "hash"
and reportOnly: false. Use it only for a site you control/accept and
for which the configured policy is complete. Without own, the origin
policy remains enforced in addition to the gateway’s.
reportOnly: true emits Content-Security-Policy-Report-Only: the
policy is evaluated and violations are reported, but nothing is
blocked. Use it to roll out a policy without breaking the site.
Nonce semantics — read this before enforcing
In nonce mode, Gate One stamps every inline <script> in the
HTML response (unless it already carries a nonce). This includes the
application’s own inline scripts and any custom injection. As a
consequence:
it removes the need for
'unsafe-inline'inscript-src;it does not protect against a
<script>injected into the HTML by a server-side vulnerability, because that script is stamped too.
If you need protection against server-side HTML injection, use the
hash or merge mode with a strict origin policy, and keep
application scripts in external files.
stampScripts:
absent /
inline(default): only<script>tags withoutsrcare stamped; external scripts stay governed by the host allow-list;all: every<script>tag is stamped (only meaningful with a host allow-list or'strict-dynamic').
Styles: the real constraint
CSS has two different mechanisms, and only one accepts a nonce:
| mechanism | controlled by | nonce? |
|---|---|---|
|
| yes |
|
| no |
A nonce (or hash) never applies to a style attribute. To obtain
a strict policy without 'unsafe-inline':
use external CSS files (
style-src 'self');use classes instead of
element.style/style="…";let Gate One nonce or hash its own runtime
<style>(it already does: nonce innoncemode, guard-stylesheet hash inhash/mergemode);for third-party widgets that insist on style attributes, isolate the exception with
style-src-attr 'unsafe-inline'rather than relaxing the wholestyle-src.
Gate One’s guard follows this rule: badge, banners, chips and gating
states are classes; hidden gated elements use
[data-gtw-gated-off]{display:none}. A static test fails the build if
an inline style reappears in the guard.
Application checklist for styles
no
style="…"attributes (including generated by JS);no
element.style = …/.style.cssText/.style.display;use
classListand classes, or an external stylesheet;no
<style>without a nonce (the gateway does not stamp application<style>tags; move them to a CSS file);SVG
styleattributes are style attributes too;third-party embeds: check whether they need
style-src-attr 'unsafe-inline'.
Scripts and event handlers
A strict script-src without 'unsafe-inline' also blocks:
onclick="…"and other inline event handlers (script-src-attr);javascript:URLs;eval(),new Function(),setTimeout("code")('unsafe-eval');document.writeof scripts without a nonce/hash.
Use addEventListener, external scripts and data attributes.
With nonce mode, inline <script> tags are stamped by the gateway,
so existing inline scripts work — but see the server-side injection
caveat above. For the strictest setup, move scripts to external files
and rely on script-src 'self' only.
A strict configuration
Start report-only:
Why these directives:
| directive | role |
|---|---|
| deny by default; every resource type must be explicitly allowed. |
| external same-origin scripts; the gateway adds its nonce and hashes. |
| external same-origin styles; the gateway adds its nonce. |
| add |
| self-hosted fonts; add the font CDN if used. |
|
|
| the PWA service worker ( |
| anti-clickjacking. |
| remove plugin/base-tag injection vectors. |
| restrict form destinations. |
Keep reportOnly: true until the reports are clean, then switch to
false. A policy that emits violations is not protecting the page in
report-only mode.
For a site that you control and whose origin CSP is structurally incompatible (e.g. hosts pinned to the origin), Gate One can own the policy:
"csp": {
"mode": "nonce",
"own": true,
"directives": { "...": "a COMPLETE policy for this site" }
}own removes the origin’s Content-Security-Policy and
-Report-Only headers, logs a warning, and cannot be combined with
reportOnly: true. Do not use it for a site you do not control.
Reading violations
The gateway serves a collector (default
/__gate-one_csp/report) and logs one line per violation:
CSP VIOLATION host="app.example.com" directive="style-src-elem"
blocked="inline" document="https://app.example.com/" source="..." line=572Typical signatures:
| directive | blocked | usual cause / fix |
|---|---|---|
|
| a |
|
|
|
|
| an inline script; in nonce mode the gateway stamps it, so check that the policy is not the origin’s (the origin policy can still block it). |
|
|
|
| missing API/SSE/WebSocket origin. | |
|
| missing source. |
| embedded iframe origin. | |
| URL | service worker origin. |
Browser DevTools also shows the violating element and the computed policy; the gateway log is the aggregate view across users.
An application that already has a CSP
If the application (or the proxified origin) sends its own
Content-Security-Policy, the browser enforces both policies: the
application’s and Gate One’s, whichever is stricter.
For scripts, csp.mode: "merge" appends the SHA-256 hashes of the
guard and other gateway-injected scripts to the origin’s
script-src-elem / script-src / default-src. The origin policy does
not need 'unsafe-inline' for the guard.
For styles, merge appends the SHA-256 hash of the guard’s
stylesheet to the origin’s style-src-elem / style-src /
default-src. The guard’s runtime <style> is then allowed by hash,
without 'unsafe-inline', and the origin policy stays in place. The
guard stylesheet is deterministic per route (static classes + the
route’s uiGate selectors), so the hash is stable and cache-friendly.
This covers the guard, not the application’s own inline styles:
style="…" attributes and application <style> tags still need to
be refactored to classes/external CSS (or covered by the application’s
own nonce/hash). If the origin policy says style-src 'none', the
gateway appends the guard hash, so the guard style is allowed while the
origin’s other restrictions remain.
nonce mode still adds the gateway’s nonce for the guard style; the
origin policy is not amended with that nonce (a per-response nonce
cannot be known by the origin), but the guard stylesheet hash is
appended by merge/hash/nonce modes via the origin amendment.
own: true is the explicit alternative when the origin policy is
fundamentally incompatible: the origin headers are removed and only the
gateway policy applies. Use it only for a site you control/accept and
with a complete policy.
External sites and upstream
For a proxified external site that already sends a CSP, the browser enforces both policies: the origin’s and the gateway’s, whichever is stricter. Gate One never removes or weakens the origin policy.
Consequences:
mergeappends the hashes of the guard script and of the guard stylesheet to the origin’sscript-src-elemandstyle-src-elem(fallbacksscript-src/style-src/default-src). A strict origin policy no longer blocks the guard, without'unsafe-inline'.if the origin policy is incompatible for another reason (hosts pinned to the origin,
script-src 'none'for the site’s own code, etc.), useown: trueorupstream.responseHeaders.removeonly for a site you control/accept, and provide a complete policy;upstream.responseHeaders.removeis still a blunt tool: it is not logged by the CSP layer and removes the origin’s protections. Prefermergeorown.external sites that use inline styles themselves will need
style-src-attr 'unsafe-inline'or a refactor you do not control.
The guard is handled by hash; the remaining difficulty is the site’s own content, not the guard. Document the exception per route.
PWA, MCP, SSE and WebSocket
PWA:
worker-src 'self'for the service worker;manifest-src 'self'if your policy declares it; the Workbox runtime is self-hosted. A CSP nonce is per response: a cached HTML page keeps its nonce, so the cached response must keep the matching CSP header. Workbox caches the response headers with the body; verify this after a cache version change. For long-lived cached HTML,hashmode is more cache-friendly than a per-response nonce.MCP: the MCP endpoint is same-origin (
/mcp);connect-src 'self'is enough for HTTP. If an MCP client uses a WebSocket, add its origin.SSE (
EventSource) and WebSocket are governed byconnect-src, notscript-src.IdP redirects: login is a top-level navigation, not
connect-src;form-actionapplies only to forms, not tolocation.href.
Rollout procedure
Enable
reportOnly: truewith the strict directives.Load the main pages; watch the browser console and the gateway
CSP VIOLATIONlogs.Fix one directive family at a time (styles first: they are the most common).
Re-run the UI smoke tests and a real navigation with a session.
Switch
reportOnly: falseonly after a clean period.Keep a rollback:
reportOnly: true(ormode: "off") can be applied bySIGHUPwithout a restart.
Minimal test commands
# the header actually emitted (nonce changes per response)
curl -sD - -o /dev/null https://app.example.com/ \
| grep -i '^content-security-policy'
# aggregate violations
docker logs <gate-one-container> 2>&1 | grep 'CSP VIOLATION'
# route-level validation
gate-one -config api-routes.json -checkKnown limitations
Gate One does not stamp application
<style>tags; use external CSS.Style attributes cannot be nonced: classes or
style-src-attr 'unsafe-inline'.In
noncemode, all inline scripts are stamped; it is not a defense against server-side HTML injection. Usehash/mergeplus a strict origin policy.A proxified external site’s CSP is enforced in addition to the gateway’s; an incompatible origin policy can block the guard.
Third-party widgets may require a documented
style-src-attrorscript-srcexception.
Gate One’s own UI (guard, badge, banners) is strict by construction and
does not require 'unsafe-inline'.