fix: encrypt OIDC client secret at rest using AES-256-GCM

The oidc_client_secret was written to app_settings as plaintext,
unlike Maps and OpenWeather API keys which are protected with
apiKeyCrypto. An attacker with read access to the SQLite file
(e.g. via a backup download) could obtain the secret and
impersonate the application with the identity provider.

- Encrypt on write in PUT /api/admin/oidc via maybe_encrypt_api_key
- Decrypt on read in GET /api/admin/oidc and in getOidcConfig()
  (oidc.ts) before passing the secret to the OIDC client library
- Add a startup migration that encrypts any existing plaintext value
  already present in the database
This commit is contained in:
jubnl
2026-04-01 04:27:50 +02:00
parent 701a8ab03a
commit bba50f038b
3 changed files with 13 additions and 3 deletions
+3 -2
View File
@@ -9,6 +9,7 @@ import { AuthRequest, User, Addon } from '../types';
import { writeAudit, getClientIp, logInfo } from '../services/auditLog';
import { getAllPermissions, savePermissions, PERMISSION_ACTIONS } from '../services/permissions';
import { revokeUserSessions } from '../mcp';
import { maybe_encrypt_api_key, decrypt_api_key } from '../services/apiKeyCrypto';
const router = express.Router();
@@ -232,7 +233,7 @@ router.get('/audit-log', (req: Request, res: Response) => {
router.get('/oidc', (_req: Request, res: Response) => {
const get = (key: string) => (db.prepare("SELECT value FROM app_settings WHERE key = ?").get(key) as { value: string } | undefined)?.value || '';
const secret = get('oidc_client_secret');
const secret = decrypt_api_key(get('oidc_client_secret'));
res.json({
issuer: get('oidc_issuer'),
client_id: get('oidc_client_id'),
@@ -247,7 +248,7 @@ router.put('/oidc', (req: Request, res: Response) => {
const set = (key: string, val: string) => db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)").run(key, val || '');
set('oidc_issuer', issuer);
set('oidc_client_id', client_id);
if (client_secret !== undefined) set('oidc_client_secret', client_secret);
if (client_secret !== undefined) set('oidc_client_secret', maybe_encrypt_api_key(client_secret) ?? '');
set('oidc_display_name', display_name);
set('oidc_only', oidc_only ? 'true' : 'false');
const authReq = req as AuthRequest;