[Security] JWT signing secret leaked via GET /api/settings to any authenticated user #70
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Critical | Category: Security
Found while verifying
architecture-reworkagainst a live dev server — confirmed present onmaintoo, unrelated to any architecture-review change.auth.pywrites the JWT signing secret directly to thesettingstable as_jwt_secretvia raw SQL (auth.py:66), bypassingsettings.py'sset_setting().get_public_user_settings()/get_public_settings()(settings.py) only redacted a fixed_SECRET_KEYSblocklist (scraper_dhl_api_key,ntfy_password,imap_password,mqtt_password) —_jwt_secretwas never added, so it passed through in plaintext toGET /api/settingsfor any authenticated user, including one authenticated with just an API key (not just admins). Anyone who read it could forge a valid JWT for any user, including admin — full authentication bypass.Fixed on
architecture-rework, commit133a832: excluded any underscore-prefixed key (the existing "internal, not user-facing" convention) from both public settings functions entirely, rather than only redacting a known-secrets blocklist. Protects any future internal key added the same way.Added
test_get_settings_never_exposes_internal_keys; confirmed it fails on the pre-fix code and passes after (verified by temporarily revertingsettings.py). Also confirmed live: the secret appeared in the raw HTTP response from a running dev server before the fix, and is gone after.Action needed after this branch merges to main and deploys: rotate the JWT secret in production (delete the
_jwt_secretrow from the productionsettingstable, or set theJWT_SECRETenv var, so a new one is generated) — any secret that was live before this fix should be treated as potentially compromised.Rotated in production 2026-07-05: deleted the
_jwt_secretrow from thesettingstable on the live DB and restarted the container. Confirmed the old row is gone; a new secret will be generated lazily on the next login/token operation (auth.py:_get_secret). All existing sessions are invalidated. Health check confirms the app came back up clean (v0.13.6). Closing.