soulsync/config
BoulderBadgeDad 8983299130 Security: stop GET /api/settings from shipping decrypted secrets to the browser
Found during the #832 audit: GET /api/settings returned dict(config_data) — and
config_data is DECRYPTED in memory — so every API key, OAuth secret, Plex/
Jellyfin token, and service password went to the browser in cleartext. Fernet
"encrypted at rest" protects a leaked DB file; it does nothing once the API
hands the plaintext to the client (devtools, HAR captures, an XSS, a screen
share, or a non-PIN'd LAN viewer).

Fix (centralized in ConfigManager):
- redacted_config() deep-copies config and replaces every _SENSITIVE_PATHS value
  that's actually set with REDACTED_SENTINEL; unset secrets stay empty so the UI
  still shows "not configured". Dict-valued secrets (tidal/qobuz OAuth sessions)
  collapse to the sentinel too. GET /api/settings now serves this copy.
- set() ignores a write of REDACTED_SENTINEL to a sensitive path, so the masked
  placeholder round-tripped by an unchanged settings form can never overwrite
  the real secret. A real value still saves; an empty value still clears.

Frontend: secret inputs are type=password, so the sentinel renders as dots
(looks like a saved secret). _wireRedactedSecrets() clears the mask on focus so
editing types fresh rather than onto the sentinel, and re-masks on blur if left
untouched — so an unchanged secret round-trips the sentinel (kept), an edited
one saves the new value, and a deliberately emptied one clears.

Tests: every sensitive path masks; unset stays empty; dict secrets mask; live
config not mutated; sentinel round-trip keeps the real secret; real value
overwrites; empty clears; sentinel on a non-secret path writes normally.
9 new tests; 518 config-touching tests pass (1 pre-existing soundcloud mock
failure, unrelated — fails identically on a clean tree).
2026-06-09 22:50:18 -07:00
..
config.example.json Add hi-res FLAC to CD quality downsampling in post-processing 2026-03-17 11:53:56 -07:00
settings.py Security: stop GET /api/settings from shipping decrypted secrets to the browser 2026-06-09 22:50:18 -07:00