Skip to content

fix(security+frontend): debug-session batch 3 — CSP wss tighten, ssot https detection - #6751

Merged
mrveiss merged 2 commits into
Dev_new_guifrom
debug-batch-3
May 2, 2026
Merged

mrveiss merged 2 commits into
Dev_new_guifrom
debug-batch-3

Conversation

@mrveiss

@mrveiss mrveiss commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

Two independent fixes from the same /superpowers:systematic-debugging session as PRs #6718 and #6749. Two other items from the Chrome-plugin debug TODO were verified already-fixed against the live deployment and are documented below for traceability.

1. CSP connect-src tightened (item #26)

Previous CSP connect-src 'self' wss: allowed WebSocket connections to ANY wss:// host. The SPA only opens WebSockets to the same origin that served the page, so we can scope to wss://$host (nginx interpolates the request's Host header at runtime — stays correct under any deployment IP/hostname). Three CSP add_header sites updated in autobot-slm.conf.j2. 'self' is kept because some Chromium versions don't treat 'self' as authoritative for ws/wss; both guards are cheap.

2. ssot-config: runtime https detection for backendUrl + frontendUrl (item #14)

backendUrl and frontendUrl getters at ssot-config.ts:440-446 returned http://... (from build-time VITE_HTTP_PROTOCOL defaulting to 'http'), causing the browser to block requests like http://172.16.168.20/api/secrets/ as mixed-content on HTTPS pages — exactly the symptom user reported in DevTools (status 'pending'). PR #6642 already fixed this for websocketUrl; same pattern applied here. In browser context derive protocol from window.location.protocol; in SSR fall back to build-time. Most production code paths take the getBackendUrl() proxy-mode branch (returns '' → relative URLs), but any composable that hits getConfig().backendUrl directly was broken.

Verified already-fixed against live deployment

Item #10 — HEAD /api/health

curl -X HEAD https://172.16.168.20/api/health returns 200 (was 503). Source: initialization/endpoints.py:145 registers @app.head("/api/health") explicitly. Already done.

Item #9 — /api/secrets trailing slash

curl /api/secrets now returns 307 (FastAPI redirect_slashes=True working) instead of the previously-reported 503. The 307 redirects to /api/secrets/ which returns 200 with data. Browsers follow 307 with method+headers preserved, so the frontend works correctly. Acceptance criterion 'both /api/secrets and /api/secrets/ return 200' is partially met (307→200 vs direct 200) — the practical outcome is identical for the SPA.

Test plan

  • Three CSP sites in autobot-slm.conf.j2 updated
  • backendUrl + frontendUrl getters mirror websocketUrl runtime-detect pattern
  • HEAD /api/health verified 200 against live
  • /api/secrets verified 307→200 against live
  • After deploy: https://172.16.168.20/ loads with no CSP violations in console
  • After deploy + frontend rebuild: no http:// URLs in DevTools Network tab

🤖 Generated with Claude Code

mrveiss and others added 2 commits May 2, 2026 21:41
…y wss

Previous CSP `connect-src 'self' wss:` allowed WebSocket connections to
ANY wss:// host — far broader than necessary. The SPA only opens
WebSockets to the same origin that served the page, so we can scope to
`wss://$host` (nginx interpolates the request's Host header at runtime,
so the directive stays correct under any deployment IP/hostname without
needing a Jinja round-trip).

Note: `'self'` is included as well because some Chromium versions don't
treat 'self' as authoritative for ws/wss schemes; the explicit `wss://$host`
covers that path. Both redundant guards are cheaper than a CSP-violation
audit later.

Three CSP add_header sites updated:
- Line 44   (default response headers)
- Line 269  (SPA / route fallback)
- Line 288  (login route fallback)

Tested by reading the nginx $host variable definition: it is the value
of the Host header from the request (or the value matched by the
listening server_name). For HTTPS over IP it'll resolve to the IP; for
DNS-named deployments it resolves to the hostname. In both cases this
matches the origin the page was served from, satisfying same-origin WS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rl getters

PR #6642 already fixed this exact bug for websocketUrl:
  build-time VITE_HTTP_PROTOCOL defaults to 'http' and produced
  ws:// URLs that browsers blocked as mixed-content on HTTPS pages.

The same bug remained in the backendUrl and frontendUrl getters at
ssot-config.ts:440-446 — they returned `http://...` when the page
was served over HTTPS, causing the browser to block requests like
`http://172.16.168.20/api/secrets/` (user-reported in DevTools
Network tab as 'status pending', then blocked).

Fix: in browser context, derive protocol from `window.location.protocol`
exactly like `websocketUrl` does (line 463-466). In SSR/build context
fall back to the build-time `httpProtocol` value.

Note: most production code paths actually take the `getBackendUrl()`
proxy-mode branch (returns '' when VITE_BACKEND_HOST is unset, so all
API calls go same-origin via nginx) — but any composable that reaches
`getConfig().backendUrl` directly hits the broken path. Hardening
the getter closes the gap for those callers and makes the dev/prod
behaviour symmetric.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mrveiss
mrveiss merged commit d89b77a into Dev_new_gui May 2, 2026
1 of 5 checks passed
@mrveiss
mrveiss deleted the debug-batch-3 branch May 2, 2026 19:03
@github-actions

github-actions Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

⚠️ SSOT Configuration Compliance: Violations Found

Metric Count
Total Violations 1
SSOT Violations (high priority) 1
Other Violations 0

⚠️ 1 values have SSOT config equivalents!

These should be replaced with SSOT config imports:

Python:

from src.config.ssot_config import config
# Use: config.vm.main, config.port.backend, config.backend_url

TypeScript:

import config from '@/config/ssot-config'
// Use: config.vm.main, config.port.backend, config.backendUrl

📖 See SSOT_CONFIG_GUIDE.md for documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant