diff --git a/packages/core/src/__tests__/encryption-key-validation.test.ts b/packages/core/src/__tests__/encryption-key-validation.test.ts index 228a4c13e..02e4c773e 100644 --- a/packages/core/src/__tests__/encryption-key-validation.test.ts +++ b/packages/core/src/__tests__/encryption-key-validation.test.ts @@ -42,6 +42,15 @@ describe("ENCRYPTION_KEY validation", () => { expect(decrypt(enc)).toBe("base64 secret"); }); + test("valid 32-byte URL-safe base64 key round-trips encrypt/decrypt", () => { + // Historical keys were sometimes generated with `openssl rand -base64 32 | + // tr +/ -_` and stored in URL-safe form (alphabet [A-Za-z0-9_-], no + // padding). Same 32 bytes — must be accepted. + process.env.ENCRYPTION_KEY = Buffer.alloc(32, 99).toString("base64url"); + const enc = encrypt("urlsafe secret"); + expect(decrypt(enc)).toBe("urlsafe secret"); + }); + test("valid 64-char hex key round-trips encrypt/decrypt", () => { process.env.ENCRYPTION_KEY = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; diff --git a/packages/core/src/utils/encryption.ts b/packages/core/src/utils/encryption.ts index ecb2a04cd..d1ab5ad56 100644 --- a/packages/core/src/utils/encryption.ts +++ b/packages/core/src/utils/encryption.ts @@ -34,6 +34,21 @@ function getEncryptionKey(): Buffer { } } + // Try as URL-safe base64 (alphabet [A-Za-z0-9_-], no padding). Historically + // some keys were generated as `openssl rand -base64 32 | tr +/ -_` and stored + // in this form; same 32 bytes, just a different alphabet. Apply the same + // round-trip check so typos still get rejected. + if (/^[A-Za-z0-9_-]+$/.test(key)) { + const urlsafeBuffer = Buffer.from(key, "base64url"); + if ( + urlsafeBuffer.length === 32 && + urlsafeBuffer.toString("base64url") === key + ) { + cachedKey = urlsafeBuffer; + return urlsafeBuffer; + } + } + // Try as hex (must be exactly 64 hex characters for 32 bytes), again // verifying the round-trip so partially-valid input is rejected. if (/^[0-9a-fA-F]+$/.test(key) && key.length % 2 === 0) {