Skip to content

crypto: harden WebCrypto jobs and promise handling#63363

Open
panva wants to merge 4 commits into
nodejs:mainfrom
panva:webcrypto-job-mode
Open

crypto: harden WebCrypto jobs and promise handling#63363
panva wants to merge 4 commits into
nodejs:mainfrom
panva:webcrypto-job-mode

Conversation

@panva
Copy link
Copy Markdown
Member

@panva panva commented May 16, 2026

After quite the BoringSSL detour I'm coming back to Web Cryptography hardening related to #59699 (comment) and #59699 (comment) in hopes that we can eventually mark #59699 as resolved.


crypto: add WebCrypto CryptoJob mode

Add a WebCrypto-specific CryptoJob mode that returns a promise from run() and resolves it when native work is finished.

Encode job output directly as Web Crypto values, including CryptoKey instances and CryptoKeyPair dictionaries. Convert operation-specific setup failures from AdditionalConfig into OperationError rejections.


crypto: remove async from WebCrypto methods

Remove async function wrappers from SubtleCrypto methods while keeping their public promise-returning behaviour.

Route method entry points through a shared helper that converts synchronous validation errors into rejected promises. Let the internal implementations return native job promises directly.


crypto: pass CryptoKey handles to KDF jobs

Pass CryptoKey handles directly into KDF jobs instead of exporting secret bytes in lib.

Normalize HKDF, PBKDF2, and Argon2 around the same job construction pattern so WebCrypto derivation paths avoid extra key material copies and keep operation failures in native job handling.


crypto: harden WebCrypto against prototype pollution

Avoid re-wrapping native WebCrypto promises with PromiseResolve(), since resolving a promise can read its user-mutated constructor.

Add a helper for chaining internal WebCrypto job promises without consulting Promise species state, and use it for intermediate job results.

Also align JWK wrapping and unwrapping with the spec's fresh-global JSON handling by detaching internal JWK values from user prototypes. Use the internal UTF-8 encoder/decoder bindings instead of shared TextEncoder/TextDecoder prototype methods.

Expand the WebCrypto prototype pollution regression test to cover SubtleCrypto methods, export formats, zero-length KDF results, JWK toJSON/kty pollution, and encoder/decoder prototype poisoning.

@panva panva requested review from addaleax, anonrig and jasnell May 16, 2026 09:16
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels May 16, 2026
@panva panva added crypto Issues and PRs related to the crypto subsystem. security Issues and PRs related to security. webcrypto commit-queue-rebase Add this label to allow the Commit Queue to land a PR in several commits. dont-land-on-v22.x PRs that should not land on the v22.x-staging branch and should not be released in v22.x. dont-land-on-v24.x PRs that should not land on the v24.x-staging branch and should not be released in v24.x. dont-land-on-v25.x PRs that should not land on the v25.x-staging branch and should not be released in v25.x. labels May 16, 2026
@panva panva requested a review from tniessen May 16, 2026 09:49
@panva
Copy link
Copy Markdown
Member Author

panva commented May 16, 2026

First benchmark: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1847/

                                                                                                confidence improvement accuracy (*)    (**)   (***)
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=10 sync='subtle'                               ***     51.49 %       ±1.88%  ±2.51%  ±3.27%
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=100 sync='subtle'                              ***     46.56 %       ±1.65%  ±2.20%  ±2.87%
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=20 sync='subtle'                               ***     50.30 %       ±1.62%  ±2.16%  ±2.81%
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=50 sync='subtle'                               ***     47.82 %       ±1.97%  ±2.64%  ±3.46%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=10 sync='subtle'                             ***     53.30 %       ±1.74%  ±2.31%  ±3.01%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=100 sync='subtle'                            ***     46.80 %       ±1.43%  ±1.90%  ±2.48%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=20 sync='subtle'                             ***     50.61 %       ±1.51%  ±2.01%  ±2.62%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=50 sync='subtle'                             ***     48.72 %       ±1.67%  ±2.23%  ±2.92%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=10 sync='subtle'                             ***     50.38 %       ±1.63%  ±2.17%  ±2.83%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=100 sync='subtle'                            ***     45.03 %       ±1.85%  ±2.46%  ±3.21%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=20 sync='subtle'                             ***     51.67 %       ±1.48%  ±1.97%  ±2.56%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=50 sync='subtle'                             ***     47.72 %       ±1.76%  ±2.35%  ±3.07%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=10 sync='subtle'                             ***     50.89 %       ±1.37%  ±1.82%  ±2.38%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=100 sync='subtle'                            ***     46.94 %       ±1.85%  ±2.46%  ±3.20%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=20 sync='subtle'                             ***     48.71 %       ±1.61%  ±2.15%  ±2.80%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=50 sync='subtle'                             ***     48.82 %       ±1.57%  ±2.09%  ±2.73%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='ec'                         ***     14.77 %       ±4.90%  ±6.54%  ±8.53%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='ed25519'                    ***     -1.02 %       ±0.51%  ±0.68%  ±0.89%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='ml-dsa-44'                           0.10 %       ±1.23%  ±1.64%  ±2.13%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='rsa-pss'                            -0.09 %       ±0.21%  ±0.28%  ±0.37%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='rsassa-pkcs1-v1_5'                   0.04 %       ±0.38%  ±0.51%  ±0.66%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='ec'                                    0.45 %       ±2.88%  ±3.85%  ±5.04%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='ed25519'                               0.38 %       ±6.06%  ±8.07% ±10.53%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='ml-dsa-44'                             7.75 %      ±26.04% ±34.67% ±45.15%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='rsa-pss'                        *    -24.54 %      ±21.84% ±29.10% ±37.97%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='rsassa-pkcs1-v1_5'              *    -23.13 %      ±18.31% ±24.41% ±31.88%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='ec'                         ***     12.74 %       ±5.34%  ±7.13%  ±9.34%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='ed25519'                            -0.87 %       ±1.98%  ±2.65%  ±3.48%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='ml-dsa-44'                           0.16 %       ±1.03%  ±1.37%  ±1.78%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='rsa-pss'                            -0.02 %       ±0.15%  ±0.21%  ±0.27%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='rsassa-pkcs1-v1_5'                   0.07 %       ±0.09%  ±0.11%  ±0.15%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='ec'                               -1.81 %       ±2.17%  ±2.92%  ±3.87%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='ed25519'                          -0.23 %       ±0.46%  ±0.61%  ±0.79%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='ml-dsa-44'                         0.42 %       ±2.50%  ±3.33%  ±4.35%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='rsa-pss'                  ***     50.85 %       ±2.68%  ±3.57%  ±4.64%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='rsassa-pkcs1-v1_5'        ***     45.80 %       ±2.30%  ±3.07%  ±4.03%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='ec'                                  7.60 %      ±24.25% ±32.32% ±42.19%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='ed25519'                           -15.55 %      ±17.62% ±23.51% ±30.74%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='ml-dsa-44'                           0.43 %      ±19.65% ±26.15% ±34.03%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='rsa-pss'                            -0.63 %       ±4.18%  ±5.57%  ±7.25%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='rsassa-pkcs1-v1_5'                  -4.76 %       ±6.20%  ±8.30% ±10.90%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='ec'                                0.65 %       ±1.95%  ±2.60%  ±3.39%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='ed25519'                           0.08 %       ±0.42%  ±0.55%  ±0.72%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='ml-dsa-44'                         0.64 %       ±2.38%  ±3.18%  ±4.16%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='rsa-pss'                  ***     12.67 %       ±1.62%  ±2.16%  ±2.81%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='rsassa-pkcs1-v1_5'        ***      6.98 %       ±1.40%  ±1.86%  ±2.42%

Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 62 comparisons, you can thus
expect the following amount of false-positive results:
  3.10 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.62 false positives, when considering a   1% risk acceptance (**, ***),
  0.06 false positives, when considering a 0.1% risk acceptance (***)

Second benchmark (same target): https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1848/

                                                                                                confidence improvement accuracy (*)    (**)   (***)
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=10 sync='subtle'                               ***     50.75 %       ±1.77%  ±2.36%  ±3.08%
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=100 sync='subtle'                              ***     48.81 %       ±1.47%  ±1.96%  ±2.55%
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=20 sync='subtle'                               ***     50.38 %       ±1.92%  ±2.56%  ±3.34%
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=50 sync='subtle'                               ***     50.03 %       ±1.24%  ±1.66%  ±2.16%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=10 sync='subtle'                             ***     52.09 %       ±1.61%  ±2.14%  ±2.78%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=100 sync='subtle'                            ***     47.31 %       ±1.44%  ±1.91%  ±2.50%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=20 sync='subtle'                             ***     50.95 %       ±1.80%  ±2.40%  ±3.14%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=50 sync='subtle'                             ***     50.48 %       ±1.62%  ±2.16%  ±2.82%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=10 sync='subtle'                             ***     52.34 %       ±1.63%  ±2.17%  ±2.83%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=100 sync='subtle'                            ***     45.70 %       ±1.69%  ±2.25%  ±2.93%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=20 sync='subtle'                             ***     51.78 %       ±1.43%  ±1.91%  ±2.48%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=50 sync='subtle'                             ***     48.36 %       ±1.30%  ±1.73%  ±2.26%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=10 sync='subtle'                             ***     50.45 %       ±1.54%  ±2.05%  ±2.67%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=100 sync='subtle'                            ***     44.86 %       ±1.15%  ±1.54%  ±2.00%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=20 sync='subtle'                             ***     50.40 %       ±1.90%  ±2.53%  ±3.29%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=50 sync='subtle'                             ***     49.21 %       ±1.13%  ±1.50%  ±1.95%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='ec'                         ***     15.68 %       ±4.18%  ±5.56%  ±7.24%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='ed25519'                            -0.35 %       ±0.89%  ±1.19%  ±1.57%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='ml-dsa-44'                           0.06 %       ±1.48%  ±1.96%  ±2.56%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='rsa-pss'                             0.01 %       ±0.21%  ±0.28%  ±0.37%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='rsassa-pkcs1-v1_5'                   0.09 %       ±0.47%  ±0.62%  ±0.81%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='ec'                            **      2.87 %       ±1.66%  ±2.21%  ±2.88%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='ed25519'                              -4.58 %       ±6.59%  ±8.78% ±11.45%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='ml-dsa-44'                             4.58 %      ±28.96% ±38.55% ±50.22%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='rsa-pss'                              -3.68 %      ±22.91% ±30.50% ±39.73%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='rsassa-pkcs1-v1_5'                     2.10 %      ±28.17% ±37.53% ±48.93%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='ec'                         ***     12.67 %       ±4.85%  ±6.46%  ±8.41%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='ed25519'                            -0.91 %       ±1.69%  ±2.25%  ±2.95%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='ml-dsa-44'                          -0.84 %       ±1.43%  ±1.90%  ±2.48%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='rsa-pss'                             0.04 %       ±0.32%  ±0.43%  ±0.56%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='rsassa-pkcs1-v1_5'                  -0.02 %       ±0.17%  ±0.22%  ±0.29%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='ec'                               -0.36 %       ±1.21%  ±1.62%  ±2.13%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='ed25519'                          -0.03 %       ±0.40%  ±0.53%  ±0.68%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='ml-dsa-44'                         1.32 %       ±2.82%  ±3.76%  ±4.91%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='rsa-pss'                  ***     47.29 %       ±1.81%  ±2.41%  ±3.15%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='rsassa-pkcs1-v1_5'        ***     44.22 %       ±2.76%  ±3.68%  ±4.81%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='ec'                                  3.92 %      ±19.39% ±25.79% ±33.57%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='ed25519'                            24.53 %      ±28.82% ±38.62% ±50.84%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='ml-dsa-44'                          18.54 %      ±23.70% ±31.73% ±41.70%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='rsa-pss'                            -1.03 %       ±5.67%  ±7.58%  ±9.92%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='rsassa-pkcs1-v1_5'                   2.67 %       ±4.84%  ±6.46%  ±8.43%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='ec'                               -0.63 %       ±1.14%  ±1.54%  ±2.04%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='ed25519'                           0.22 %       ±0.42%  ±0.56%  ±0.73%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='ml-dsa-44'                        -0.17 %       ±3.31%  ±4.40%  ±5.73%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='rsa-pss'                  ***     12.16 %       ±2.37%  ±3.17%  ±4.15%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='rsassa-pkcs1-v1_5'        ***      8.25 %       ±1.11%  ±1.49%  ±1.95%

Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 62 comparisons, you can thus
expect the following amount of false-positive results:
  3.10 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.62 false positives, when considering a   1% risk acceptance (**, ***),
  0.06 false positives, when considering a 0.1% risk acceptance (***)

@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 85.39020% with 161 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.13%. Comparing base (550f195) to head (9473f1d).
⚠️ Report is 74 commits behind head on main.

Files with missing lines Patch % Lines
src/crypto/crypto_util.h 49.01% 33 Missing and 19 partials ⚠️
src/crypto/crypto_keygen.h 78.87% 2 Missing and 13 partials ⚠️
src/crypto/crypto_keys.cc 59.25% 6 Missing and 5 partials ⚠️
src/crypto/crypto_util.cc 75.60% 3 Missing and 7 partials ⚠️
lib/internal/crypto/util.js 89.04% 8 Missing ⚠️
src/crypto/crypto_argon2.cc 60.00% 5 Missing and 3 partials ⚠️
src/crypto/crypto_hkdf.cc 65.21% 5 Missing and 3 partials ⚠️
src/crypto/crypto_pbkdf2.cc 57.89% 2 Missing and 6 partials ⚠️
lib/internal/crypto/webcrypto.js 98.59% 5 Missing ⚠️
src/crypto/crypto_turboshake.cc 0.00% 2 Missing and 3 partials ⚠️
... and 12 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #63363      +/-   ##
==========================================
+ Coverage   90.06%   90.13%   +0.06%     
==========================================
  Files         714      718       +4     
  Lines      225648   228149    +2501     
  Branches    42711    42864     +153     
==========================================
+ Hits       203237   205641    +2404     
- Misses      14200    14247      +47     
- Partials     8211     8261      +50     
Files with missing lines Coverage Δ
lib/internal/crypto/aes.js 92.61% <100.00%> (+0.11%) ⬆️
lib/internal/crypto/argon2.js 98.70% <100.00%> (+2.75%) ⬆️
lib/internal/crypto/cfrg.js 94.67% <100.00%> (-0.24%) ⬇️
lib/internal/crypto/chacha20_poly1305.js 98.47% <100.00%> (+0.04%) ⬆️
lib/internal/crypto/diffiehellman.js 97.76% <100.00%> (+0.02%) ⬆️
lib/internal/crypto/ec.js 95.54% <100.00%> (-0.09%) ⬇️
lib/internal/crypto/hash.js 99.00% <100.00%> (ø)
lib/internal/crypto/hkdf.js 98.84% <100.00%> (+2.77%) ⬆️
lib/internal/crypto/mac.js 99.02% <100.00%> (+0.03%) ⬆️
lib/internal/crypto/ml_dsa.js 97.46% <100.00%> (-0.10%) ⬇️
... and 29 more

... and 78 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Renegade334

This comment was marked as off-topic.

@panva
Copy link
Copy Markdown
Member Author

panva commented May 16, 2026

@Renegade334 you are incorrect in your assessments. The public methods still reject, not throw. Nothing is changed on the surface as evidenced by the lack of test changes and pre-existing passing comprehensive test cov including WPTs.

@Renegade334
Copy link
Copy Markdown
Member

Renegade334 commented May 16, 2026

Oh good, code review hid webcrypto.js as a "large diff" 🤦‍♂️ I see the new machinery, apologies.

(FWIW PromiseResolve is still exposed to the same .constructor mutability issue as before, but there's not a lot we can do about that besides wait for a spec change.)

@panva
Copy link
Copy Markdown
Member Author

panva commented May 16, 2026

@Renegade334 i can do this across the board

// WebCrypto methods return promises, including for synchronous validation
// failures. Keep that conversion in one place so method bodies stay readable.
function callSubtleCryptoMethod(fn, receiver, args) {
  try {
    const result = ReflectApply(fn, receiver, args);
    // PromiseResolve(promise) reads promise.constructor. Avoid re-wrapping
    // native promises so Promise.prototype.constructor pollution is ignored.
    return isPromise(result) ? result : PromiseResolve(result);
  } catch (err) {
    return PromiseReject(err);
  }
}

That way PromiseResolve(result) is only called on what isn't a native job, which i now realize includes the key export jobs for which i can do a follow up to re-introduce a simpler native key export job now that everything goes through handles already, if key exports would go through jobs too then the only PromiseResolve that would be used would be for useless early returns. And it's fairly useless for PromiseReject.

Oh and the composed methods (wrapKey, encapsulateKey, decapsulateKey)... I have think about those a bit.

@Renegade334
Copy link
Copy Markdown
Member

Renegade334 commented May 16, 2026

Nice thought! Any significant impact on benchmarks?

@panva
Copy link
Copy Markdown
Member Author

panva commented May 16, 2026

Nice thought! Any significant impact on benchmarks?

🤷‍♂️ we'll see when i do it

@panva panva marked this pull request as draft May 16, 2026 11:06
@panva
Copy link
Copy Markdown
Member Author

panva commented May 16, 2026

Third benchmark: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1849/

                                                                                                confidence improvement accuracy (*)    (**)   (***)
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=10 sync='subtle'                               ***     51.57 %       ±1.51%  ±2.00%  ±2.61%
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=100 sync='subtle'                              ***     47.09 %       ±1.42%  ±1.89%  ±2.46%
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=20 sync='subtle'                               ***     52.23 %       ±1.63%  ±2.17%  ±2.83%
crypto/webcrypto-digest.js n=100000 method='SHA-1' data=50 sync='subtle'                               ***     52.15 %       ±1.35%  ±1.79%  ±2.33%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=10 sync='subtle'                             ***     52.08 %       ±1.57%  ±2.09%  ±2.73%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=100 sync='subtle'                            ***     48.34 %       ±1.44%  ±1.91%  ±2.49%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=20 sync='subtle'                             ***     51.62 %       ±1.96%  ±2.62%  ±3.41%
crypto/webcrypto-digest.js n=100000 method='SHA-256' data=50 sync='subtle'                             ***     49.68 %       ±1.42%  ±1.89%  ±2.46%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=10 sync='subtle'                             ***     49.96 %       ±1.26%  ±1.67%  ±2.18%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=100 sync='subtle'                            ***     44.78 %       ±1.72%  ±2.28%  ±2.97%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=20 sync='subtle'                             ***     48.48 %       ±1.32%  ±1.76%  ±2.29%
crypto/webcrypto-digest.js n=100000 method='SHA-384' data=50 sync='subtle'                             ***     48.24 %       ±1.42%  ±1.89%  ±2.46%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=10 sync='subtle'                             ***     50.68 %       ±1.62%  ±2.16%  ±2.81%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=100 sync='subtle'                            ***     46.23 %       ±1.42%  ±1.89%  ±2.46%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=20 sync='subtle'                             ***     50.82 %       ±1.80%  ±2.39%  ±3.12%
crypto/webcrypto-digest.js n=100000 method='SHA-512' data=50 sync='subtle'                             ***     50.08 %       ±1.41%  ±1.87%  ±2.44%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='ec'                         ***     12.70 %       ±4.56%  ±6.10%  ±8.02%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='ed25519'                            -0.51 %       ±0.52%  ±0.70%  ±0.91%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='ml-dsa-44'                           0.69 %       ±1.29%  ±1.72%  ±2.25%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='rsa-pss'                            -0.49 %       ±0.53%  ±0.72%  ±0.94%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='parallel' keyType='rsassa-pkcs1-v1_5'                   0.06 %       ±0.36%  ±0.48%  ±0.63%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='ec'                             *      2.29 %       ±1.89%  ±2.51%  ±3.27%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='ed25519'                              -1.42 %       ±6.06%  ±8.06% ±10.49%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='ml-dsa-44'                             0.92 %      ±25.22% ±33.56% ±43.68%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='rsa-pss'                              -8.27 %      ±29.75% ±39.58% ±51.51%
crypto/webcrypto-sign.js n=1000 keyReuse='shared' mode='serial' keyType='rsassa-pkcs1-v1_5'                   -13.26 %      ±20.28% ±27.04% ±35.30%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='ec'                         ***     13.54 %       ±5.84%  ±7.77% ±10.12%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='ed25519'                      *     -1.06 %       ±1.02%  ±1.36%  ±1.78%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='ml-dsa-44'                           0.37 %       ±1.14%  ±1.51%  ±1.97%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='rsa-pss'                             0.13 %       ±0.23%  ±0.31%  ±0.41%
crypto/webcrypto-sign.js n=1000 keyReuse='unique' mode='parallel' keyType='rsassa-pkcs1-v1_5'                  -0.04 %       ±0.21%  ±0.29%  ±0.38%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='ec'                               -0.45 %       ±1.14%  ±1.54%  ±2.04%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='ed25519'                          -0.12 %       ±0.44%  ±0.59%  ±0.77%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='ml-dsa-44'                  *     -2.19 %       ±1.98%  ±2.67%  ±3.53%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='rsa-pss'                  ***     46.75 %       ±1.98%  ±2.64%  ±3.44%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='parallel' keyType='rsassa-pkcs1-v1_5'        ***     42.27 %       ±1.70%  ±2.27%  ±2.95%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='ec'                                -19.18 %      ±22.10% ±29.52% ±38.66%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='ed25519'                            21.84 %      ±32.51% ±43.51% ±57.17%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='ml-dsa-44'                          10.98 %      ±30.39% ±40.56% ±53.02%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='rsa-pss'                            -1.09 %       ±5.91%  ±7.89% ±10.31%
crypto/webcrypto-verify.js n=1000 keyReuse='shared' mode='serial' keyType='rsassa-pkcs1-v1_5'                   0.70 %       ±3.06%  ±4.07%  ±5.30%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='ec'                                0.99 %       ±1.28%  ±1.73%  ±2.29%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='ed25519'                          -0.07 %       ±0.67%  ±0.90%  ±1.18%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='ml-dsa-44'                        -2.33 %       ±3.21%  ±4.27%  ±5.56%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='rsa-pss'                  ***     11.77 %       ±1.46%  ±1.95%  ±2.56%
crypto/webcrypto-verify.js n=1000 keyReuse='unique' mode='parallel' keyType='rsassa-pkcs1-v1_5'        ***      8.16 %       ±1.18%  ±1.57%  ±2.06%

Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 62 comparisons, you can thus
expect the following amount of false-positive results:
  3.10 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.62 false positives, when considering a   1% risk acceptance (**, ***),
  0.06 false positives, when considering a 0.1% risk acceptance (***)

wrt benchmark runs 1 and 2, the async results can't be trusted.

@panva panva marked this pull request as ready for review May 16, 2026 17:52
@panva panva force-pushed the webcrypto-job-mode branch 5 times, most recently from 68a3a2b to 1d5f8f1 Compare May 16, 2026 20:20
@panva
Copy link
Copy Markdown
Member Author

panva commented May 16, 2026

fix one find 2 more... the webcrypto job mode is a good start but this really shows we need all methods to end with a native promise to get rid of the js promise machinery. export, import, composite methods, ... 😭

@panva panva force-pushed the webcrypto-job-mode branch from 1d5f8f1 to 03e6e99 Compare May 17, 2026 18:27
@panva
Copy link
Copy Markdown
Member Author

panva commented May 17, 2026

rebased.

Comment thread lib/internal/crypto/util.js Outdated
@panva panva added the review wanted PRs that need reviews. label May 22, 2026
Comment thread lib/internal/crypto/argon2.js Outdated
Comment thread lib/internal/crypto/util.js Outdated
Comment thread lib/internal/crypto/util.js Outdated
Comment thread lib/internal/crypto/webcrypto.js Outdated
Comment thread src/crypto/README.md Outdated
Comment thread test/parallel/test-webcrypto-crypto-job-mode.js Outdated
Comment thread test/parallel/test-webcrypto-derivekey.js Outdated
Comment thread test/parallel/test-webcrypto-promise-prototype-pollution.mjs Outdated
panva added 4 commits May 22, 2026 11:47
Add a WebCrypto-specific CryptoJob mode that returns a promise from
run() and resolves it when native work is finished.

Encode job output directly as Web Crypto values, including CryptoKey
instances and CryptoKeyPair dictionaries. Convert operation-specific
setup failures from AdditionalConfig into OperationError rejections.

Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Remove async function wrappers from SubtleCrypto methods while keeping
their public promise-returning behaviour.

Route method entry points through a shared helper that converts
synchronous validation errors into rejected promises. Let the internal
implementations return native job promises directly.

Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Pass CryptoKey handles directly into KDF jobs instead of exporting
secret bytes in lib.

Normalize HKDF, PBKDF2, and Argon2 around the same job construction
pattern so WebCrypto derivation paths avoid extra key material copies
and keep operation failures in native job handling.

Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Avoid re-wrapping native WebCrypto promises with PromiseResolve(),
since resolving a promise can read its user-mutated constructor.

Add a helper for chaining internal WebCrypto job promises without
consulting Promise species state, and use it for intermediate job
results.

Also align JWK wrapping and unwrapping with the spec's fresh-global
JSON handling by detaching internal JWK values from user prototypes.
Use the internal UTF-8 encoder/decoder bindings instead of shared
TextEncoder/TextDecoder prototype methods.

Expand the WebCrypto prototype pollution regression test to cover
SubtleCrypto methods, export formats, zero-length KDF results, JWK
toJSON/kty pollution, and encoder/decoder prototype poisoning.

Signed-off-by: Filip Skokan <panva.ip@gmail.com>
@panva panva force-pushed the webcrypto-job-mode branch from 03e6e99 to 9473f1d Compare May 22, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commit-queue-rebase Add this label to allow the Commit Queue to land a PR in several commits. crypto Issues and PRs related to the crypto subsystem. dont-land-on-v22.x PRs that should not land on the v22.x-staging branch and should not be released in v22.x. dont-land-on-v24.x PRs that should not land on the v24.x-staging branch and should not be released in v24.x. dont-land-on-v25.x PRs that should not land on the v25.x-staging branch and should not be released in v25.x. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. review wanted PRs that need reviews. security Issues and PRs related to security. webcrypto

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants