From 6bdcb9af832c87d8702a7fc882a93cc5c445c773 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 24 May 2026 12:21:30 +0000 Subject: [PATCH 1/4] webcrypto: reject oversized BufferSource inputs instead of aborting SubtleCrypto entry points copy their BufferSource arguments into a WTF::Vector, whose capacity is capped below the maximum legal ArrayBuffer size. Validate the length before copying and reject the promise with an OperationError when it exceeds the cap. --- src/jsc/bindings/webcrypto/SubtleCrypto.cpp | 59 ++++++++++----- test/js/web/crypto/web-crypto.test.ts | 81 +++++++++++++++++++++ 2 files changed, 123 insertions(+), 17 deletions(-) diff --git a/src/jsc/bindings/webcrypto/SubtleCrypto.cpp b/src/jsc/bindings/webcrypto/SubtleCrypto.cpp index ddf169c5b5f..a6fb69b7746 100644 --- a/src/jsc/bindings/webcrypto/SubtleCrypto.cpp +++ b/src/jsc/bindings/webcrypto/SubtleCrypto.cpp @@ -530,7 +530,11 @@ static std::optional toKeyData(SubtleCrypto::KeyFormat format, SubtleCr promise->reject(Exception { TypeError }); return std::nullopt; }, - [](auto& bufferSource) -> std::optional { + [&promise](auto& bufferSource) -> std::optional { + if (!WTF::isValidCapacityForVector(bufferSource->byteLength())) { + promise->reject(OperationError, "Input data is too large"_s); + return std::nullopt; + } return KeyData { Vector(std::span { static_cast(bufferSource->data()), bufferSource->byteLength() }) }; }), keyDataVariant); @@ -551,9 +555,16 @@ static std::optional toKeyData(SubtleCrypto::KeyFormat format, SubtleCr RELEASE_ASSERT_NOT_REACHED(); } -static Vector copyToVector(BufferSource&& data) +// WTF::Vector capacity is capped below the maximum legal ArrayBuffer size, and exceeding the cap +// CRASH()es inside Vector::allocateBuffer. Validate the length before copying and reject the +// promise instead, mirroring the toKeyData contract: nullopt means the promise was already rejected. +static std::optional> copyToVector(BufferSource&& data, Ref& promise) { - return std::span { data.data(), data.length() }; + if (!WTF::isValidCapacityForVector(data.length())) { + promise->reject(OperationError, "Input data is too large"_s); + return std::nullopt; + } + return Vector { std::span { data.data(), data.length() } }; } static bool isSupportedExportKey(JSGlobalObject& state, CryptoAlgorithmIdentifier identifier) @@ -630,7 +641,9 @@ void SubtleCrypto::encrypt(JSC::JSGlobalObject& state, AlgorithmIdentifier&& alg } auto params = paramsOrException.releaseReturnValue(); - auto data = copyToVector(WTF::move(dataBufferSource)); + auto data = copyToVector(WTF::move(dataBufferSource), promise); + if (!data) + return; if (params->identifier != key.algorithmIdentifier()) { promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); @@ -656,7 +669,7 @@ void SubtleCrypto::encrypt(JSC::JSGlobalObject& state, AlgorithmIdentifier&& alg rejectWithException(promise.releaseNonNull(), ec, msg); }; - algorithm->encrypt(*params, key, WTF::move(data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); + algorithm->encrypt(*params, key, WTF::move(*data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); } void SubtleCrypto::decrypt(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, CryptoKey& key, BufferSource&& dataBufferSource, Ref&& promise) @@ -673,7 +686,9 @@ void SubtleCrypto::decrypt(JSC::JSGlobalObject& state, AlgorithmIdentifier&& alg } auto params = paramsOrException.releaseReturnValue(); - auto data = copyToVector(WTF::move(dataBufferSource)); + auto data = copyToVector(WTF::move(dataBufferSource), promise); + if (!data) + return; if (params->identifier != key.algorithmIdentifier()) { promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); @@ -699,7 +714,7 @@ void SubtleCrypto::decrypt(JSC::JSGlobalObject& state, AlgorithmIdentifier&& alg rejectWithException(promise.releaseNonNull(), ec, msg); }; - algorithm->decrypt(*params, key, WTF::move(data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); + algorithm->decrypt(*params, key, WTF::move(*data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); } void SubtleCrypto::sign(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, CryptoKey& key, BufferSource&& dataBufferSource, Ref&& promise) @@ -711,7 +726,9 @@ void SubtleCrypto::sign(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algori } auto params = paramsOrException.releaseReturnValue(); - auto data = copyToVector(WTF::move(dataBufferSource)); + auto data = copyToVector(WTF::move(dataBufferSource), promise); + if (!data) + return; if (params->identifier != key.algorithmIdentifier()) { promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); @@ -737,7 +754,7 @@ void SubtleCrypto::sign(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algori rejectWithException(promise.releaseNonNull(), ec, msg); }; - algorithm->sign(*params, key, WTF::move(data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); + algorithm->sign(*params, key, WTF::move(*data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); } void SubtleCrypto::verify(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, CryptoKey& key, BufferSource&& signatureBufferSource, BufferSource&& dataBufferSource, Ref&& promise) @@ -749,8 +766,12 @@ void SubtleCrypto::verify(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algo } auto params = paramsOrException.releaseReturnValue(); - auto signature = copyToVector(WTF::move(signatureBufferSource)); - auto data = copyToVector(WTF::move(dataBufferSource)); + auto signature = copyToVector(WTF::move(signatureBufferSource), promise); + if (!signature) + return; + auto data = copyToVector(WTF::move(dataBufferSource), promise); + if (!data) + return; if (params->identifier != key.algorithmIdentifier()) { promise->reject(InvalidAccessError, "CryptoKey doesn't match AlgorithmIdentifier"_s); @@ -776,7 +797,7 @@ void SubtleCrypto::verify(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algo rejectWithException(promise.releaseNonNull(), ec, msg); }; - algorithm->verify(*params, key, WTF::move(signature), WTF::move(data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); + algorithm->verify(*params, key, WTF::move(*signature), WTF::move(*data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); } void SubtleCrypto::digest(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, BufferSource&& dataBufferSource, Ref&& promise) @@ -791,7 +812,9 @@ void SubtleCrypto::digest(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algo } auto params = paramsOrException.releaseReturnValue(); - auto data = copyToVector(WTF::move(dataBufferSource)); + auto data = copyToVector(WTF::move(dataBufferSource), promise); + if (!data) + return; auto algorithm = CryptoAlgorithmRegistry::singleton().create(params->identifier); @@ -807,7 +830,7 @@ void SubtleCrypto::digest(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algo rejectWithException(promise.releaseNonNull(), ec, msg); }; - algorithm->digest(WTF::move(data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); + algorithm->digest(WTF::move(*data), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); } void SubtleCrypto::generateKey(JSC::JSGlobalObject& state, AlgorithmIdentifier&& algorithmIdentifier, bool extractable, Vector&& keyUsages, Ref&& promise) @@ -1170,7 +1193,9 @@ void SubtleCrypto::wrapKey(JSC::JSGlobalObject& state, KeyFormat format, CryptoK void SubtleCrypto::unwrapKey(JSC::JSGlobalObject& state, KeyFormat format, BufferSource&& wrappedKeyBufferSource, CryptoKey& unwrappingKey, AlgorithmIdentifier&& unwrapAlgorithmIdentifier, AlgorithmIdentifier&& unwrappedKeyAlgorithmIdentifier, bool extractable, Vector&& keyUsages, Ref&& promise) { - auto wrappedKey = copyToVector(WTF::move(wrappedKeyBufferSource)); + auto wrappedKey = copyToVector(WTF::move(wrappedKeyBufferSource), promise); + if (!wrappedKey) + return; bool isDecryption = false; @@ -1284,11 +1309,11 @@ void SubtleCrypto::unwrapKey(JSC::JSGlobalObject& state, KeyFormat format, Buffe // The 11 December 2014 version of the specification suggests we should perform the following task asynchronously: // https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-unwrapKey // It is not beneficial for less time consuming operations. Therefore, we perform it synchronously. - unwrapAlgorithm->unwrapKey(unwrappingKey, WTF::move(wrappedKey), WTF::move(callback), WTF::move(exceptionCallback)); + unwrapAlgorithm->unwrapKey(unwrappingKey, WTF::move(*wrappedKey), WTF::move(callback), WTF::move(exceptionCallback)); return; } - unwrapAlgorithm->decrypt(*unwrapParams, unwrappingKey, WTF::move(wrappedKey), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); + unwrapAlgorithm->decrypt(*unwrapParams, unwrappingKey, WTF::move(*wrappedKey), WTF::move(callback), WTF::move(exceptionCallback), *scriptExecutionContext(), m_workQueue); } } diff --git a/test/js/web/crypto/web-crypto.test.ts b/test/js/web/crypto/web-crypto.test.ts index 053a6ed9ae1..fa16d48c25d 100644 --- a/test/js/web/crypto/web-crypto.test.ts +++ b/test/js/web/crypto/web-crypto.test.ts @@ -207,6 +207,87 @@ describe("Web Crypto", () => { }); }); +describe("oversized inputs", () => { + // Every SubtleCrypto entry point copies its BufferSource argument into a + // WTF::Vector, whose capacity is capped below the maximum legal + // ArrayBuffer size. Inputs above the cap must reject the promise instead of + // aborting the process. Run in a subprocess so the ~2GiB allocation does not + // bloat the test runner; the buffer is never written so RSS stays small. + it("rejects >2 GiB inputs instead of aborting", async () => { + const script = ` + let big; + try { + big = new Uint8Array(2 ** 31); + } catch { + console.log("SKIP"); + process.exit(0); + } + + const aesKey = await crypto.subtle.importKey("raw", new Uint8Array(32).fill(1), { name: "AES-GCM" }, false, [ + "encrypt", + "decrypt", + "unwrapKey", + ]); + const hmacKey = await crypto.subtle.importKey( + "raw", + new Uint8Array(32).fill(2), + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign", "verify"], + ); + const iv = new Uint8Array(12).fill(3); + + const results = {}; + const record = (label, promise) => + promise.then( + () => (results[label] = "resolved"), + e => (results[label] = e.name), + ); + + await record("digest", crypto.subtle.digest("SHA-256", big)); + await record("encrypt", crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, big)); + await record("decrypt", crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, big)); + await record("sign", crypto.subtle.sign("HMAC", hmacKey, big)); + await record("verify data", crypto.subtle.verify("HMAC", hmacKey, new Uint8Array(32), big)); + await record("verify signature", crypto.subtle.verify("HMAC", hmacKey, big, new Uint8Array(32))); + await record("importKey", crypto.subtle.importKey("raw", big, { name: "AES-GCM" }, false, ["encrypt"])); + await record( + "unwrapKey", + crypto.subtle.unwrapKey("raw", big, aesKey, { name: "AES-GCM", iv }, { name: "AES-GCM" }, false, ["encrypt"]), + ); + + // Normal-sized inputs must keep working in the same process. + await crypto.subtle.digest("SHA-256", new Uint8Array(16)); + const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, new Uint8Array(16).fill(4)); + const roundTrip = new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, ciphertext)); + results["small round-trip"] = roundTrip.every(b => b === 4) ? "ok" : "mismatch"; + + console.log(JSON.stringify(results)); + `; + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", script], + env: bunEnv, + stdout: "pipe", + stderr: "inherit", + }); + const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]); + if (stdout.trim() !== "SKIP") { + expect(JSON.parse(stdout)).toEqual({ + "digest": "OperationError", + "encrypt": "OperationError", + "decrypt": "OperationError", + "sign": "OperationError", + "verify data": "OperationError", + "verify signature": "OperationError", + "importKey": "OperationError", + "unwrapKey": "OperationError", + "small round-trip": "ok", + }); + } + expect(exitCode).toBe(0); + }); +}); + describe("Ed25519", () => { describe("generateKey", () => { it("should return CryptoKeys without namedCurve in algorithm field", async () => { From fa677ed0e4d1eca1934f2cbb300d846af7f3b4fe Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 24 May 2026 16:27:10 +0000 Subject: [PATCH 2/4] webcrypto: bound BufferSource members of algorithm dictionaries --- src/jsc/bindings/webcrypto/SubtleCrypto.cpp | 28 +++++++++++++++++++++ test/js/web/crypto/web-crypto.test.ts | 20 +++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/jsc/bindings/webcrypto/SubtleCrypto.cpp b/src/jsc/bindings/webcrypto/SubtleCrypto.cpp index a6fb69b7746..78c741fe881 100644 --- a/src/jsc/bindings/webcrypto/SubtleCrypto.cpp +++ b/src/jsc/bindings/webcrypto/SubtleCrypto.cpp @@ -106,6 +106,22 @@ static bool isSafeCurvesEnabled(JSGlobalObject& state) // return context && context->settingsValues().webCryptoSafeCurvesEnabled; } +// The lazy *Vector() accessors on the parameter classes copy these dictionary members into +// Vector with no size check, and exceeding the Vector capacity cap CRASH()es in +// allocateBuffer. Validate them while normalizing so an oversized member rejects instead. +static bool isAcceptableVectorSource(const BufferSource& data) +{ + return WTF::isValidCapacityForVector(data.length()); +} + +static bool isAcceptableVectorSource(const std::optional& data) +{ + if (!data) + return true; + auto length = std::visit([](auto& buffer) -> size_t { return buffer ? buffer->byteLength() : 0; }, *data); + return WTF::isValidCapacityForVector(length); +} + static ExceptionOr> normalizeCryptoAlgorithmParameters(JSGlobalObject& state, SubtleCrypto::AlgorithmIdentifier algorithmIdentifier, Operations operation) { VM& vm = state.vm(); @@ -143,6 +159,8 @@ static ExceptionOr> normalizeCryptoAl case CryptoAlgorithmIdentifier::RSA_OAEP: { auto params = convertDictionary(state, value.get()); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + if (!isAcceptableVectorSource(params.label)) + return Exception { OperationError, "Input data is too large"_s }; result = makeUnique(params); break; } @@ -150,18 +168,24 @@ static ExceptionOr> normalizeCryptoAl case CryptoAlgorithmIdentifier::AES_CFB: { auto params = convertDictionary(state, value.get()); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + if (!isAcceptableVectorSource(params.iv)) + return Exception { OperationError, "Input data is too large"_s }; result = makeUnique(params); break; } case CryptoAlgorithmIdentifier::AES_CTR: { auto params = convertDictionary(state, value.get()); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + if (!isAcceptableVectorSource(params.counter)) + return Exception { OperationError, "Input data is too large"_s }; result = makeUnique(params); break; } case CryptoAlgorithmIdentifier::AES_GCM: { auto params = convertDictionary(state, value.get()); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + if (!isAcceptableVectorSource(params.iv) || !isAcceptableVectorSource(params.additionalData)) + return Exception { OperationError, "Input data is too large"_s }; result = makeUnique(params); break; } @@ -309,6 +333,8 @@ static ExceptionOr> normalizeCryptoAl case CryptoAlgorithmIdentifier::HKDF: { auto params = convertDictionary(state, value.get()); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + if (!isAcceptableVectorSource(params.salt) || !isAcceptableVectorSource(params.info)) + return Exception { OperationError, "Input data is too large"_s }; auto hashIdentifier = toHashIdentifier(state, params.hash); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); if (hashIdentifier.hasException()) return hashIdentifier.releaseException(); @@ -319,6 +345,8 @@ static ExceptionOr> normalizeCryptoAl case CryptoAlgorithmIdentifier::PBKDF2: { auto params = convertDictionary(state, value.get()); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + if (!isAcceptableVectorSource(params.salt)) + return Exception { OperationError, "Input data is too large"_s }; auto hashIdentifier = toHashIdentifier(state, params.hash); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); if (hashIdentifier.hasException()) return hashIdentifier.releaseException(); diff --git a/test/js/web/crypto/web-crypto.test.ts b/test/js/web/crypto/web-crypto.test.ts index fa16d48c25d..896d8594517 100644 --- a/test/js/web/crypto/web-crypto.test.ts +++ b/test/js/web/crypto/web-crypto.test.ts @@ -235,6 +235,10 @@ describe("oversized inputs", () => { false, ["sign", "verify"], ); + const cbcKey = await crypto.subtle.importKey("raw", new Uint8Array(32).fill(5), { name: "AES-CBC" }, false, [ + "encrypt", + ]); + const hkdfKey = await crypto.subtle.importKey("raw", new Uint8Array(32).fill(6), "HKDF", false, ["deriveBits"]); const iv = new Uint8Array(12).fill(3); const results = {}; @@ -256,6 +260,19 @@ describe("oversized inputs", () => { crypto.subtle.unwrapKey("raw", big, aesKey, { name: "AES-GCM", iv }, { name: "AES-GCM" }, false, ["encrypt"]), ); + // BufferSource members of the algorithm dictionaries are copied into + // Vectors by the parameter classes' lazy accessors, not by the entry + // points, so they need their own guard. + await record( + "encrypt additionalData", + crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData: big }, aesKey, new Uint8Array(16)), + ); + await record("encrypt iv", crypto.subtle.encrypt({ name: "AES-CBC", iv: big }, cbcKey, new Uint8Array(16))); + await record( + "deriveBits salt", + crypto.subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt: big, info: new Uint8Array(0) }, hkdfKey, 256), + ); + // Normal-sized inputs must keep working in the same process. await crypto.subtle.digest("SHA-256", new Uint8Array(16)); const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, new Uint8Array(16).fill(4)); @@ -281,6 +298,9 @@ describe("oversized inputs", () => { "verify signature": "OperationError", "importKey": "OperationError", "unwrapKey": "OperationError", + "encrypt additionalData": "OperationError", + "encrypt iv": "OperationError", + "deriveBits salt": "OperationError", "small round-trip": "ok", }); } From ae286f118956e7412e99b6f0a212f30a55b51dcf Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 24 May 2026 17:18:35 +0000 Subject: [PATCH 3/4] webcrypto: bound the RSA public exponent input --- src/jsc/bindings/webcrypto/SubtleCrypto.cpp | 11 +++++++++++ test/js/web/crypto/web-crypto.test.ts | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/src/jsc/bindings/webcrypto/SubtleCrypto.cpp b/src/jsc/bindings/webcrypto/SubtleCrypto.cpp index 78c741fe881..25f4dfe06fb 100644 --- a/src/jsc/bindings/webcrypto/SubtleCrypto.cpp +++ b/src/jsc/bindings/webcrypto/SubtleCrypto.cpp @@ -122,6 +122,13 @@ static bool isAcceptableVectorSource(const std::optional(length); } +// RsaKeyGenParams.publicExponent is a WebIDL BigInteger (a Uint8Array, not a +// BufferSource), but publicExponentVector() does the same unguarded append. +static bool isAcceptableVectorSource(const RefPtr& data) +{ + return !data || WTF::isValidCapacityForVector(data->byteLength()); +} + static ExceptionOr> normalizeCryptoAlgorithmParameters(JSGlobalObject& state, SubtleCrypto::AlgorithmIdentifier algorithmIdentifier, Operations operation) { VM& vm = state.vm(); @@ -244,6 +251,8 @@ static ExceptionOr> normalizeCryptoAl return Exception { NotSupportedError, "RSAES-PKCS1-v1_5 support is deprecated"_s }; auto params = convertDictionary(state, value.get()); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + if (!isAcceptableVectorSource(params.publicExponent)) + return Exception { OperationError, "Input data is too large"_s }; result = makeUnique(params); break; } @@ -252,6 +261,8 @@ static ExceptionOr> normalizeCryptoAl case CryptoAlgorithmIdentifier::RSA_OAEP: { auto params = convertDictionary(state, value.get()); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); + if (!isAcceptableVectorSource(params.publicExponent)) + return Exception { OperationError, "Input data is too large"_s }; auto hashIdentifier = toHashIdentifier(state, params.hash); RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError }); if (hashIdentifier.hasException()) return hashIdentifier.releaseException(); diff --git a/test/js/web/crypto/web-crypto.test.ts b/test/js/web/crypto/web-crypto.test.ts index 896d8594517..525ea9daf37 100644 --- a/test/js/web/crypto/web-crypto.test.ts +++ b/test/js/web/crypto/web-crypto.test.ts @@ -272,6 +272,14 @@ describe("oversized inputs", () => { "deriveBits salt", crypto.subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt: big, info: new Uint8Array(0) }, hkdfKey, 256), ); + // publicExponent is a WebIDL BigInteger (Uint8Array), not a BufferSource. + await record( + "generateKey publicExponent", + crypto.subtle.generateKey({ name: "RSA-OAEP", modulusLength: 2048, publicExponent: big, hash: "SHA-256" }, true, [ + "encrypt", + "decrypt", + ]), + ); // Normal-sized inputs must keep working in the same process. await crypto.subtle.digest("SHA-256", new Uint8Array(16)); @@ -301,6 +309,7 @@ describe("oversized inputs", () => { "encrypt additionalData": "OperationError", "encrypt iv": "OperationError", "deriveBits salt": "OperationError", + "generateKey publicExponent": "OperationError", "small round-trip": "ok", }); } From dae2a7cc900fa4fea8a4fc1362ee1d2e86efb14a Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 24 May 2026 18:01:01 +0000 Subject: [PATCH 4/4] test: assert empty stderr in the oversized-input fixture --- test/js/web/crypto/web-crypto.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/js/web/crypto/web-crypto.test.ts b/test/js/web/crypto/web-crypto.test.ts index 525ea9daf37..36693288916 100644 --- a/test/js/web/crypto/web-crypto.test.ts +++ b/test/js/web/crypto/web-crypto.test.ts @@ -293,9 +293,10 @@ describe("oversized inputs", () => { cmd: [bunExe(), "-e", script], env: bunEnv, stdout: "pipe", - stderr: "inherit", + stderr: "pipe", }); - const [stdout, exitCode] = await Promise.all([proc.stdout.text(), proc.exited]); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stderr).toBe(""); if (stdout.trim() !== "SKIP") { expect(JSON.parse(stdout)).toEqual({ "digest": "OperationError",