Files
diegosouzapw--omniroute/tests/unit/decrypt-stale-key-hint-6148.test.ts
2026-07-13 13:39:12 +08:00

92 lines
3.6 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import path from "node:path";
import { pathToFileURL } from "node:url";
// #6148 — A decryption failure caused by a stale/changed STORAGE_ENCRYPTION_KEY
// must surface as a clear, specific error (HTTP 424, type
// "storage_encryption_stale") instead of the misleading "Auth failed: 401" that
// resulted from coercing the null credential to "" and sending an empty Bearer
// token upstream.
const ORIGINAL_STORAGE_KEY = process.env.STORAGE_ENCRYPTION_KEY;
// Cache-busted fresh import so the encryption module re-derives its key from the
// current STORAGE_ENCRYPTION_KEY (module-level key cache would otherwise persist).
async function importFresh(modulePath: string) {
const url = pathToFileURL(path.resolve(modulePath)).href;
return import(`${url}?test=${Date.now()}-${Math.random().toString(16).slice(2)}`);
}
test.after(() => {
if (ORIGINAL_STORAGE_KEY === undefined) {
delete process.env.STORAGE_ENCRYPTION_KEY;
} else {
process.env.STORAGE_ENCRYPTION_KEY = ORIGINAL_STORAGE_KEY;
}
});
test("decryptConnectionFields flags a credential that no longer decrypts (#6148)", async () => {
// 1. Encrypt an apiKey under key A.
process.env.STORAGE_ENCRYPTION_KEY = "stale-key-6148-A";
const encA = await importFresh("src/lib/db/encryption.ts");
const ciphertext = encA.encrypt("sk-real-secret-key");
assert.match(ciphertext, /^enc:v1:/, "expected a real enc:v1 ciphertext");
// 2. Read it back under a DIFFERENT key B (simulating a changed key).
process.env.STORAGE_ENCRYPTION_KEY = "stale-key-6148-B";
const encB = await importFresh("src/lib/db/encryption.ts");
const decrypted = encB.decryptConnectionFields({
provider: "openai",
apiKey: ciphertext,
});
// The credential fails to decrypt (null) but the guard flag distinguishes this
// from a genuinely empty credential.
assert.equal(decrypted.apiKey, null, "stale key must decrypt to null");
assert.equal(
decrypted.credentialDecryptFailed,
true,
"undecryptable ciphertext must set credentialDecryptFailed"
);
assert.equal(encB.looksEncrypted(ciphertext), true);
});
test("a genuinely empty credential is NOT flagged as decrypt failure (#6148)", async () => {
process.env.STORAGE_ENCRYPTION_KEY = "stale-key-6148-empty";
const enc = await importFresh("src/lib/db/encryption.ts");
const decrypted = enc.decryptConnectionFields({ provider: "openai", apiKey: null });
assert.notEqual(decrypted.credentialDecryptFailed, true, "empty credential must not flag");
});
test("models route guard returns HTTP 424 storage_encryption_stale (#6148)", async () => {
const guard = await importFresh(
"src/app/api/providers/[id]/models/staleEncryptionGuard.ts"
);
// Connection flagged by decryptConnectionFields (stale key).
const staleResponse = guard.buildStaleEncryptionKeyResponse({
provider: "openai",
apiKey: null,
credentialDecryptFailed: true,
});
assert.ok(staleResponse, "guard must return a response for a stale connection");
assert.equal(staleResponse.status, 424, "must be HTTP 424, not an upstream 401");
const body = await staleResponse.json();
assert.equal(body.error.type, "storage_encryption_stale");
assert.match(body.error.message, /decrypt/i);
// Rule #12 — no stack trace leakage in the error body.
assert.equal(body.error.message.includes("at /"), false);
// A healthy connection must NOT be short-circuited.
const okResponse = guard.buildStaleEncryptionKeyResponse({
provider: "openai",
apiKey: "sk-real-secret-key",
});
assert.equal(okResponse, null, "healthy connection must proceed (null)");
});