fix: prevent caching of API key handoff

This commit is contained in:
mARTin-B78 2026-09-04 08:52:46 +02:00
parent 3456b5a3b0
commit 1c0c985d34
2 changed files with 17 additions and 3 deletions

View File

@ -1,7 +1,7 @@
"""Settings, TTS routes, routing log, server logs, and voice design presets endpoints."""
from __future__ import annotations
from fastapi import APIRouter, HTTPException, Request
from fastapi import APIRouter, HTTPException, Request, Response
from core.config import _load_settings, _save_settings, _normalize_settings, _SETTINGS_KEYS, _ensure_external_api_key
from core.routing import _load_tts_routes, _save_tts_routes
@ -19,7 +19,7 @@ _SECRET_MASK = "__configured__"
@router.get("/api/settings")
async def get_settings(request: Request):
async def get_settings(request: Request, response: Response):
s = _load_settings()
# Lazily generated on first read, not at server startup — a fresh
# install shows a real usable key in Settings immediately without a
@ -46,15 +46,19 @@ async def get_settings(request: Request):
s[key] = _SECRET_MASK
else:
s.pop(key, None)
response.headers["Cache-Control"] = "no-store, no-cache, max-age=0"
response.headers["Pragma"] = "no-cache"
return s
@router.post("/api/settings/regenerate-api-key")
async def regenerate_api_key():
async def regenerate_api_key(response: Response):
import secrets
s = _load_settings()
s["external_api_key"] = secrets.token_urlsafe(32)
_save_settings(s)
response.headers["Cache-Control"] = "no-store, no-cache, max-age=0"
response.headers["Pragma"] = "no-cache"
return {"external_api_key": s["external_api_key"]}

View File

@ -79,6 +79,7 @@ class SecurityValidationTests(unittest.TestCase):
payload = response.json()
self.assertEqual(payload.get("llm_api_key"), "__configured__")
self.assertEqual(payload.get("engine_api_keys", {}).get("local"), "__configured__")
self.assertIn("no-store", response.headers.get("cache-control", ""))
self.assertNotIn("provider-secret", response.text)
self.assertNotIn("engine-secret", response.text)
@ -93,6 +94,15 @@ class SecurityValidationTests(unittest.TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(_load_settings().get("llm_api_key"), "keep-this-secret")
def test_explicit_external_key_regeneration_is_not_cacheable(self):
from fastapi.testclient import TestClient
from server import app
with TestClient(app) as client:
response = client.post("/api/settings/regenerate-api-key", headers={"origin": "http://testserver"})
self.assertEqual(response.status_code, 200)
self.assertTrue(response.json().get("external_api_key"))
self.assertIn("no-store", response.headers.get("cache-control", ""))
def test_fish_cache_eviction_is_bounded(self):
from pathlib import Path
from tempfile import TemporaryDirectory