From 3489610cb8d9334198daffd9bf86ae488061f4b8 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Tue, 1 Sep 2026 16:23:40 +0200 Subject: [PATCH] fix(analytics): warn about the CSP allowlist on every tracker provider A self-hosted Umami/Rybbit domain configured in Settings -> Analytics is always blocked by the static script-src allowlist, silently, with only a console error. The amber CSP warning that explains this already existed but was rendered only inside the "custom" provider panel -- not on the two providers where an admin actually types a self-hosted URL. Extract it to a local CspWarning and render it in the Umami and Rybbit panels too. Both translation keys already exist in en.json/de.json. Interpretation: the dynamic-CSP option was investigated and rejected as not reachable for the header that actually governs these documents. In the Docker deployment nginx.conf:58 does `proxy_hide_header Content-Security-Policy`, so helmet's CSP and the res.setHeader CSP at server.js:445 are stripped before they leave the stack -- nginx's static server-level CSP is the only one the browser sees for the SPA documents the tracker is injected into. nginx.conf is COPYied verbatim by the Dockerfile (only index.html goes through envsubst), and the tracker URL lives in the DB rather than the environment, so making it reflect the setting would need start-time templating plus a DB read. The CSP itself therefore still has to be edited by hand; the warning now says so where the admin can see it. Refs testplan REPORT.md #18 (Part 3, B.02). --- .../features/settings/tabs/AnalyticsTab.tsx | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/frontend/src/features/settings/tabs/AnalyticsTab.tsx b/frontend/src/features/settings/tabs/AnalyticsTab.tsx index e266bba4..6b178977 100644 --- a/frontend/src/features/settings/tabs/AnalyticsTab.tsx +++ b/frontend/src/features/settings/tabs/AnalyticsTab.tsx @@ -15,6 +15,35 @@ interface AnalyticsTabProps { const PROVIDER_OPTIONS: TrackerProvider[] = ['none', 'umami', 'rybbit', 'custom']; +/** + * The shipped CSP `script-src` is a static allowlist that no configured + * tracker domain is ever added to, so a self-hosted Umami/Rybbit instance is + * blocked by the browser with nothing but a console error to show for it. + * Shown for every provider that loads a script from another origin. + */ +const CspWarning: React.FC = () => { + const { t } = useTranslation(); + + return ( +
+
+ +
+

+ {t('settings.analytics.customCspWarning', 'Content-Security-Policy reminder')} +

+

+ {t( + 'settings.analytics.customCspWarningText', + 'PicPeak ships with a strict CSP (`script-src \'self\'`). If your tracker loads from another domain, add that domain to your reverse-proxy or nginx CSP config — otherwise the browser silently blocks the script.', + )} +

+
+
+
+ ); +}; + export const AnalyticsTab: React.FC = ({ analyticsSettings, setAnalyticsSettings, @@ -128,6 +157,8 @@ export const AnalyticsTab: React.FC = ({ )}

+ + )} @@ -191,6 +222,8 @@ export const AnalyticsTab: React.FC = ({ )}

+ + )} @@ -222,22 +255,7 @@ export const AnalyticsTab: React.FC = ({

-
-
- -
-

- {t('settings.analytics.customCspWarning', 'Content-Security-Policy reminder')} -

-

- {t( - 'settings.analytics.customCspWarningText', - 'PicPeak ships with a strict CSP (`script-src \'self\'`). If your tracker loads from another domain, add that domain to your reverse-proxy or nginx CSP config — otherwise the browser silently blocks the script.', - )} -

-
-
-
+ )}