chirp-ui CSP contract check — the effective CSP must keep Alpine alive (#233).
chirp-ui drives its shell with Alpine: components evaluate expressions as JS
(needsscript-src 'unsafe-eval') and toggle visibility via inline
style="display:none"attributes that cannot be nonced (needs
style-src 'unsafe-inline'). If the effective Content-Security-Policy forbids
either, the entire interactive shell (collapse, dropdowns, theme toggle, command
palette, modals) silently dies in the browser — and because CORS masks
cross-origin script errors, there is no console error. That invisible failure is
the worst class, so this rule fails loud atapp.check()time instead.
As of #233use_chirp_ui(app) owns this: it flips csp_nonce_enabled=True,
which makes the compiler wireCSPNonceMiddleware with 'unsafe-eval'+
style-src 'unsafe-inline'automatically — so a stock chirp-ui app passes this
check with no hand-written CSP. The rule exists to catch an app that pins its
own conflicting static CSP (e.g. aSecurityHeadersMiddlewarewhose
script-src drops inline/'unsafe-eval'and there is no nonce mechanism,
or whosestyle-srcforbids inline style).
This rule runs only when chirp-ui is active (extras["chirpui_components"]
is set byuse_chirp_uiat freeze). It is a no-op for non-chirp-ui apps so
they are unaffected.
Severity is env-aware, mirroringrules_security_stack / rules_csp_nonce:
ERROR in production, WARNING in staging, silent in development (the default)
so dev apps and shipped examples stay clean.
The rule stays silent when:
- chirp-ui is not active, or
- a per-request nonce mechanism is active AND its style-src permits inline (the auto-wired path — everything Alpine needs is granted), or
- a static CSP is in force that already permits both inline script
(
'unsafe-inline'or'unsafe-eval'for the eval surface) and inline style.
Detection follows the sibling rules: middleware is matched by class name
(type(mw).__name__) so this layer never imports middleware classes. The
@alpinejs/csp build (alpine_csp=True) avoids evaland the inline
bootstrap, but chirp-ui's shipped components still emit inline@click/
x-show/:class and the modal x-datafactory call, so it is not
treated as exempt here — chirp-ui needs the standard build's relaxations.
contracts.rules_chirpui_csp
| 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 thestyle-srcdirective value from a CSP string, or None.
Falls back todefault-src per the CSP spec when style-srcis absent.
True when the effective style-src forbids inlinestyle=attributes.
A CSP forbids inline style when astyle-src (or fallback default-src) is present and…
True when the script-src permits Alpine's inline bootstrap + eval.
Alpine needs inline script execution: either'unsafe-inline'(covers the bootstrap) or, under a nonce…
Resolve the static CSP string the app will actually send, or None.
Mirrorsrules_csp_nonce._effective_csp: an explicit
SecurityHeadersMiddlewareconfig wins; otherwise the app-level
content_security_policy…
Flag a chirp-ui app whose effective CSP would kill Alpine.
No-op unless chirp-ui is active (extras["chirpui_components"]set by
use_chirp_ui). When active, validates…
_style_src_directive
function
def _style_src_directive(csp: str) -> str | None
Return thestyle-srcdirective value from a CSP string, or None.
Falls back todefault-src per the CSP spec when style-srcis absent.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
csp
|
str
|
— |
_style_forbids_inline
function
def _style_forbids_inline(csp: str) -> bool
True when the effective style-src forbids inlinestyle=attributes.
A CSP forbids inline style when astyle-src (or fallback default-src)
is present and does not list'unsafe-inline'. Alpine's x-showwrites
inline style attributes that cannot be nonced, so this is fatal for chirp-ui.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
csp
|
str
|
— |
_script_allows_alpine
function
def _script_allows_alpine(csp: str) -> bool
True when the script-src permits Alpine's inline bootstrap + eval.
Alpine needs inline script execution: either'unsafe-inline'(covers the
bootstrap) or, under a nonce policy, the bootstrap is nonced — but it still
needs'unsafe-eval'for expression evaluation. This helper answers "does a
static CSP grant what Alpine needs without a nonce mechanism", so it
requires'unsafe-inline' (un-nonced inline) AND 'unsafe-eval'.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
csp
|
str
|
— |
_static_csp
function
def _static_csp(middleware_list: list[Any], config: Any) -> str | None
Resolve the static CSP string the app will actually send, or None.
Mirrorsrules_csp_nonce._effective_csp: an explicit
SecurityHeadersMiddlewareconfig wins; otherwise the app-level
content_security_policy fallback. The dynamic CSPNonceMiddlewareCSP
is detected separately (it is always nonce-bearing + Alpine-aware by
construction once chirp-ui flips the flag).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
middleware_list
|
list[Any]
|
— | |
config
|
Any
|
— |
check_chirpui_csp
function
def check_chirpui_csp(router: Router, config: Any, middleware_list: list[Any], extras: dict[str, Any] | None = None) -> list[ContractIssue]
Flag a chirp-ui app whose effective CSP would kill Alpine.
No-op unless chirp-ui is active (extras["chirpui_components"]set by
use_chirp_ui). When active, validates the EFFECTIVE CSP the app will send:
- script-src must allow Alpine's inline bootstrap + eval (via a nonce
mechanism — auto-wired by
csp_nonce_enabled— or static'unsafe-inline'+'unsafe-eval'); - style-src must allow inline style (Alpine
x-showis un-nonceable).
Severity is env-aware (production ERROR, staging WARNING, development silent),
mirroringrules_security_stack / rules_csp_nonce.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
router
|
Router
|
— | |
config
|
Any
|
— | |
middleware_list
|
list[Any]
|
— | |
extras
|
dict[str, Any] | None
|
None
|
View source · /home/runner/work/chirp/chirp/site/../src/chirp/contracts/rules_chirpui_csp.py:1