mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: production CSP blocking PostHog/Sentry/Scalar and silent failure hardening
The production CSP had connect-src/script-src/font-src set to 'self' only, silently blocking all analytics and error reporting in production while working fine in dev (where CSP is not applied). CSP fixes: - Add PostHog ingest + assets origins to connect-src and script-src - Add Sentry ingest origin to connect-src - Add Scalar fonts origin to font-src for API docs pages - Extract CSP construction into testable buildCsp() function Silent failure hardening: - Settings/features stores now set loadError flag and allow retry on subsequent fetch() calls instead of permanently caching failed state - Analytics init no longer sets initialized=true before the try block, allowing retry on failure - Settings dialog Tools section disables save button when settings failed to load, preventing accidental config wipe - Branding logo storage moved from process.cwd() to FILES_STORAGE_PATH so logos persist across Docker container recreation Test coverage: - 16 CSP directive tests covering all external service domains - Store retry-on-error behavior tests for settings and features stores - Analytics init retry-after-failure test
This commit is contained in:
@@ -2292,6 +2292,7 @@ function AuditLogSection() {
|
||||
function ToolsSection() {
|
||||
const [disabledTools, setDisabledTools] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadFailed, setLoadFailed] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [showRestartBanner, setShowRestartBanner] = useState(false);
|
||||
@@ -2302,8 +2303,9 @@ function ToolsSection() {
|
||||
setDisabledTools(
|
||||
data.settings.disabledTools ? JSON.parse(data.settings.disabledTools) : [],
|
||||
);
|
||||
setLoadFailed(false);
|
||||
})
|
||||
.catch(() => {})
|
||||
.catch(() => setLoadFailed(true))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
@@ -2424,11 +2426,17 @@ function ToolsSection() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{loadFailed && (
|
||||
<div className="px-4 py-3 rounded-lg border border-red-500/30 bg-red-500/10 text-sm text-red-700 dark:text-red-400">
|
||||
Failed to load tool settings. Saving is disabled to prevent data loss.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-3 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
disabled={saving || loadFailed}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{saving && <Loader2 className="h-3.5 w-3.5 animate-spin" />}
|
||||
|
||||
Reference in New Issue
Block a user