l4d2web/csp: allow Steam avatar CDN in img-src
The live-state grid renders player avatars as <img src="https://avatars.steamstatic.com/...">, but the CSP img-src directive was `'self' data:` — so the browser silently blocked every avatar load, leaving placeholder circles in place. The DB cache and Steam API path were both healthy; only the browser-side load was blocked. Use the wildcard *.steamstatic.com host-source rather than pinning a single hostname: Steam rotates avatars across steamcdn-a.akamaihd.net, avatars.akamai/cloudflare/fastly.steamstatic.com over time, and a single-hostname allowlist would re-break on the next shuffle. Test now pins img-src explicitly — the previous assertions only checked default-src/frame-ancestors/form-action, so a regression of this exact line would have silently passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b13d164931
commit
e28d4fad8c
2 changed files with 7 additions and 1 deletions
|
|
@ -90,12 +90,14 @@ def create_app(test_config: dict[str, object] | None = None) -> Flask:
|
||||||
nonce = getattr(g, "csp_nonce", "")
|
nonce = getattr(g, "csp_nonce", "")
|
||||||
# script-src nonce blocks inline XSS; style 'unsafe-inline' kept for
|
# script-src nonce blocks inline XSS; style 'unsafe-inline' kept for
|
||||||
# htmx's auto-injected indicator styles. img/data: for SVG icons.
|
# htmx's auto-injected indicator styles. img/data: for SVG icons.
|
||||||
|
# *.steamstatic.com covers Steam avatar hosts (avatars.steamstatic.com
|
||||||
|
# plus the cloudflare/akamai/fastly mirrors they rotate through).
|
||||||
response.headers.setdefault(
|
response.headers.setdefault(
|
||||||
"Content-Security-Policy",
|
"Content-Security-Policy",
|
||||||
"default-src 'self'; "
|
"default-src 'self'; "
|
||||||
f"script-src 'self' 'nonce-{nonce}'; "
|
f"script-src 'self' 'nonce-{nonce}'; "
|
||||||
"style-src 'self' 'unsafe-inline'; "
|
"style-src 'self' 'unsafe-inline'; "
|
||||||
"img-src 'self' data:; "
|
"img-src 'self' data: https://*.steamstatic.com; "
|
||||||
"connect-src 'self'; "
|
"connect-src 'self'; "
|
||||||
"frame-ancestors 'none'; "
|
"frame-ancestors 'none'; "
|
||||||
"base-uri 'self'; "
|
"base-uri 'self'; "
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,10 @@ def test_security_headers_present(client) -> None:
|
||||||
assert "default-src 'self'" in csp
|
assert "default-src 'self'" in csp
|
||||||
assert "frame-ancestors 'none'" in csp
|
assert "frame-ancestors 'none'" in csp
|
||||||
assert "form-action 'self'" in csp
|
assert "form-action 'self'" in csp
|
||||||
|
# Steam avatar CDN must be explicitly allowed; otherwise the browser
|
||||||
|
# silently blocks the avatar <img> loads and the live-state grid shows
|
||||||
|
# placeholder circles with names but no faces.
|
||||||
|
assert "img-src 'self' data: https://*.steamstatic.com" in csp
|
||||||
|
|
||||||
|
|
||||||
def test_csp_nonce_matches_inline_script(client) -> None:
|
def test_csp_nonce_matches_inline_script(client) -> None:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue