From 1ec608d873f2dbea352c6b1bdaa1033c2ed5f980 Mon Sep 17 00:00:00 2001 From: Dane Knecht Date: Sat, 21 Feb 2026 07:39:11 -0700 Subject: [PATCH] fix(node): fix five trivial bugs across crypto and inspect - Fix isRpcWildcardType checking RpcPromise twice instead of RpcProperty - Fix randomInt(n, n) infinite loop: reject min >= max, not just min > max - Fix randomFillSync default size not accounting for offset - Fix publicEncrypt/publicDecrypt error message saying 'privateKey' - Fix kState Symbol description ('kFinalized' -> 'kState') - Add tests for randomInt and randomFillSync edge cases --- src/node/internal/crypto_cipher.ts | 4 +-- src/node/internal/crypto_random.ts | 6 ++-- src/node/internal/crypto_util.ts | 2 +- src/node/internal/internal_inspect.ts | 2 +- .../api/node/tests/crypto_random-test.js | 34 +++++++++++++++++++ 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/node/internal/crypto_cipher.ts b/src/node/internal/crypto_cipher.ts index 7a84e7ebac0..4799d9a6c61 100644 --- a/src/node/internal/crypto_cipher.ts +++ b/src/node/internal/crypto_cipher.ts @@ -618,7 +618,7 @@ export function publicEncrypt( ): Buffer { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (key === undefined) { - throw new ERR_MISSING_ARGS('privateKey'); + throw new ERR_MISSING_ARGS('key'); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (buffer === undefined) { @@ -660,7 +660,7 @@ export function publicDecrypt( ): Buffer { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (key === undefined) { - throw new ERR_MISSING_ARGS('privateKey'); + throw new ERR_MISSING_ARGS('key'); } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (buffer === undefined) { diff --git a/src/node/internal/crypto_random.ts b/src/node/internal/crypto_random.ts index 79249115414..9f8483b4283 100644 --- a/src/node/internal/crypto_random.ts +++ b/src/node/internal/crypto_random.ts @@ -87,7 +87,7 @@ export function randomFillSync( } else offset = 0; if (size !== undefined) { validateInteger(size, 'size', 0, maxLength - offset); - } else size = maxLength; + } else size = maxLength - offset; if (isAnyArrayBuffer(buffer)) { buffer = Buffer.from(buffer); } @@ -243,8 +243,8 @@ export function randomInt( max = minOrMax; } - if (min > max) { - throw new ERR_OUT_OF_RANGE('min', 'min <= max', min); + if (min >= max) { + throw new ERR_OUT_OF_RANGE('min', 'min < max', min); } if (callback != null) { diff --git a/src/node/internal/crypto_util.ts b/src/node/internal/crypto_util.ts index bb2787de249..9526bbc873c 100644 --- a/src/node/internal/crypto_util.ts +++ b/src/node/internal/crypto_util.ts @@ -39,7 +39,7 @@ type ArrayLike = cryptoImpl.ArrayLike; export const kHandle = Symbol('kHandle'); export const kFinalized = Symbol('kFinalized'); -export const kState = Symbol('kFinalized'); +export const kState = Symbol('kState'); // eslint-disable-next-line @typescript-eslint/no-explicit-any export function getStringOption(options: any, key: string): string | undefined { diff --git a/src/node/internal/internal_inspect.ts b/src/node/internal/internal_inspect.ts index 42a1b4fbe06..a67f5f5e2ba 100644 --- a/src/node/internal/internal_inspect.ts +++ b/src/node/internal/internal_inspect.ts @@ -3035,7 +3035,7 @@ function isRpcWildcardType(value: unknown) { return ( value instanceof internalWorkers.RpcStub || value instanceof internalWorkers.RpcPromise || - value instanceof internalWorkers.RpcPromise + value instanceof internalWorkers.RpcProperty ); } diff --git a/src/workerd/api/node/tests/crypto_random-test.js b/src/workerd/api/node/tests/crypto_random-test.js index 68bc9bb250f..370012a52d9 100644 --- a/src/workerd/api/node/tests/crypto_random-test.js +++ b/src/workerd/api/node/tests/crypto_random-test.js @@ -401,6 +401,40 @@ export const timingSafeEqualTest = { }, }; +export const randomIntTest = { + async test() { + const { randomInt } = await import('node:crypto'); + + // min === max should throw, not infinite-loop + throws(() => randomInt(5, 5), { code: 'ERR_OUT_OF_RANGE' }); + + // min > max should throw + throws(() => randomInt(10, 5), { code: 'ERR_OUT_OF_RANGE' }); + + // Valid range returns value in [min, max) + const val = randomInt(0, 10); + ok(val >= 0 && val < 10); + + // Single-arg form: randomInt(max) means [0, max) + strictEqual(randomInt(1), 0); + }, +}; + +export const randomFillSyncTest = { + async test() { + const { randomFillSync } = await import('node:crypto'); + + // With offset but no size, should fill only buf.length - offset bytes + const buf = Buffer.alloc(10, 0); + randomFillSync(buf, 4); + + // First 4 bytes must be untouched (all zero) + for (let i = 0; i < 4; i++) { + strictEqual(buf[i], 0, `byte ${i} should be untouched`); + } + }, +}; + // Ref: https://github.com/cloudflare/workerd/issues/2716 export const getRandomValuesIllegalInvocation = { async test() {