The dashboard sent script-src 'unsafe-inline' from two sources. Its own
meta/csp.yml granted it to script-src-attr, and build_csp_header adds it
to script-src-elem and script-src-attr for every app with
services.logout.enabled. Both reach script-src through the family union,
and the token in script-src-elem also closes the hash gate, so the
hashes that sys-front-inj-javascript, -dashboard, -logout and -matomo
register for their inline snippets were discarded.
build_csp_header now skips the logout grant on a directive whose
meta/csp.yml sets unsafe-inline: false. Only web-app-dashboard sets it:
on script-src and script-src-elem. Its injected snippets now run by hash
and the logout one-liner is hashed like the others. A render of
build_csp_header for every role, variant, logout on/off and tor on/off
(1712 headers) differs from origin/main only in the 8 web-app-dashboard
headers; the 86 other roles that declare a logout service render
byte-identical policies.
script-src-attr keeps 'unsafe-inline': port-ui 2.2.2 still renders
inline event handlers (menu onclick entries, openDynamicPopup,
closeAllModals, the card image onerror). It goes once the port-ui
release without inline handlers is pinned and header.yml.j2 moves from
onclick to action.
The dashboard Playwright spec asserts that script-src and script-src-elem
carry no 'unsafe-inline' on the landing page, next to its existing check
for CSP violations.
Refs #1122
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
unsafe-inline re-admits the inline execution an XSS needs and unsafe-eval re-admits string-to-code, so a CSP relaxation must be verified against the running app rather than copied from another role. The new lint requires a nocheck marker plus a Reason on the flag's own line, so a marker cannot drift to the neighbouring directive.
The 197 pre-existing grants across 70 roles are checked out with a TODO: they were never verified and still have to be.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add lookup('asset', app, pkg, path) and lookup('asset_host'): both resolve
the asset origin through one shared resolve_host, returning the internal
CDN domain when web-svc-cdn is deployed with flavor internal and
cdn.jsdelivr.net otherwise, so template URLs and CSP origins can never
diverge. Versions come from the role's committed files/package-lock.json.
Roles declare frontend dependencies in files/package.json; web-svc-cdn
aggregates every role lockfile, runs one npm ci per role in a node
container and mirrors the packages under /npm/<pkg>@<version>/ following
jsdelivr's path scheme, so internal and external URLs differ only by host.
Convert web-app-dashboard (keycloak-js 24.0.5 UMD) and web-app-littlejs
(bootstrap, fontawesome) to the asset lookup, drop the vendored keycloak
blob, and add a repository lint forbidding hardcoded external CDN hosts
in templates and CSP metas; pre-existing external CDN entries are checked
out with nocheck markers and a migration TODO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cdn.jsdelivr.net started answering HTTP 400 on its bare root, failing the external URL test for every CSP origin entry. The origins are CSP sources, not document links; suppress the check per line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Autoformat (mbake/yamllint) re-indented sequence items under their mapping key
across the regenerated meta files and wrapped one long URL scalar in the matomo
inject task. Whitespace/formatting only, no semantic change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the csp, domains, and networks blocks out of roles/*/meta/server.yml
into dedicated meta files, each mapping to its own top-level applications key
via the recursive meta-walk:
csp: -> meta/csp.yml -> applications.<app>.csp
domains: -> meta/domains.yml -> applications.<app>.domains
networks: -> meta/networks.yml -> applications.<app>.networks
Residual nginx/proxy keys (status_codes, client_max_body_size, locations,
ip6_enabled, config_upstream_url) stay in server.yml; an emptied server.yml is
deleted. 98 server.yml -> 249 new files, 74 deleted.
- Consumers repointed applications.<app>.server.{csp,domains,networks} ->
applications.<app>.{csp,domains,networks} across templates, vars, tasks,
lookups and filters.
- Infra readers migrated: canonical_domains_map, application_domain_index,
build_ldap_role_entries, service-registry overlay discovery,
roles_with_service, and the file-based readers utils/domains/list.py +
utils/meta/scan.py (read the now-deleted server.yml directly).
- Detection + mapping: _META_TOPICS gains csp/domains/networks;
ROLE_FILE_META_{CSP,DOMAINS,NETWORKS} constants + ROLE_FILES entries.
- New self-expiring lint guard (test_server_meta_no_split_fields) fails if a
server.yml still carries a split field; auto-fails from 2026-12-28 as a
delete reminder.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>