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.',
- )}
-
-
-
-
+
)}