CSP-nonce contract check — framework inline scripts need a nonce mechanism (#181, #195).
When an app ships a nonce-based / inline-forbidding Content-Security-Policy
(ascript-src that does not include 'unsafe-inline'), every inline
<script> the framework emits must carry a live nonceattribute or it is
silently blocked by the browser.
As of #195 the framework injects every compile-time inline<script>through a
per-request snippet factory (nonce -> snippet), so each script carries the
live nonce whenever a per-request nonce mechanism is active — that is,
CSPNonceMiddleware is wired, or config.csp_nonce_enabledis set (which
auto-wires it at freeze time). The framework's inline-script surfaces are:
- the Alpine
safeDatabootstrap (alpine=True, non-CSP build), - the htmx
safe_targetscript, - the
sse_lifecyclescript, - the event
delegationscript, - the
view_transitionsscript ("htmx"/"full"modes), - the islands runtime bootstrap (
islands=True), - the
speculation_rules<script type="speculationrules">, - Suspense initial-load OOB scripts (nonced via the request lifecycle, #181).
The genuinely un-nonceable case this rule flags is therefore narrow: an
inline-forbidding CSP is in force but there is no per-request nonce mechanism
— e.g. a staticSecurityHeadersMiddleware CSP whose script-srcdrops
'unsafe-inline' without CSPNonceMiddlewareand without
csp_nonce_enabled. In that configuration csp_nonce() returns ""so the
factories emit un-nonced scripts that the browser blocks — and at least one
framework inline-script feature is enabled, so something actually breaks.
Severity is env-aware, mirroringrules_security_stack: ERROR in production,
WARNING in staging, silent in development (the default) so dev apps and
shipped examples stay clean.
The rule stays silent when:
- no inline-forbidding policy is in force (no nonce-only CSP), or
- a per-request nonce mechanism is active (everything is nonceable), or
- no framework inline-script feature is enabled (nothing to block).
The@alpinejs/csp build (alpine_csp=True) ships no inline bootstrap, so it
is never counted as an inline-script feature.
Detection followsrules_security_stack: middleware is matched by class
name (type(mw).__name__) so this layer never imports middleware classes.
The CSP string is read fromSecurityHeadersMiddlewareconfig or
config.content_security_policyand parsed for an inline-forbidding
script-src. This check does not double-fire with security_stack
(CSRF/Session presence) orcsrf_session(stack ordering).
contracts.rules_csp_nonce
| Name | Type | Default | Description |
|---|---|---|---|
type
|
|
— | |
qualified_name
|
|
— | |
element_type
|
|
— | |
description
|
|
— | |
source_file
|
|
— | |
line_number
|
|
— | |
is_autodoc
|
|
— | |
autodoc_element
|
|
— | |
_autodoc_template
|
|
— | |
_autodoc_url_path
|
|
— | |
_autodoc_page_type
|
|
— | |
title
|
|
— | |
doc_content_hash
|
|
— |
Symbols on this page
Return thescript-srcdirective value from a CSP string, or None.
Falls back todefault-src per the CSP spec when script-srcis absent.
True when the effective script-src forbids inline scripts.
A CSP forbids inline scripts when ascript-src(or fallback
default-src) is present and does …
Resolve the static CSP string the app will actually send.
Order of precedence mirrors how responses are built: an explicit
SecurityHeadersMiddlewareconfig wins; otherwise…
Return the framework inline-script features enabled onconfig.
Each entry names a compile-time inline<script>the framework injects.
alpine_csp=Trueis excluded because the …
Flag framework inline scripts blocked by a nonce-only CSP with no nonce.
Every framework inline<script>is built through a per-request snippet factory (#195),…
_script_src_directive
function
def _script_src_directive(csp: str) -> str | None
Return thescript-srcdirective value from a CSP string, or None.
Falls back todefault-src per the CSP spec when script-srcis absent.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
csp
|
str
|
— |
_forbids_inline
function
def _forbids_inline(csp: str) -> bool
True when the effective script-src forbids inline scripts.
A CSP forbids inline scripts when ascript-src(or fallback
default-src) is present and does not list 'unsafe-inline'.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
csp
|
str
|
— |
_effective_csp
function
def _effective_csp(config: Any, middleware_list: list[Any]) -> str | None
Resolve the static CSP string the app will actually send.
Order of precedence mirrors how responses are built: an explicit
SecurityHeadersMiddlewareconfig wins; otherwise the app-level
content_security_policy(if any). This is the static CSP only — the
dynamic per-request nonce CSP fromCSPNonceMiddlewareis detected
separately (it is always nonce-bearing by construction, so its presence is a
nonce mechanism, not an un-nonceable hazard).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
config
|
Any
|
— | |
middleware_list
|
list[Any]
|
— |
_enabled_inline_script_features
function
def _enabled_inline_script_features(config: Any) -> list[str]
Return the framework inline-script features enabled onconfig.
Each entry names a compile-time inline<script>the framework injects.
alpine_csp=True is excluded because the @alpinejs/cspbuild ships no
inline bootstrap.view_transitions is counted only in its non-off
modes (True/"htmx"/"full"); its "full"HEAD markup is a
<style> governed by style-src, but the script snippet is always
present in non-offmodes.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
config
|
Any
|
— |
check_csp_nonce
function
def check_csp_nonce(router: Router, config: Any, middleware_list: list[Any], discovered_routes: list[Any] | None = None) -> list[ContractIssue]
Flag framework inline scripts blocked by a nonce-only CSP with no nonce.
Every framework inline<script>is built through a per-request snippet
factory (#195), so it carries the live nonce whenever a per-request nonce
mechanism is active (CSPNonceMiddleware or csp_nonce_enabled). The
genuinely un-nonceable case — flagged here — is an inline-forbidding CSP in
force with no nonce mechanism while a framework inline-script feature is
enabled. Thencsp_nonce() returns ""and the factories emit un-nonced
scripts the browser silently blocks.
Severity is env-aware (production ERROR, staging WARNING, development
silent), mirroringrules_security_stack.
Stays silent when: no inline-forbidding policy is in force; or a nonce mechanism is active (everything is nonceable); or no inline-script feature is enabled.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
router
|
Router
|
— | |
config
|
Any
|
— | |
middleware_list
|
list[Any]
|
— | |
discovered_routes
|
list[Any] | None
|
None
|
View source · /home/runner/work/chirp/chirp/site/../src/chirp/contracts/rules_csp_nonce.py:1