Problem
backend/src/lib/userApiKeys.ts:40–47:
function encryptionKey(): Buffer {
const secret =
process.env.USER_API_KEYS_ENCRYPTION_SECRET ||
process.env.API_KEYS_ENCRYPTION_SECRET ||
process.env.SUPABASE_SECRET_KEY; // ← last-resort fallback
...
}
assertSecretIsolation() (PR #74) prevents the server from starting when secrets overlap — but it only fires at startup. The encryptionKey() function itself still accepts the Supabase service key as a fallback. If the isolation check is ever not wired (e.g., a different entrypoint, a test environment), user API keys would be encrypted under the same secret used for JWT verification.
Impact
A single SUPABASE_SECRET_KEY leak would expose all stored user LLM API keys. PR #74 added a startup check but did not remove the root cause.
Fix
Remove the SUPABASE_SECRET_KEY fallback from encryptionKey(). Require USER_API_KEYS_ENCRYPTION_SECRET unconditionally — throw new Error("USER_API_KEYS_ENCRYPTION_SECRET must be set") if absent.
Problem
backend/src/lib/userApiKeys.ts:40–47:assertSecretIsolation()(PR #74) prevents the server from starting when secrets overlap — but it only fires at startup. TheencryptionKey()function itself still accepts the Supabase service key as a fallback. If the isolation check is ever not wired (e.g., a different entrypoint, a test environment), user API keys would be encrypted under the same secret used for JWT verification.Impact
A single
SUPABASE_SECRET_KEYleak would expose all stored user LLM API keys. PR #74 added a startup check but did not remove the root cause.Fix
Remove the
SUPABASE_SECRET_KEYfallback fromencryptionKey(). RequireUSER_API_KEYS_ENCRYPTION_SECRETunconditionally — thrownew Error("USER_API_KEYS_ENCRYPTION_SECRET must be set")if absent.