From def490ceca2697bc20c5069467164765ebb3330d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 20:26:53 +0000 Subject: [PATCH] Preview: GCM-Tag-Laenge fest, Inhalt validiert Co-Authored-By: Claude Sonnet 5.5 --- server/services/previewEnv.js | 12 +++++++++--- test/previewEnv.test.js | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/server/services/previewEnv.js b/server/services/previewEnv.js index 805cc40..e305113 100644 --- a/server/services/previewEnv.js +++ b/server/services/previewEnv.js @@ -15,7 +15,7 @@ export function envKey() { export function encryptEnv(obj, key) { const iv = crypto.randomBytes(12) - const cipher = crypto.createCipheriv('aes-256-gcm', key, iv) + const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: 16 }) const ct = Buffer.concat([cipher.update(JSON.stringify(obj || {}), 'utf8'), cipher.final()]) const tag = cipher.getAuthTag() return ['v1', iv, tag, ct].map((p) => (typeof p === 'string' ? p : p.toString('base64'))).join(':') @@ -26,10 +26,16 @@ export function decryptEnv(str, key) { const parts = String(str).split(':') if (parts.length !== 4 || parts[0] !== 'v1') throw new Error('Ungueltiges Variablenformat') const [, iv, tag, ct] = parts.map((p, i) => (i === 0 ? p : Buffer.from(p, 'base64'))) - const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv) + if (iv.length !== 12 || tag.length !== 16) throw new Error('Ungueltiges Variablenformat') + const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv, { authTagLength: 16 }) decipher.setAuthTag(tag) const plain = Buffer.concat([decipher.update(ct), decipher.final()]).toString('utf8') - return JSON.parse(plain) + const obj = JSON.parse(plain) + if (!obj || typeof obj !== 'object' || Array.isArray(obj) + || !Object.values(obj).every((v) => typeof v === 'string')) { + throw new Error('Ungueltiges Variablenformat') + } + return obj } export function envNames(str, key) { diff --git a/test/previewEnv.test.js b/test/previewEnv.test.js index 86063c2..d7b827c 100644 --- a/test/previewEnv.test.js +++ b/test/previewEnv.test.js @@ -79,3 +79,25 @@ test('maskLog', () => { assert.equal(maskLog('', env), '') assert.equal(maskLog('text', {}), 'text') }) + +test('gekuerzter Tag, falsche IV-Laenge, manipulierter Ciphertext => Fehler', () => { + const p = encryptEnv({ A: 'geheim' }, key).split(':') + const b = (i) => Buffer.from(p[i], 'base64') + const join = (x) => x.join(':') + const short = [...p]; short[2] = b(2).subarray(0, 8).toString('base64') + assert.throws(() => decryptEnv(join(short), key)) + const iv = [...p]; iv[1] = b(1).subarray(0, 8).toString('base64') + assert.throws(() => decryptEnv(join(iv), key)) + const ct = [...p]; const c = b(3); c[0] ^= 1; ct[3] = c.toString('base64') + assert.throws(() => decryptEnv(join(ct), key)) +}) + +test('entschluesselter Inhalt muss Objekt mit String-Werten sein', () => { + for (const bad of [['x'], 'text', { A: 1 }, null]) { + const iv = crypto.randomBytes(12) + const c = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: 16 }) + const ct = Buffer.concat([c.update(JSON.stringify(bad), 'utf8'), c.final()]) + const s = ['v1', iv, c.getAuthTag(), ct].map((x) => (typeof x === 'string' ? x : x.toString('base64'))).join(':') + assert.throws(() => decryptEnv(s, key)) + } +})