🛡️ Sentinel: [CRITICAL/HIGH] Upgrade token encryption to AES-GCM (#117)

Upgraded `encodeAssetId` and `decodeAssetId` to use `aes-256-gcm` (Authenticated Encryption) instead of `aes-256-cbc`.
AES-CBC lacks authentication, leaving it potentially susceptible to padding oracle attacks or ciphertext manipulation.
The new token adds a `v2:` prefix which strictly enforces AES-GCM while supporting legacy fallback for cached urls.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
Ralksta
2026-04-21 22:51:39 +02:00
committed by GitHub
parent 13fcfe8436
commit 3bbec5a4ac
2 changed files with 45 additions and 15 deletions

View File

@@ -18,12 +18,11 @@ describe('tokens', () => {
});
describe('encodeAssetId', () => {
it('produces a non-empty base64url string', () => {
it('produces a non-empty string with v2: prefix', () => {
const token = encodeAssetId(SAMPLE_UUID);
expect(token).toBeTruthy();
expect(typeof token).toBe('string');
// base64url characters only
expect(token).toMatch(/^[A-Za-z0-9_-]+$/);
expect(token).toMatch(/^v2:[A-Za-z0-9_-]+$/);
});
it('is deterministic — same input yields same token', () => {
@@ -46,6 +45,20 @@ describe('tokens', () => {
expect(decoded).toBe(SAMPLE_UUID);
});
it('round-trips a legacy aes-256-cbc token', async () => {
// Manually encode using legacy method
const crypto = await import('crypto');
const authSecret = 'test-auth-secret-32-chars-long-min';
const key = crypto.createHash('sha256').update(authSecret).digest();
const iv = crypto.createHash('sha256').update(SAMPLE_UUID).digest().subarray(0, 16);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
const encrypted = Buffer.concat([cipher.update(SAMPLE_UUID, 'utf8'), cipher.final()]);
const legacyToken = Buffer.concat([iv, encrypted]).toString('base64url');
const decoded = decodeAssetId(legacyToken);
expect(decoded).toBe(SAMPLE_UUID);
});
it('returns null for a garbage string', () => {
expect(decodeAssetId('not-a-valid-token')).toBeNull();
});