Skip to content

Commit 2c05216

Browse files
committed
fix: correct base64 fallback decoding and use crypto random for padding
1 parent 4acea69 commit 2c05216

3 files changed

Lines changed: 38 additions & 4 deletions

File tree

packages/builder/lib/base64.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const base64UrlEncode = (
1717

1818
// Use environment-specific encoding
1919
let base64: string;
20-
if (typeof globalThis !== 'undefined' && 'btoa' in globalThis) {
20+
if (typeof globalThis !== 'undefined' && typeof globalThis.btoa === 'function') {
2121
// Browser environment
2222
base64 = globalThis.btoa(text);
2323
} else {
@@ -61,7 +61,7 @@ export const base64UrlDecode = (input: string): ArrayBuffer => {
6161
const base64 = base64UrlDecodeString(input);
6262

6363
// Decode based on environment
64-
if (typeof globalThis !== 'undefined' && 'atob' in globalThis) {
64+
if (typeof globalThis !== 'undefined' && typeof globalThis.atob === 'function') {
6565
// Browser environment
6666
const binaryString = globalThis.atob(base64);
6767
const bytes = new Uint8Array(binaryString.length);
@@ -71,5 +71,9 @@ export const base64UrlDecode = (input: string): ArrayBuffer => {
7171
return bytes.buffer;
7272
}
7373
// Node.js environment
74-
return Buffer.from(base64, 'base64').buffer;
74+
const buffer = Buffer.from(base64, 'base64');
75+
return buffer.buffer.slice(
76+
buffer.byteOffset,
77+
buffer.byteOffset + buffer.byteLength,
78+
);
7579
};

packages/builder/lib/payload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ const padPayload = (payload: Uint8Array): Uint8Array<ArrayBuffer> => {
216216
const maxRandomPadding = Math.min(100, availableSpace);
217217
const paddingSize =
218218
maxRandomPadding > 0
219-
? Math.floor(Math.random() * (maxRandomPadding + 1))
219+
? crypto.getRandomValues(new Uint8Array(1))[0] % (maxRandomPadding + 1)
220220
: 0;
221221

222222
const paddingArray = new ArrayBuffer(

packages/builder/test/unit.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { describe, expect, test } from 'vitest';
2+
import { base64UrlDecode } from '../lib/base64.js';
23
import { buildPushHTTPRequest } from '../lib/main.js';
34
import type {
45
BuilderOptions,
@@ -217,6 +218,35 @@ describe('Subscription Keys Validation', () => {
217218
});
218219
});
219220

221+
// ============================================================================
222+
// Base64 Decoding Tests
223+
// ============================================================================
224+
describe('Base64 URL Decoding', () => {
225+
test('decodes to exact byte length in default runtime path', () => {
226+
const decoded = new Uint8Array(base64UrlDecode('AQ'));
227+
228+
expect(decoded).toEqual(new Uint8Array([1]));
229+
expect(decoded.byteLength).toBe(1);
230+
});
231+
232+
test('decodes to exact byte length in Buffer fallback path', () => {
233+
const originalAtob = globalThis.atob;
234+
// Force Node fallback path to verify sliced ArrayBuffer behavior.
235+
// `Buffer#buffer` alone would expose backing store, not exact bytes.
236+
// @ts-expect-error test override
237+
globalThis.atob = undefined;
238+
239+
try {
240+
const decoded = new Uint8Array(base64UrlDecode('AQ'));
241+
242+
expect(decoded).toEqual(new Uint8Array([1]));
243+
expect(decoded.byteLength).toBe(1);
244+
} finally {
245+
globalThis.atob = originalAtob;
246+
}
247+
});
248+
});
249+
220250
// ============================================================================
221251
// TTL Validation Tests
222252
// ============================================================================

0 commit comments

Comments
 (0)