From 951914dd6929cc8ebfb4540e6edfbc72ff3a197b Mon Sep 17 00:00:00 2001 From: Barely Removable Date: Wed, 26 Aug 2026 05:11:28 -0700 Subject: [PATCH] Redact API keys from error technical-details disclosure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SABnzbd and Tautulli embed their API key in the request URL, so it was showing up verbatim in the raw exception text under "Technical details" on the dashboard — visible to anyone with the study-room password. Added a redact_secrets filter that strips apikey/token/ password query params before the raw string is rendered. Co-Authored-By: Claude Sonnet 5 --- app.py | 14 ++++++++++++++ templates/dashboard.html | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 85ff6b9..0d44548 100644 --- a/app.py +++ b/app.py @@ -66,7 +66,21 @@ def friendly_error(raw, service=""): return f"{name} couldn't be reached." +_SECRET_PARAM_RE = re.compile( + r"(?i)\b(apikey|api_key|token|password)=([^&\s'\")]+)" +) + + +def redact_secrets(raw): + """Strip API keys/tokens out of raw exception text before it's shown in + the UI. Some services (SABnzbd, Tautulli) embed the key in the request + URL, so it ends up verbatim in requests/urllib3 exception strings. + """ + return _SECRET_PARAM_RE.sub(r"\1=[redacted]", str(raw or "")) + + app.jinja_env.filters["friendly_error"] = friendly_error +app.jinja_env.filters["redact_secrets"] = redact_secrets app.config["SESSION_COOKIE_PATH"] = "/" app.config["SESSION_COOKIE_HTTPONLY"] = True app.config["SESSION_COOKIE_SAMESITE"] = "Lax" diff --git a/templates/dashboard.html b/templates/dashboard.html index e52ba04..62e290e 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -980,7 +980,7 @@ {% if raw %}
Technical details -
{{ raw }}
+
{{ raw | redact_secrets }}
{% endif %}