Encrypt connection credentials with an owner-bound AES-GCM envelope

Adds a v2 credential envelope next to the v1 tool-secret helpers:
AES-256-GCM, a master key derived once per process from
ENCRYPTION_SECRET_KEY, and a per-record key from HKDF over the owner's
id, which is also the associated data, so a blob moved onto another
user's row does not decrypt. The envelope names its key, so
ENCRYPTION_SECRET_KEY_PREVIOUS keeps old rows readable during a
rotation.

Log redaction now also covers token_info, tokens and client_info, and
the API warns at startup when the public default key is in use.
This commit is contained in:
arc53-machine committed 2026-09-28 17:03:02 +01:00
1 parent 45c4d928c4
commit 667d4bbab0
6 files changed
+275 -3

No files matched your search

+5 -1
View File
@@ -33,13 +33,17 @@ _SECRET_SUBSTRINGS = (
"credential",
"authorization",
"bearer",
# Connection secrets: an OAuth token_info blob, MCP token and client
# registration dicts (``client_secret`` is covered by ``secret``).
"token_info",
"client_info",
)
def is_secret_key(key: str) -> bool:
"""True when ``key`` names a credential that must not be persisted/returned."""
k = key.lower()
if k == "token":
if k in ("token", "tokens"):
return True
return any(s in k for s in _SECRET_SUBSTRINGS)