contracts.rules_csp_nonce

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

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 …

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 AlpinesafeData bootstrap (alpine=True, non-CSP build),
  • the htmxsafe_targetscript,
  • thesse_lifecyclescript,
  • the eventdelegationscript,
  • theview_transitions script ("htmx"/"full"modes),
  • the islands runtime bootstrap (islands=True),
  • thespeculation_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

_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