diff options
| author | Christian Cleberg <[email protected]> | 2026-08-03 12:55:47 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-08-03 12:55:47 -0500 |
| commit | 7628832bfe9aaf3deafc2787e596e0ca65b0cad0 (patch) | |
| tree | e8962ea842b32dd8bf11804dbdc2a51c5bd330f6 /linux/nginx/etc | |
| parent | 263f281b30ac0f7daaf654e2e9abee71f9d80709 (diff) | |
| download | dotfiles-7628832bfe9aaf3deafc2787e596e0ca65b0cad0.tar.gz dotfiles-7628832bfe9aaf3deafc2787e596e0ca65b0cad0.tar.bz2 dotfiles-7628832bfe9aaf3deafc2787e596e0ca65b0cad0.zip | |
nginx: enforce piped CSP, drop dead freshrss CSP and rss vhost
Promote piped.krz.sh from Content-Security-Policy-Report-Only to enforcing.
Validated in a real browser first: homepage, VOD playback and seek on two
videos, channel page and a 39-result search were all clean, and every host
contacted was already allowed (self, pipedapi, pipedproxy, blob:). No fallback
to pipedproxy.kavin.rocks, so viewer IPs stay on the self-hosted proxy. The
check was proven meaningful before being trusted by injecting a disallowed
image and confirming a securitypolicyviolation with disposition "report".
Remove the FreshRSS CSP (include file plus the $csp_freshrss map). The header
never reached a browser: rss.krz.sh is routed tunnel-direct to the container
on :8099 and never transits nginx. Promoting it would also have regressed
behaviour rather than hardened it, since browsers enforce the intersection of
stacked policies and this one was stricter than FreshRSS's own exactly where
feed content lives (no blob: in img-src, no * in media-src/frame-src).
FreshRSS keeps its own, correctly RSS-aware, policy.
Delete conf.d/rss.conf entirely. It was unreachable: no onion targets :10045,
no other vhost uses the port, and rss.zerolabs.sh is redirected to rss.krz.sh
at the Cloudflare edge before the origin is reached.
Diffstat (limited to 'linux/nginx/etc')
4 files changed, 14 insertions, 52 deletions
diff --git a/linux/nginx/etc/nginx/conf.d/rss.conf b/linux/nginx/etc/nginx/conf.d/rss.conf deleted file mode 100644 index 963950f..0000000 --- a/linux/nginx/etc/nginx/conf.d/rss.conf +++ /dev/null @@ -1,31 +0,0 @@ -upstream freshrss { server 127.0.0.1:8099; keepalive 64; } -server { - listen 127.0.0.1:10045; - server_name rss.zerolabs.sh; - - include custom.d/basic.conf; - location / { - proxy_pass http://freshrss/; - # This add_header discarded EVERYTHING from basic.conf -- nginx does - # not merge add_header across levels. Verified: this vhost was serving - # X-Frame-Options and nothing else (no Referrer-Policy, no HSTS, no - # Permissions-Policy, no X-Content-Type-Options). Restored below. - # SAMEORIGIN (not the shared map's DENY) -- hence the _no_xfo bundle, - # which omits X-Frame-Options so the two do not conflict. - add_header X-Frame-Options SAMEORIGIN; - include custom.d/security/headers_in_location_no_xfo.conf; - include custom.d/security/content-security-policy-freshrss-report-only.conf; - proxy_redirect off; - proxy_buffering off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Port $server_port; - proxy_read_timeout 90; - proxy_set_header Authorization $http_authorization; - proxy_pass_header Authorization; - } - include custom.d/security/robots_index_only.conf; -} - diff --git a/linux/nginx/etc/nginx/custom.d/http/content_type_maps.conf b/linux/nginx/etc/nginx/custom.d/http/content_type_maps.conf index b27cbf0..f8a5e84 100644 --- a/linux/nginx/etc/nginx/custom.d/http/content_type_maps.conf +++ b/linux/nginx/etc/nginx/custom.d/http/content_type_maps.conf @@ -99,13 +99,19 @@ map $sent_http_content_type $content_security_policy_inline { ~*text/(html|javascript)|application/pdf|xml "default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'"; } -# --- Proxied third-party apps ------------------------------------------- -# These two are shipped REPORT-ONLY (see the *-report-only.conf includes). -# Both are third-party SPAs whose runtime behaviour cannot be exercised from -# the shell -- no browser here -- and a wrong directive fails SILENTLY: video -# stops playing, or every article image disappears, with only a console -# message. Report-Only gives the visibility with zero outage risk. Promote to -# enforcing after checking a real browser console. +# --- Proxied third-party app --------------------------------------------- +# Shipped Report-Only first, then PROMOTED TO ENFORCING 2026-08-03 after a real +# browser validated it: homepage, VOD playback + seek on two videos, channel +# page, and a 39-result search were all clean, and every host contacted was +# already in the policy (self, pipedapi, pipedproxy, blob:). +# +# The check was proven meaningful before being believed: a deliberately +# disallowed image was injected and fired a securitypolicyviolation with +# disposition "report", confirming the header was live rather than absent. +# +# (A FreshRSS policy lived here too. It was removed -- rss.krz.sh is routed +# tunnel-direct to the container and never transits nginx, so the header never +# reached a browser. FreshRSS enforces its own, correctly RSS-aware, CSP.) # piped.krz.sh. Derived from evidence, not guesswork: # - connect-src: BACKEND_HOSTNAME=pipedapi.krz.sh (container env). @@ -121,15 +127,6 @@ map $sent_http_content_type $csp_piped { ~*text/(html|javascript)|application/pdf|xml "default-src 'self'; connect-src 'self' https://pipedapi.krz.sh; img-src 'self' https://pipedproxy.krz.sh data: blob:; media-src 'self' https://pipedproxy.krz.sh blob:; script-src 'self' 'unsafe-inline' data:; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:; font-src 'self' data:; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'"; } -# rss.zerolabs.sh (FreshRSS). Operator-only behind Cloudflare Access, so the -# T5 stake is low; the risk is breakage. An RSS reader renders arbitrary feed -# HTML, so img/media must allow remote hosts or every article image dies. -# That remote fetching is inherent to the app and is controlled by FreshRSS's -# own "load remote images" setting, not by CSP. -map $sent_http_content_type $csp_freshrss { - ~*text/(html|javascript)|application/pdf|xml "default-src 'self'; img-src 'self' https: data:; media-src 'self' https:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; frame-src https:; base-uri 'none'; form-action 'self'; frame-ancestors 'self'; object-src 'none'"; -} - # Permissions-Policy variant for media apps. The shared policy sets # `fullscreen=()`, which is exactly why piped.conf excludes basic.conf -- it # would stop videos going fullscreen. This keeps every other restriction and diff --git a/linux/nginx/etc/nginx/custom.d/security/content-security-policy-freshrss-report-only.conf b/linux/nginx/etc/nginx/custom.d/security/content-security-policy-freshrss-report-only.conf deleted file mode 100644 index bff2fd5..0000000 --- a/linux/nginx/etc/nginx/custom.d/security/content-security-policy-freshrss-report-only.conf +++ /dev/null @@ -1,4 +0,0 @@ -# rss.zerolabs.sh -- REPORT-ONLY. See the note in content_type_maps.conf: -# the authenticated reading view cannot be exercised from the shell, and a -# wrong img-src silently removes every article image. -add_header Content-Security-Policy-Report-Only $csp_freshrss always; diff --git a/linux/nginx/etc/nginx/custom.d/security/content-security-policy-piped-report-only.conf b/linux/nginx/etc/nginx/custom.d/security/content-security-policy-piped-report-only.conf index 5c76cd5..24917aa 100644 --- a/linux/nginx/etc/nginx/custom.d/security/content-security-policy-piped-report-only.conf +++ b/linux/nginx/etc/nginx/custom.d/security/content-security-policy-piped-report-only.conf @@ -2,4 +2,4 @@ # nothing is blocked. Promote to enforcing (rename the header to # Content-Security-Policy) only after loading a video, seeking, going # fullscreen and opening a channel page with a real browser console open. -add_header Content-Security-Policy-Report-Only $csp_piped always; +add_header Content-Security-Policy $csp_piped always; |
