From dfd1911be16a31b000de7397bd6dc84e6ef10f57 Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai <157192462+saurabhhhcodes@users.noreply.github.com> Date: Wed, 20 May 2026 23:37:55 +0530 Subject: [PATCH 1/2] fix: validate encrypted token payload sizes --- src/lib/crypto.ts | 25 ++++++++++++++++++- test/crypto.test.js | 61 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 test/crypto.test.js diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index d1339d3a..ca892095 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -9,6 +9,10 @@ const IV_LENGTH = 12; const AUTH_TAG_LENGTH = 16; const KEY_ERROR_MESSAGE = "ENCRYPTION_KEY env var must be a 32-byte hex string"; +const IV_ERROR_MESSAGE = + "Encrypted token IV must be a 12-byte hex string"; +const PAYLOAD_ERROR_MESSAGE = + "Encrypted token payload must include at least a 16-byte auth tag"; function getEncryptionKey(): Buffer { const key = process.env.ENCRYPTION_KEY; @@ -26,6 +30,24 @@ function getEncryptionKey(): Buffer { return keyBuffer; } +function assertFixedHex(value: string, expectedChars: number, message: string) { + if (!new RegExp(`^[0-9a-fA-F]{${expectedChars}}$`).test(value)) { + throw new Error(message); + } +} + +function validateEncryptedTokenPayload(encrypted: string, iv: string) { + assertFixedHex(iv, IV_LENGTH * 2, IV_ERROR_MESSAGE); + + if ( + encrypted.length < AUTH_TAG_LENGTH * 2 || + encrypted.length % 2 !== 0 || + !/^[0-9a-fA-F]+$/.test(encrypted) + ) { + throw new Error(PAYLOAD_ERROR_MESSAGE); + } +} + export function encryptToken(plaintext: string): { encrypted: string; iv: string; @@ -53,6 +75,7 @@ export function decryptToken( ): string | null { try { const key = getEncryptionKey(); + validateEncryptedTokenPayload(encrypted, iv); const encryptedBuffer = Buffer.from(encrypted, "hex"); const ivBuffer = Buffer.from(iv, "hex"); @@ -77,4 +100,4 @@ export function decryptToken( console.error("Token decryption failed:", error); return null; } -} \ No newline at end of file +} diff --git a/test/crypto.test.js b/test/crypto.test.js new file mode 100644 index 00000000..3c10a70c --- /dev/null +++ b/test/crypto.test.js @@ -0,0 +1,61 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const test = require("node:test"); +const ts = require("typescript"); + +function loadCryptoModule() { + const sourcePath = path.join(__dirname, "..", "src", "lib", "crypto.ts"); + const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "devtrack-crypto-")); + const outPath = path.join(outDir, "crypto.cjs"); + const source = fs.readFileSync(sourcePath, "utf8"); + const output = ts.transpileModule(source, { + compilerOptions: { + esModuleInterop: true, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2020, + }, + }).outputText; + + fs.writeFileSync(outPath, output); + return require(outPath); +} + +test("decryptToken rejects malformed IV before decipher creation", () => { + const { decryptToken } = loadCryptoModule(); + process.env.ENCRYPTION_KEY = "a".repeat(64); + const originalError = console.error; + console.error = () => {}; + + try { + assert.equal(decryptToken("0".repeat(32), "abcd"), null); + } finally { + console.error = originalError; + } +}); + +test("decryptToken rejects payloads shorter than the auth tag", () => { + const { decryptToken } = loadCryptoModule(); + process.env.ENCRYPTION_KEY = "b".repeat(64); + const originalError = console.error; + console.error = () => {}; + + try { + assert.equal(decryptToken("0".repeat(30), "1".repeat(24)), null); + } finally { + console.error = originalError; + } +}); + +test("decryptToken still decrypts valid encrypted tokens", () => { + const { decryptToken, encryptToken } = loadCryptoModule(); + process.env.ENCRYPTION_KEY = "c".repeat(64); + + const encrypted = encryptToken("github-token-123"); + + assert.equal( + decryptToken(encrypted.encrypted, encrypted.iv), + "github-token-123" + ); +}); From 4463db909f6bb79cd448323cc00e01840faba0e6 Mon Sep 17 00:00:00 2001 From: saurabhhhcodes <157192462+saurabhhhcodes@users.noreply.github.com> Date: Thu, 21 May 2026 13:15:01 +0530 Subject: [PATCH 2/2] test: align Playwright session token encoding --- e2e/dashboard-widgets.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/e2e/dashboard-widgets.spec.js b/e2e/dashboard-widgets.spec.js index dd7a2ce8..7a9ffcf6 100644 --- a/e2e/dashboard-widgets.spec.js +++ b/e2e/dashboard-widgets.spec.js @@ -9,7 +9,6 @@ test.beforeEach(async ({ page }) => { name: "next-auth.session-token", value: await encode({ secret: authSecret, - salt: "next-auth.session-token", token: { name: "Playwright User", email: "playwright@example.com",