Skip to content

Update BoringSSL, add SHA3 to WebCrypto and node:crypto#29323

Merged
Jarred-Sumner merged 9 commits intomainfrom
jarred/boringssl-apr-14
Apr 15, 2026
Merged

Update BoringSSL, add SHA3 to WebCrypto and node:crypto#29323
Jarred-Sumner merged 9 commits intomainfrom
jarred/boringssl-apr-14

Conversation

@Jarred-Sumner
Copy link
Copy Markdown
Collaborator

@Jarred-Sumner Jarred-Sumner commented Apr 15, 2026

Summary

  • Bump BoringSSL to oven-sh/boringssl@0c5fce43b — synced with upstream beafe3db1, ~690 commits, with Bun's patches re-applied on top using a proper merge commit so future syncs don't conflict.
  • Add SHA3-256/384/512 to crypto.subtle.digest() and subtle.sign/verify with HMAC. Wraps BoringSSL's existing internal Keccak via a new EVP-based CryptoDigestContext.
  • Add sha3-224/256/384/512 to node:crypto createHash/createHmac/getHashes (via EVP.zig and EVP_get_digestbyname fallback in ncrypto.cpp).
  • Update boringssl.translated.zig env_md_ctx_st.md_data from [208]u8 to [240]u8 to match BoringSSL's new EVP_MAX_MD_DATA_SIZE (the Keccak state is 240 bytes). Without this, Zig-allocated digest contexts read SHA state bytes as the digest pointer and segfault.
  • Wire SHA3 through SerializedScriptValue so HMAC-SHA3 keys structured-clone, and through OpenSSLUtilities/CryptoKeyHMAC for key derivation defaults.

The new BoringSSL also brings ML-KEM-768/1024 and ML-DSA-44/65/87 (NIST FIPS 203/204) into EVP_PKEY, so node:crypto can support them in a follow-up.

Fixes #24075

Test plan

  • bun test test/js/web/crypto/web-crypto-sha3.test.ts — 15 new tests (NIST FIPS 202 vectors, HMAC-SHA3, large async path)
  • bun test test/js/web/crypto/ test/js/bun/crypto/ — 10,269 pass, 0 fail
  • bun test test/js/node/crypto/ — 797 pass, 0 fail
  • Cross-validated Hash.copy() + diverging updates against Python hashlib.sha3_256
  • Verified sizeof(EVP_MD_CTX) = 264 in both C and Zig

Bumps BoringSSL to oven-sh/boringssl@0c5fce43b (synced with upstream
beafe3db1). Adds SHA3-256/384/512 to crypto.subtle.digest and HMAC,
plus sha3-224/256/384/512 to node:crypto createHash/createHmac.

Updates the Zig env_md_ctx_st struct to match the new
EVP_MAX_MD_DATA_SIZE (240, was 208), which is required since the
md_data buffer now holds the larger Keccak state.
@robobun
Copy link
Copy Markdown
Collaborator

robobun commented Apr 15, 2026

Updated 2:05 AM PT - Apr 15th, 2026

@Jarred-Sumner, your commit 4be6752 has 3 failures in Build #45762 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 29323

That installs a local version of the PR into your bun-29323 executable, so you can run:

bun-29323 --bun

@github-actions
Copy link
Copy Markdown
Contributor

Found 3 issues this PR may fix:

  1. Invalid digest: sha3-256 #24075 - Directly fixes "Invalid digest: sha3-256" by adding SHA3-256/384/512 support to node:crypto and WebCrypto
  2. getHashes function does not report all available hashes #20592 - Partially addresses missing hashes in getHashes() by adding sha3-256/384/512 to the output
  3. Proposal: Implement "Modern Algorithms in the Web Cryptography API" (WICG specification) #29218 - Partially implements the WICG "Modern Algorithms in WebCrypto" proposal (SHA-3 digest support, plus BoringSSL update enabling future ML-KEM/ML-DSA work)

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #24075
Fixes #20592
Fixes #29218

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. webcrypto: add SHA-3 digests and SubtleCrypto.supports() #29222 - Also adds SHA-3 digest support to WebCrypto and touches the same CryptoDigest/CryptoAlgorithmIdentifier/EVP code paths

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds SHA‑3 support (SHA3‑224/256/384/512) across Bun: updates BoringSSL commit and Zig bindings to expose EVP_sha3_*; implements SHA‑3 WebCrypto classes and EVP-based digest/HMAC plumbing in C++/headers; extends enums/serialization, HMAC/key handling, PBKDF2 acceptance, allowlist, and tests.

Changes

Cohort / File(s) Summary
BoringSSL dependency & test fixture
scripts/build/deps/boringssl.ts, test/js/node/process/process.test.js
Updated BoringSSL commit reference used to fetch the dependency and adjusted the test fixture expecting the new process.versions.boringssl value.
Zig BoringSSL bindings
src/deps/boringssl.translated.zig
Adjusted EVP-related struct/constant sizes and added extern declarations for EVP_sha3_224/256/384/512.
EVP mapping (Zig) & PBKDF2 digest acceptance
src/bun.js/api/crypto/EVP.zig, src/bun.js/api/crypto/PBKDF2.zig
Mapped Algorithm.md() to return BoringSSL.EVP_sha3_*() for SHA‑3 variants; relaxed PBKDF2.fromJS digest rejection to only exclude .shake128/.shake256 (allowing sha3-*).
New SHA‑3 WebCrypto classes
src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.h, src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.cpp
Added CryptoAlgorithmSHA3_256/384/512 declarations and implementations with dispatch logic that computes short inputs synchronously and long inputs asynchronously via WorkQueue.
Algorithm identifiers, registry & serialization
src/bun.js/bindings/webcrypto/CryptoAlgorithmIdentifier.h, src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp, src/bun.js/bindings/webcore/SerializedScriptValue.cpp
Added SHA3_256/384/512 enum values; registered SHA‑3 algorithms in OpenSSL registry; extended serialized tags and (de)serialization branches and adjusted tag maximum.
Digest implementation & OpenSSL utilities
src/bun.js/bindings/webcrypto/CryptoDigest.h, src/bun.js/bindings/webcrypto/CryptoDigest.cpp, src/bun.js/bindings/webcrypto/OpenSSLUtilities.cpp
Extended CryptoDigest::Algorithm for SHA‑3; added CryptoDigestContextEVP using EVP_sha3_*() and updated OpenSSL digest mapping; added required OpenSSL include.
HMAC, key handling & AsymmetricKeyValue
src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.cpp, src/bun.js/bindings/webcrypto/CryptoKeyHMAC.cpp, src/bun.js/bindings/AsymmetricKeyValue.cpp
Accepted SHA‑3 identifiers in HMAC import/export logic; added HMAC key-length mappings for SHA‑3; treated non‑asymmetric SHA‑3 identifiers as non‑asymmetric in AsymmetricKeyValue.
SubtleCrypto normalization
src/bun.js/bindings/webcrypto/SubtleCrypto.cpp
Included SHA‑3 identifiers in digest parameter normalization and treat SHA‑3 import-key operations as NotSupportedError.
Allowlist / exported symbols
scripts/verify-baseline-static/allowlist-x64.txt
Adjusted BoringSSL Curve25519 allowlist formatting and added two mangled ADX/BMI2 symbol entries.
Tests (Web & Node)
test/js/web/crypto/web-crypto-sha3.test.ts
Added WebCrypto and Node.js tests for SHA‑3 digests, HMAC signing/verification and fixed vectors, PBKDF2-SHA3 vector, and Node crypto compatibility checks for sha3-256/384/512.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Update BoringSSL, add SHA3 to WebCrypto and node:crypto' clearly and concisely summarizes the primary changes: a BoringSSL bump and SHA3 cryptographic algorithm support additions across multiple APIs.
Description check ✅ Passed The PR description comprehensively covers what was done (BoringSSL bump, SHA3 additions, struct updates, wiring through serialization) and verification method (test counts and cross-validation). It aligns well with the template's requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/bun.js/bindings/webcrypto/CryptoDigest.cpp`:
- Around line 107-137: Add defensive ASSERT checks for the return values of the
OpenSSL EVP calls inside CryptoDigestContextEVP: verify
EVP_MD_CTX_init/EVP_DigestInit_ex in the constructor, EVP_DigestUpdate in
addBytes, and EVP_DigestFinal_ex (and EVP_MD_CTX_cleanup if desired) in the
destructor/computeHash to catch unexpected failures; update the constructor
(CryptoDigestContextEVP), addBytes, and computeHash methods to assert the EVP_*
functions return success (non-zero) and provide clear assertion messages
referencing the function names.

In `@test/js/web/crypto/web-crypto-sha3.test.ts`:
- Around line 40-42: The test "SHA3-256 large input (>64 bytes, async path)"
uses "a".repeat(1_000_000) which violates the test guideline; update the input
creation in that test to use Buffer.alloc(1_000_000, "a").toString() instead of
.repeat(). Locate the test case with description "SHA3-256 large input (>64
bytes, async path)" in web-crypto-sha3.test.ts and replace the TextEncoder input
source accordingly so the rest of the test (crypto.subtle.digest call) remains
unchanged.
- Around line 95-116: Replace the inline require(...) calls with module-scope
imports: remove the in-test require usages of createHash, createHmac, and
getHashes and add a single top-level import like "import { createHash,
createHmac, getHashes } from 'node:crypto';" so the tests use the module-scoped
symbols (createHash, createHmac, getHashes) instead of calling require() inside
each it() block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c2128a47-27dd-428f-a0ee-70a30875aaa3

📥 Commits

Reviewing files that changed from the base of the PR and between 4d3fb02 and e6ca20c.

📒 Files selected for processing (16)
  • scripts/build/deps/boringssl.ts
  • src/bun.js/api/crypto/EVP.zig
  • src/bun.js/bindings/AsymmetricKeyValue.cpp
  • src/bun.js/bindings/webcore/SerializedScriptValue.cpp
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.cpp
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmIdentifier.h
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.cpp
  • src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA3.h
  • src/bun.js/bindings/webcrypto/CryptoDigest.cpp
  • src/bun.js/bindings/webcrypto/CryptoDigest.h
  • src/bun.js/bindings/webcrypto/CryptoKeyHMAC.cpp
  • src/bun.js/bindings/webcrypto/OpenSSLUtilities.cpp
  • src/bun.js/bindings/webcrypto/SubtleCrypto.cpp
  • src/deps/boringssl.translated.zig
  • test/js/web/crypto/web-crypto-sha3.test.ts

Comment thread src/bun.js/bindings/webcrypto/CryptoDigest.cpp
Comment thread test/js/web/crypto/web-crypto-sha3.test.ts
Comment thread test/js/web/crypto/web-crypto-sha3.test.ts Outdated
Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No bugs found, but this PR updates a major crypto dependency (~690 BoringSSL commits) and adds new algorithm support across WebCrypto, node:crypto, and Zig FFI — a human should sign off on the memory layout change in boringssl.translated.zig and the BoringSSL bump itself.

Extended reasoning...

Overview

This PR bumps BoringSSL (~690 upstream commits) and wires SHA3-256/384/512 through the full WebCrypto stack: new CryptoAlgorithmSHA3 classes, an EVP-based CryptoDigestContext, SerializedScriptValue structured-clone tags, HMAC key-length defaults, OpenSSLUtilities digest mapping, SubtleCrypto normalization, and Zig-side EVP bindings. SHA3-224 is also added to node:crypto only. 15 new NIST FIPS 202 test vectors are included.

Security Risks

This touches the core cryptographic primitives layer. The BoringSSL bump is ~690 commits — any regression in the upstream library or misapplied patch would affect TLS, all digest/HMAC operations, and key handling across all of Bun. The boringssl.translated.zig struct layout change (md_data: [208]u8[240]u8, EVP_MAX_MD_BLOCK_SIZE 128 → 144) is a low-level ABI detail that must exactly match the compiled BoringSSL; a mismatch causes silent memory corruption or segfaults. The author has verified sizeof(EVP_MD_CTX) = 264 in C and Zig, which is good, but this class of change benefits from a second pair of eyes.

Level of Scrutiny

High. This is a security-library dependency update combined with new algorithm support in production-critical WebCrypto code. The implementation looks correct and follows existing patterns precisely, test coverage is thorough, and NIST vectors are validated. Still, the BoringSSL bump and Zig ABI change are exactly the kind of thing that should have explicit human sign-off.

Other Factors

No bugs were identified by the automated review. The HMAC key-length defaults (SHA3-256: 1088 bits, SHA3-384: 832 bits, SHA3-512: 576 bits) match the correct Keccak rate values. JWK export for SHA3 intentionally leaves alg empty due to the absence of a standardized JWK algorithm name, which is reasonable. The structured-clone tag values (24–26) are correctly appended and the max-value constant is updated. Overall the logic is sound, but the dependency bump scope alone warrants a human reviewer.

Comment thread test/js/web/crypto/web-crypto-sha3.test.ts
Comment thread test/js/web/crypto/web-crypto-sha3.test.ts
Comment thread src/bun.js/bindings/webcrypto/CryptoDigest.cpp
Upstream BoringSSL commit f7543e6db moved internal symbols into the bssl:: namespace. The x25519 ADX scalar multiply functions are runtime-gated by CRYPTO_is_BMI2_capable() && CRYPTO_is_ADX_capable() in crypto/curve25519/curve25519.cc.
Comment thread src/bun.js/bindings/webcrypto/SubtleCrypto.cpp
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
test/js/web/crypto/web-crypto-sha3.test.ts (2)

35-37: ⚠️ Potential issue | 🟡 Minor

Replace .repeat() with Buffer.alloc(...).toString() for the large-input fixture.

This test currently uses "a".repeat(1_000_000), which violates the test guideline for repetitive strings.

Suggested patch
-    const input = new TextEncoder().encode("a".repeat(1_000_000));
+    const input = new TextEncoder().encode(Buffer.alloc(1_000_000, "a").toString());

As per coding guidelines: “Use Buffer.alloc(count, fill).toString() instead of 'A'.repeat(count) to create repetitive strings in tests.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/js/web/crypto/web-crypto-sha3.test.ts` around lines 35 - 37, In the test
"SHA3-256 large input (>64 bytes, async path)" replace the repetitive-string
fixture that uses "a".repeat(1_000_000) with Buffer.alloc(1_000_000,
"a").toString() so the TextEncoder().encode(...) call uses the Buffer-generated
string; update the const input assignment (the line creating input before
calling crypto.subtle.digest) to use Buffer.alloc(...) to conform to the
guideline.

76-77: ⚠️ Potential issue | 🟡 Minor

Use module-scope node:crypto imports instead of inline require() in test bodies.

These tests are not validating dynamic module loading behavior, so inline require() should be replaced with top-level imports.

Suggested patch
 import { describe, expect, it } from "bun:test";
+import { createHash, createHmac, getHashes, pbkdf2Sync } from "node:crypto";
@@
   it("createHash sha3-256", () => {
-    const { createHash } = require("node:crypto");
     expect(createHash("sha3-256").update("abc").digest("hex")).toBe(
@@
   it("createHash sha3-384", () => {
-    const { createHash } = require("node:crypto");
     expect(createHash("sha3-384").update("abc").digest("hex")).toBe(
@@
   it("createHmac sha3-512", () => {
-    const { createHmac } = require("node:crypto");
     const out = createHmac("sha3-512", Buffer.from("key")).update("data").digest("hex");
@@
   it("getHashes includes sha3", () => {
-    const { getHashes } = require("node:crypto");
     const hashes = getHashes();
@@
   it("pbkdf2Sync sha3-256", () => {
-    const { pbkdf2Sync } = require("node:crypto");
     expect(pbkdf2Sync("pw", "salt", 1000, 32, "sha3-256").toString("hex")).toBe(

As per coding guidelines: “Only use dynamic import or require when the test is specifically testing dynamic import or require behavior. Otherwise, always use module-scope import statements.”

Also applies to: 83-84, 90-91, 96-97, 104-105

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/js/web/crypto/web-crypto-sha3.test.ts` around lines 76 - 77, The tests
use inline require("node:crypto") inside test bodies to get createHash; change
these to module-scope imports by adding a top-level import of createHash from
"node:crypto" and remove the inline require calls in the tests (references to
createHash in web-crypto-sha3.test.ts should be updated accordingly); apply the
same replacement for the other inline requires in this file where createHash (or
other crypto symbols) are required in test bodies.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/js/web/crypto/web-crypto-sha3.test.ts`:
- Around line 27-44: The new WebCrypto test suite "crypto.subtle.digest SHA-3"
(the describe block in web-crypto-sha3.test.ts) triggers ASAN SIGILL under
BUN_JSC_validateExceptionChecks=1; to fix, add the test file name
web-crypto-sha3.test.ts to test/no-validate-exceptions.txt under the "#
normalizeCryptoAlgorithmParameters" section so it is skipped by the ASAN
validation; ensure the exact filename is listed on its own line within that
section so CI will not run the validation for this test.

---

Duplicate comments:
In `@test/js/web/crypto/web-crypto-sha3.test.ts`:
- Around line 35-37: In the test "SHA3-256 large input (>64 bytes, async path)"
replace the repetitive-string fixture that uses "a".repeat(1_000_000) with
Buffer.alloc(1_000_000, "a").toString() so the TextEncoder().encode(...) call
uses the Buffer-generated string; update the const input assignment (the line
creating input before calling crypto.subtle.digest) to use Buffer.alloc(...) to
conform to the guideline.
- Around line 76-77: The tests use inline require("node:crypto") inside test
bodies to get createHash; change these to module-scope imports by adding a
top-level import of createHash from "node:crypto" and remove the inline require
calls in the tests (references to createHash in web-crypto-sha3.test.ts should
be updated accordingly); apply the same replacement for the other inline
requires in this file where createHash (or other crypto symbols) are required in
test bodies.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 10b7c468-a7b8-49f6-84d9-d297c8acf43e

📥 Commits

Reviewing files that changed from the base of the PR and between 62a8a4d and 22a55d1.

📒 Files selected for processing (2)
  • src/bun.js/api/crypto/PBKDF2.zig
  • test/js/web/crypto/web-crypto-sha3.test.ts

Comment on lines +27 to +44
describe("crypto.subtle.digest SHA-3", () => {
for (const [alg, input, expected] of vectors) {
it(`${alg}(${JSON.stringify(input)})`, async () => {
const buf = await crypto.subtle.digest(alg, new TextEncoder().encode(input));
expect(hex(buf)).toBe(expected);
});
}

it("SHA3-256 large input (>64 bytes, async path)", async () => {
const input = new TextEncoder().encode("a".repeat(1_000_000));
const buf = await crypto.subtle.digest("SHA3-256", input);
expect(hex(buf)).toBe("5c8875ae474a3634ba4fd55ec85bffd661f32aca75c6d699d0cdcb6c115891c1");
});

it("rejects unknown digest", async () => {
expect(crypto.subtle.digest("SHA3-1024" as any, new Uint8Array())).rejects.toThrow();
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ASAN stability fix is likely still missing for this new WebCrypto test file.

This suite matches the known BUN_JSC_validateExceptionChecks=1 SIGILL pattern in ASAN runs. Please add this test file under the # normalizeCryptoAlgorithmParameters section in test/no-validate-exceptions.txt to avoid CI-only crashes unrelated to SHA3 logic.

Based on learnings: “In oven-sh/bun, SIGILL crashes in ASAN CI for webcrypto tests … are caused by BUN_JSC_validateExceptionChecks=1 … Adding the test under the # normalizeCryptoAlgorithmParameters section of that file is the correct and complete fix.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/js/web/crypto/web-crypto-sha3.test.ts` around lines 27 - 44, The new
WebCrypto test suite "crypto.subtle.digest SHA-3" (the describe block in
web-crypto-sha3.test.ts) triggers ASAN SIGILL under
BUN_JSC_validateExceptionChecks=1; to fix, add the test file name
web-crypto-sha3.test.ts to test/no-validate-exceptions.txt under the "#
normalizeCryptoAlgorithmParameters" section so it is skipped by the ASAN
validation; ensure the exact filename is listed on its own line within that
section so CI will not run the validation for this test.

Jarred-Sumner and others added 3 commits April 14, 2026 21:02
- Handle SHA3 in RSA-PSS/OAEP/RSASSA JWK import/export switches: omit
  alg field on export (no IETF-registered identifier exists), accept
  null alg on import. Prevents ASSERT_NOT_REACHED in debug builds when
  exporting RSA keys generated with SHA3 hash.
- Remove stale ripemd160 HMAC rejection from CryptoHasher.zig - it
  works now that RIPEMD160 is in libcrypto.
- Move sha3-* and ripemd160 from CryptoHasher's "unsupported" test
  list to the supported HMAC vectors (cross-validated against Python).
- ASSERT on EVP_DigestInit_ex return in CryptoDigestContextEVP.
- Fix missing await on rejects.toThrow(), use Buffer.alloc instead of
  string repeat, hoist node:crypto imports to module scope.
The scope was declared but never checked, tripping JSC's exception scope validation in ASAN builds. Matches the pattern in sign/verify/encrypt/decrypt which also call normalizeCryptoAlgorithmParameters without declaring a local scope (the IDL binding wrapper handles pending exceptions).
Comment on lines +207 to +209
case CryptoAlgorithmIdentifier::SHA3_256:
case CryptoAlgorithmIdentifier::SHA3_384:
case CryptoAlgorithmIdentifier::SHA3_512:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Adding SHA3 to Operations::Digest in SubtleCrypto.cpp (lines 207-209) causes toHashIdentifier() to succeed for SHA3 identifiers in every algorithm that calls it, inadvertently enabling crypto.subtle.sign/verify({name:'ECDSA', hash:'SHA3-256'}, ...) and crypto.subtle.deriveBits({name:'HKDF', hash:'SHA3-256'}, ...) without throwing NotSupportedError. ECDSA+SHA3 and HKDF+SHA3 are not defined in the W3C WebCrypto Level 1/2 spec, so Bun would silently produce non-interoperable signatures and key material that no other browser (Chrome, Firefox, Safari) can replicate.

Extended reasoning...

Root cause: toHashIdentifier() in SubtleCrypto.cpp validates hash parameters by calling normalizeCryptoAlgorithmParameters with Operations::Digest. This PR adds SHA3_256, SHA3_384, and SHA3_512 to the Operations::Digest switch (lines 207-209) so that crypto.subtle.digest() and HMAC can use SHA3. However, every other algorithm that validates its hash parameter — ECDSA sign/verify (line ~183), HKDF DeriveBits (line ~312), RSA key generation, and PBKDF2 — also calls toHashIdentifier() through the same path. Adding SHA3 to Operations::Digest therefore silently enables SHA3 as a valid hash parameter for all of these algorithms simultaneously.

ECDSA impact (bug_002): Before this PR, toHashIdentifier('SHA3-256') returned NotSupportedError because SHA3_256 was not registered. After this PR, normalizeCryptoAlgorithmParameters matches SHA3_256 in the Digest switch and returns successfully, so ECDSA sign/verify normalization (SubtleCrypto.cpp ~183-188) stores SHA3_256 in params.hashIdentifier. CryptoAlgorithmECDSAOpenSSL.cpp then calls digestAlgorithm(parameters.hashIdentifier) at line 41, which now returns EVP_sha3_256() (because this PR also updated OpenSSLUtilities.cpp). Since the result is non-null, the if (\!md) return Exception { NotSupportedError }; guard is bypassed and BoringSSL actually computes a real ECDSA-SHA3 signature. ECDSA with SHA-3 is not defined in the W3C WebCrypto Level 1 or Level 2 spec, nor in NIST FIPS 186-5 (which only standardizes ECDSA with SHA-2 families). Chrome, Firefox, and Safari all reject this operation; Bun would produce signatures that no other WebCrypto implementation can verify.

HKDF impact (bug_003): The same mechanism applies to HKDF. The HKDF DeriveBits normalization (SubtleCrypto.cpp ~309-312) calls toHashIdentifier(state, params.hash), which now resolves 'SHA3-256' successfully. CryptoAlgorithmHKDFOpenSSL.cpp:40-42 then calls digestAlgorithm(parameters.hashIdentifier), receives EVP_sha3_256() instead of nullptr, and proceeds to derive key material using HKDF-SHA3-256. The WebCrypto spec Section 33.5 defines HKDF only with SHA-1 and SHA-2 hashes. No other WebCrypto implementation accepts HKDF+SHA3, so derived key material cannot be reproduced by any spec-compliant peer.

Addressing the HKDF refutation: One verifier argued HKDF+SHA3 is intentional because PBKDF2+SHA3 was explicitly enabled (by removing SHA3 from the blocklist in PBKDF2.zig), RFC 5869 is hash-agnostic, and the WICG Modern Algorithms proposal covers derivation functions. However: (1) PBKDF2+SHA3 was an explicit, deliberate decision with matching Zig-side code changes; HKDF+SHA3 was not explicitly enabled anywhere — it is a side effect. (2) The WICG proposal is not yet ratified and no other browser has shipped HKDF+SHA3. (3) Producing key material that diverges from all other WebCrypto implementations without a spec justification is a silent interoperability break, not an extension. The fix described below makes HKDF+SHA3 opt-in when the spec catches up, rather than silently on by default today.

How to fix: The cleanest fix is to explicitly reject SHA3 identifiers inside the ECDSA sign/verify normalization and the HKDF DeriveBits normalization, after toHashIdentifier returns — for example, by checking if (hashIdentifier == SHA3_256 || hashIdentifier == SHA3_384 || hashIdentifier == SHA3_512) return Exception { NotSupportedError };. Alternatively, toHashIdentifier itself could be split into a general variant and a spec-restricted variant that only allows SHA-1/2 families, with callers choosing which to use. The Operations::Digest switch should remain as-is since SHA3 is valid for crypto.subtle.digest() and HMAC.

Step-by-step proof for ECDSA: (1) Before PR: CryptoAlgorithmRegistry::singleton().identifier('SHA3-256') returns std::nulloptnormalizeCryptoAlgorithmParameters returns NotSupportedError at the if (\!identifier) guard → ECDSA sign rejects. (2) After PR: SHA3_256 is registered via registerAlgorithm<CryptoAlgorithmSHA3_256>(); the Operations::Digest switch matches → toHashIdentifier returns SHA3_256 → ECDSA normalization stores it → digestAlgorithm(SHA3_256) returns EVP_sha3_256() → BoringSSL signs with SHA3 → non-interoperable signature returned with no error.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional — Bun matches Node.js's webcrypto behavior here, not browsers'.

Node.js supports SHA3 as a hash for all of these (just verified with Node 24):

await crypto.subtle.sign({name:"ECDSA", hash:"SHA3-256"}, key, data)      // works
await crypto.subtle.deriveBits({name:"HKDF", hash:"SHA3-256", ...}, k, n) // works
await crypto.subtle.generateKey({name:"HMAC", hash:"SHA3-256", length:256}, ...) // works

Node emits an ExperimentalWarning for SHA3 in webcrypto, but accepts it everywhere a hash is taken — not just digest().

On the NIST claim: FIPS 186-5 §6.4 says "An approved hash function or XOF, as specified in FIPS 180 or FIPS 202, shall be used" — FIPS 202 is SHA-3, so ECDSA-SHA3 is NIST-approved. RFC 5869 (HKDF) is hash-agnostic by design.

On interop: the "non-interoperable with browsers" concern applies equally to subtle.digest("SHA3-256") itself — no browser ships any SHA3 in WebCrypto yet, including the WICG-draft digest operation. Restricting SHA3 to digest-only wouldn't make the feature browser-portable; it would just force users to fall back to node:crypto for sign/HKDF (which already supports the same combos).

Keeping as-is for Node parity. Happy to add Node's experimental warning in a follow-up if wanted.

Comment on lines 125 to 134
return alg.isNull() || alg == ALG384;
case CryptoAlgorithmIdentifier::SHA_512:
return alg.isNull() || alg == ALG512;
case CryptoAlgorithmIdentifier::SHA3_256:
case CryptoAlgorithmIdentifier::SHA3_384:
case CryptoAlgorithmIdentifier::SHA3_512:
return alg.isNull();
default:
return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 When exporting an HMAC-SHA3-256/384/512 key as JWK, no alg field is emitted (the switch in exportKey falls through with a bare break at lines 182-188 of CryptoAlgorithmHMAC.cpp), and the import checkAlgCallback at lines 125-131 accepts any SHA3 JWK whenever alg is null — so a SHA3-256 key exported to JWK can be silently re-imported as SHA3-512. The JWK roundtrip is lossy with respect to the hash variant; consider throwing NotSupportedError for SHA3 HMAC JWK export/import, or documenting that JWK does not preserve the SHA3 hash variant.

Extended reasoning...

What the bug is and how it manifests

When exportKey('jwk', hmacSha3Key) is called, CryptoAlgorithmHMAC.cpp lines 182-188 match all three SHA3 variants and execute a bare break without setting jwk.alg. This is intentional — no IANA-registered JWK alg identifiers exist for HMAC-SHA3. The exported JWK therefore looks like {kty:'oct', k:'...'} with no alg field for any SHA3 variant.

The specific code path that triggers it

During importKey('jwk', jwkData, {name:'HMAC', hash:'SHA3-512'}), the code reaches CryptoKeyHMAC::importJwk which calls the checkAlgCallback lambda at lines 125-131. For all three SHA3 identifiers the callback returns alg.isNull(). Since the exported JWK never sets alg regardless of which SHA3 variant was used, alg.isNull() is always true — the check passes silently. The import then proceeds with SHA3_512 as the hash but with the key material of the original SHA3-256 key.

Why existing code does not prevent it

For SHA-2 HMAC, exportKey always sets jwk.alg to a specific string (HS256, HS384, etc.), and the import callback verifies alg == 'HS512' (for example). Cross-variant confusion is rejected at import time. For SHA3, no analogous IANA-registered alg string exists, so the export produces no alg field and the import check is reduced to the vacuous alg.isNull() condition — which is true for all SHA3 export JWKs and therefore cannot distinguish between variants.

What the impact would be

The JWK roundtrip does not preserve the SHA3 hash variant. A developer who exports a SHA3-256 HMAC key, transmits the JWK, and re-imports it with hash:'SHA3-512' will get an HMAC-SHA3-512 key backed by 32-byte key material without any error. HMAC values computed with the original key will not match those from the re-imported key, silently breaking correctness guarantees.

Step-by-step proof

  1. const key256 = await crypto.subtle.generateKey({name:'HMAC', hash:'SHA3-256'}, true, ['sign','verify']); — creates a SHA3-256 HMAC key (default 1088 bits per CryptoKeyHMAC.cpp line 46, i.e. 136 bytes).
  2. const jwk = await crypto.subtle.exportKey('jwk', key256); — the switch in exportKey hits SHA3_256 case at line 182 and breaks; jwk.alg is never set (null/absent).
  3. const key512 = await crypto.subtle.importKey('jwk', jwk, {name:'HMAC', hash:'SHA3-512'}, true, ['sign','verify']); — importJwk calls checkAlgCallback(SHA3_512, null_string); the case at line 128 returns alg.isNull() == true; import succeeds.
  4. key512 is now an HMAC-SHA3-512 key backed by the 136-byte key material of the original SHA3-256 key — wrong variant, no error.

How to fix it

Option A (clean): Return NotSupportedError from exportKey and importKey for SHA3 HMAC in JWK format, making the limitation explicit and consistent with the existing rejection of SHA3 as a standalone importKey algorithm in SubtleCrypto.cpp lines 381-387.

Option B (best-effort): Emit a non-standard alg string (e.g. HS3-256) and verify it in the import callback, preventing cross-variant confusion at the cost of interoperability with other implementations that follow the JWK spec strictly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches Node.js's webcrypto behavior:

> const k = await crypto.subtle.generateKey({name:"HMAC",hash:"SHA3-256",length:256},true,["sign"])
> (await crypto.subtle.exportKey("jwk", k)).alg
undefined

There's no IETF-registered JWK alg value for HMAC-SHA3 or RSA-SHA3, so omitting it is the only correct behavior — emitting a made-up identifier would break import in other implementations. The hash variant must be supplied at import time, same as it would be for an alg-less JWK from any source. Throwing on export would make the feature useless for key persistence with no real safety benefit (re-importing with wrong hash → wrong-size signatures → fails immediately on first verify, not silent corruption).

Comment thread src/bun.js/bindings/webcrypto/CryptoDigest.cpp
Comment thread test/js/web/crypto/web-crypto-sha3.test.ts
Comment on lines 163 to 169

invalid: {
switch (try EVP.Algorithm.map.fromJSCaseInsensitive(globalThis, arg4) orelse break :invalid) {
.shake128, .shake256, .@"sha3-224", .@"sha3-256", .@"sha3-384", .@"sha3-512" => break :invalid,
.shake128, .shake256 => break :invalid,
else => |alg| break :brk alg,
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟣 Calling pbkdf2Sync('pw', 'salt', 1000, 32, 'blake2s256') panics the Zig runtime because blake2s256 passes the PBKDF2 blocklist check (only .shake128 and .shake256 are blocked) but EVP.Algorithm.md() returns null for it — and PBKDF2.run() force-unwraps that null via .? at line 25. This is a pre-existing bug (blake2s256 was never blocked before this PR either), but this PR modifies the exact switch statement that should also guard it.

Extended reasoning...

What the bug is and how it manifests

blake2s256 is a valid entry in EVP.Algorithm.map (EVP.zig line ~77), so PBKDF2.fromJS resolves it to the .blake2s256 algorithm variant and proceeds past the invalid-algorithm gate. However, EVP.Algorithm.md() only handles .blake2b256 and .blake2b512 in its explicit match arms; .blake2s256 falls through to the else => null branch and returns null. PBKDF2.run() then calls algorithm.md().? (line 25), which force-unwraps an optional null — a Zig runtime panic that crashes the entire process.

The specific code path that triggers it

  1. pbkdf2Sync('pw', 'salt', 1000, 32, 'blake2s256') calls PBKDF2.fromJS.
  2. EVP.Algorithm.map.fromJSCaseInsensitive resolves 'blake2s256' to .blake2s256.
  3. The blocklist switch (lines 163-169, as modified by this PR) only matches .shake128 and .shake256; .blake2s256 falls to the else => |alg| break :brk alg arm and is accepted.
  4. PBKDF2.run() calls algorithm.md().?md() returns null for .blake2s256 — Zig panics.

Why existing code doesn't prevent it

Before this PR, sha3 variants were also listed in the blocklist. The PR correctly removed them (since sha3 now has real EVP implementations). However, blake2s256 was also never blocked, and the modification to this switch statement is the exact right place to also block it. EVP.Algorithm.md() has no blake2s256 case because BoringSSL does not expose an EVP_blake2s256() function (only EVP_blake2b256/EVP_blake2b512 exist in BoringSSL's public API).

Impact

Any call to Node's pbkdf2 / pbkdf2Sync with digest 'blake2s256' will terminate the process with a Zig panic. This is a process-crashing denial-of-service if user-supplied digest names flow into PBKDF2.

How to fix it

Add .blake2s256 to the break :invalid list in PBKDF2.fromJS:

.shake128, .shake256, .blake2s256 => break :invalid,

Alternatively, change PBKDF2.run() to handle a null md() gracefully instead of force-unwrapping.

Step-by-step proof

  1. EVP.Algorithm.map includes "blake2s256" => .blake2s256 — the name resolves successfully.
  2. EVP.Algorithm.md() match arms: .blake2b256, .blake2b512, .md4, ..., .@"sha3-512" — no .blake2s256 case; hits else => null.
  3. PBKDF2.fromJS blocklist: .shake128, .shake256 => break :invalid; .blake2s256 is not listed, falls to else => |alg| break :brk alg.
  4. PBKDF2.run() line 25: algorithm.md().? force-unwraps null → Zig runtime panic, process terminates.

…n digest

RETURN_IF_EXCEPTION immediately after the call so the scope is
satisfied on both the JS-exception path and the WebCore-exception
path. Removing the scope (previous attempt) just moved the
unchecked-exception assertion to JSDOMPromiseDeferred::reject.

Also: ASSERT_UNUSED on EVP_DigestUpdate/Final return values, and
replace HMAC-SHA3-512 length check with a Python-validated vector.
Comment on lines 784 to 790
auto& vm = state.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTF::move(algorithmIdentifier), Operations::Digest);
RETURN_IF_EXCEPTION(scope, void());
if (paramsOrException.hasException()) {
promise->reject(paramsOrException.releaseException().code(), "Unrecognized algorithm name"_s);
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 In SubtleCrypto::digest, RETURN_IF_EXCEPTION(scope, void()) fires BEFORE the paramsOrException.hasException() check — the opposite of the established pattern in every other SubtleCrypto method. When normalizeCryptoAlgorithmParameters encounters a JS exception (e.g., from a getter on the algorithm object), the function returns void() and destroys the Ref<DeferredPromise> without ever rejecting it, leaving the JS Promise permanently pending. While post-PR commit 4be6752 added this ordering deliberately to avoid an ASAN assertion in JSDOMPromiseDeferred::reject, this trades one bug for another — the correct fix is to clear or scope the pending exception before rejecting the promise, not to abandon the promise entirely.

Extended reasoning...

What the bug is and how it manifests

In SubtleCrypto::digest (lines 784–790 of the modified SubtleCrypto.cpp), the ordering is:

auto paramsOrException = normalizeCryptoAlgorithmParameters(state, WTF::move(algorithmIdentifier), Operations::Digest);
RETURN_IF_EXCEPTION(scope, void());   // fires BEFORE hasException() check
if (paramsOrException.hasException()) {
    promise->reject(paramsOrException.releaseException().code(), "Unrecognized algorithm name");
    return;
}

Every other SubtleCrypto method (generateKey, deriveKey, deriveBits, importKey) consistently does the opposite — hasException() check first, reject the promise, then RETURN_IF_EXCEPTION after. The digest method inverts this order, meaning that on the JS-exception path the promise is never rejected.

The specific code path that triggers it

When the caller provides an algorithm object whose name getter throws a JavaScript exception:

await crypto.subtle.digest({ get name() { throw new Error('boom') } }, new Uint8Array());
  1. normalizeCryptoAlgorithmParameters calls convertDictionary<CryptoAlgorithmParameters>, which accesses .name and triggers the getter.
  2. The getter throws → JSC VM records a pending exception → convertDictionary propagates Exception { ExistingExceptionError }.
  3. normalizeCryptoAlgorithmParameters returns with ExistingExceptionError; the JSC VM still has a live pending exception.
  4. Back in digest: RETURN_IF_EXCEPTION(scope, void()) sees the pending VM exception and returns void() immediately — before hasException() is ever checked.
  5. SubtleCrypto::digest exits without calling promise->reject().
  6. The Ref<DeferredPromise> is destroyed; the JS Promise is permanently pending.

Addressing the refutation: why the deliberate ordering still constitutes a bug

Post-PR commit 4be6752 placed RETURN_IF_EXCEPTION before hasException() deliberately, because the prior attempt (commit 4514cea, which removed the throw scope entirely) caused an ASAN unchecked-exception assertion inside JSDOMPromiseDeferred::reject. The assertion occurs because JSDOMPromiseDeferred::reject must execute JS (to create a DOMException object) while a pending JSC exception already exists in the VM — which is illegal per JSC's throw-scope discipline.

However, this is a choice between two bugs, not a correct fix: an ASAN assertion in debug builds is replaced by a silent promise hang in all builds. The correct resolution is to clear the pending exception (via scope.clearException() or equivalent) before calling promise->reject(), allowing the promise to be properly rejected without triggering the unchecked-exception assertion. All other SubtleCrypto methods work because their normalization paths do not commonly produce ExistingExceptionError in the way digest does when algorithm property getters throw.

What the impact is

Any call to crypto.subtle.digest() where the algorithm parameter's property getter throws a JS exception produces a Promise that never settles. The exception is consumed by RETURN_IF_EXCEPTION inside the C++ layer, so neither the promise rejection handler nor the try/catch around await ever fires. The result is an effectively leaked Promise and unresponsive code path.

How to fix it

The correct approach — matching how WebKit handles analogous ExistingExceptionError situations — is: (1) check paramsOrException.hasException() first, (2) if the exception is ExistingExceptionError, call scope.clearException() to remove the pending VM exception before calling promise->reject(), then return. This allows promise rejection without triggering the JSDOMPromiseDeferred assertion, and matches the pattern used by every other SubtleCrypto method.

Step-by-step proof

  1. crypto.subtle.digest({ get name() { throw new Error('x') } }, buf) called from JS.
  2. WebIDL glue creates a DeferredPromise and calls SubtleCrypto::digest.
  3. normalizeCryptoAlgorithmParameters accesses .name; getter throws; JSC VM exception is set at SubtleCrypto.cpp:112 (inside convertDictionary); function returns ExistingExceptionError.
  4. RETURN_IF_EXCEPTION(scope, void()) at line 787 checks scope — pending exception found — returns void().
  5. Ref<DeferredPromise> destructor runs; no resolve/reject called.
  6. The JS Promise object the caller is await-ing will never settle — .catch() never fires, .then() never fires, await never resumes.

Comment on lines +28 to +45
describe("crypto.subtle.digest SHA-3", () => {
for (const [alg, input, expected] of vectors) {
it(`${alg}(${JSON.stringify(input)})`, async () => {
const buf = await crypto.subtle.digest(alg, new TextEncoder().encode(input));
expect(hex(buf)).toBe(expected);
});
}

it("SHA3-256 large input (>64 bytes, async path)", async () => {
const input = Buffer.alloc(1_000_000, "a");
const buf = await crypto.subtle.digest("SHA3-256", input);
expect(hex(buf)).toBe("5c8875ae474a3634ba4fd55ec85bffd661f32aca75c6d699d0cdcb6c115891c1");
});

it("rejects unknown digest", async () => {
await expect(crypto.subtle.digest("SHA3-1024" as any, new Uint8Array())).rejects.toThrow();
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The new test file test/js/web/crypto/web-crypto-sha3.test.ts is missing from test/no-validate-exceptions.txt under the # normalizeCryptoAlgorithmParameters section, causing real ASAN CI failures. The robobun bot already reported the failure: "unchecked exception at reject @ JSDOMPromiseDeferred.cpp:190 (thrown from SubtleCrypto.cpp:112) on x64-asan". Fix: add test/js/web/crypto/web-crypto-sha3.test.ts to that section of test/no-validate-exceptions.txt.

Extended reasoning...

What the bug is and how it manifests

The new test file test/js/web/crypto/web-crypto-sha3.test.ts was not added to test/no-validate-exceptions.txt under the # normalizeCryptoAlgorithmParameters section. This omission is confirmed by actual CI failure: robobun already reported "unchecked exception at reject @ src/bun.js/bindings/webcore/JSDOMPromiseDeferred.cpp:190 (thrown from src/bun.js/bindings/webcrypto/SubtleCrypto.cpp:112) on 🐧 13 x64-asan (new)". This is not a hypothetical failure — it is happening in the current build.

The specific code path that triggers it

The ASAN CI runner sets BUN_JSC_validateExceptionChecks=1, which causes JavaScriptCore to assert (SIGILL) whenever a C++ call returns a JSC exception but the caller has not checked for it via RETURN_IF_EXCEPTION or similar. The new test makes calls to crypto.subtle.digest, crypto.subtle.sign, crypto.subtle.verify, crypto.subtle.generateKey, crypto.subtle.importKey, and crypto.subtle.exportKey — all of which pass through normalizeCryptoAlgorithmParameters in SubtleCrypto.cpp, which can throw JSC exceptions. Some of these call paths lack a DECLARE_THROW_SCOPE / RETURN_IF_EXCEPTION guard (specifically the sign and verify paths), triggering the SIGILL under validateExceptionChecks=1.

Why existing code does not prevent it

The test/no-validate-exceptions.txt file is the project's mechanism for marking test files that exercise these WebCrypto code paths. The sibling file test/js/web/crypto/web-crypto.test.ts is already listed on line 145 of that file for exactly this reason. Every other WebCrypto test file that calls crypto.subtle operations has been added to this list. The new SHA3 test file was simply never added when the PR was authored.

What the impact would be

The bug is not theoretical — the robobun comment documents a live x64-asan CI failure caused by this exact test file. Without the exception entry, every ASAN CI run will SIGILL on this test, producing a false-positive failure that blocks merging and obscures real regressions.

How to fix it

Add one line to test/no-validate-exceptions.txt under the existing # normalizeCryptoAlgorithmParameters section:

test/js/web/crypto/web-crypto-sha3.test.ts

Step-by-step proof

  1. test/no-validate-exceptions.txt line 145 lists test/js/web/crypto/web-crypto.test.ts under # normalizeCryptoAlgorithmParameters — the sibling file is already excepted.
  2. The new file calls crypto.subtle.sign (line 52) and crypto.subtle.verify (lines 56, 58). In SubtleCrypto.cpp, SubtleCrypto::sign and SubtleCrypto::verify call normalizeCryptoAlgorithmParameters without a DECLARE_THROW_SCOPE, so a thrown JSC exception is unchecked.
  3. Under BUN_JSC_validateExceptionChecks=1, JSC asserts on unchecked exceptions — SIGILL.
  4. robobun Build #45756 confirms the crash on x64-asan with this exact file and stack location.

@Jarred-Sumner Jarred-Sumner merged commit 96f1e21 into main Apr 15, 2026
62 of 65 checks passed
@Jarred-Sumner Jarred-Sumner deleted the jarred/boringssl-apr-14 branch April 15, 2026 06:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid digest: sha3-256

2 participants