Commit 4e38081bec6 for nodejs

commit 4e38081bec69c423479218c669d3b95e37396332
Author: Filip Skokan <panva.ip@gmail.com>
Date:   Tue Sep 22 22:29:27 2026 +0200

    crypto: create key usage arrays without species

    Build public usages and JWK key_ops from the native usage mask so
    array species cannot replace or mutate internal usage arrays.

    Signed-off-by: Filip Skokan <panva.ip@gmail.com>
    Assisted-by: Codex
    PR-URL: https://github.com/nodejs/node/pull/66237
    Reviewed-By: James M Snell <jasnell@gmail.com>
    Reviewed-By: Aviv Keller <me@aviv.sh>

diff --git a/lib/internal/crypto/kem_hybrids.js b/lib/internal/crypto/kem_hybrids.js
index 58fcb6e8fd1..d95a651b61b 100644
--- a/lib/internal/crypto/kem_hybrids.js
+++ b/lib/internal/crypto/kem_hybrids.js
@@ -1,7 +1,6 @@
 'use strict';

 const {
-  ArrayPrototypeSlice,
   BigInt,
   PromiseWithResolvers,
   SafeSet,
@@ -24,7 +23,7 @@ const {
   getCryptoKeySeedData,
   getCryptoKeySecondaryHandle,
   getCryptoKeyType,
-  getCryptoKeyUsages,
+  getCryptoKeyUsagesMask,
   InternalCryptoKey,
 } = require('internal/crypto/keys');

@@ -51,6 +50,7 @@ const {
 const {
   getBufferSourceBytes,
   getUsagesMask,
+  getUsagesFromMask,
   jobPromise,
   jobPromiseThen,
   resolveWebCryptoResult,
@@ -860,7 +860,7 @@ function exportJwkKey(key, rawEncapsulationKey, config) {
     kty: 'AKP',
     alg: config.name,
     pub: base64urlEncode(rawEncapsulationKey),
-    key_ops: ArrayPrototypeSlice(getCryptoKeyUsages(key), 0),
+    key_ops: getUsagesFromMask(getCryptoKeyUsagesMask(key)),
     ext: getCryptoKeyExtractable(key),
   };
   if (getCryptoKeyType(key) === 'private')
diff --git a/lib/internal/crypto/keys.js b/lib/internal/crypto/keys.js
index f54ba68f670..cdbf163ec08 100644
--- a/lib/internal/crypto/keys.js
+++ b/lib/internal/crypto/keys.js
@@ -2,7 +2,6 @@

 const {
   ArrayPrototypeMap,
-  ArrayPrototypeSlice,
   ObjectDefineProperties,
   ObjectPrototypeHasOwnProperty,
   ObjectSetPrototypeOf,
@@ -1224,7 +1223,7 @@ const {
         type: getCryptoKeyType(this),
         extractable: getCryptoKeyExtractable(this),
         algorithm: cloneAlgorithm(getCryptoKeyAlgorithm(this)),
-        usages: ArrayPrototypeSlice(getCryptoKeyUsages(this), 0),
+        usages: getUsagesFromMask(getCryptoKeyUsagesMask(this)),
       }, opts)}`;
     }

@@ -1258,7 +1257,7 @@ const {
       const slots = getSlots(this);
       let cached = slots[kSlotClonedUsages];
       if (cached === undefined) {
-        cached = ArrayPrototypeSlice(getCryptoKeyUsagesFromSlots(slots), 0);
+        cached = getUsagesFromMask(slots[kSlotUsagesMask]);
         slots[kSlotClonedUsages] = cached;
       }
       return cached;
diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js
index 7777774a217..2f44d726026 100644
--- a/lib/internal/crypto/webcrypto.js
+++ b/lib/internal/crypto/webcrypto.js
@@ -3,7 +3,6 @@
 const {
   ArrayBufferIsView,
   ArrayIsArray,
-  ArrayPrototypeSlice,
   FunctionPrototypeCall,
   JSONParse,
   JSONStringify,
@@ -52,7 +51,6 @@ const {
   getCryptoKeyExtractable,
   getCryptoKeyHandle,
   getCryptoKeyType,
-  getCryptoKeyUsages,
   getCryptoKeyUsagesMask,
   hasCryptoKeyUsage,
   importGenericSecretKey,
@@ -66,6 +64,7 @@ const {
 const {
   cleanupWebCryptoResult,
   getBlockSize,
+  getUsagesFromMask,
   jobPromiseThen,
   normalizeAlgorithm,
   normalizeHashName,
@@ -801,7 +800,7 @@ function exportKeyJWK(key) {
   // `parameters` before native export populates key material. Delete it for
   // algorithms without a JWK alg value to keep the expected shape.
   const parameters = {
-    key_ops: ArrayPrototypeSlice(getCryptoKeyUsages(key), 0),
+    key_ops: getUsagesFromMask(getCryptoKeyUsagesMask(key)),
     ext: getCryptoKeyExtractable(key),
     alg,
   };
diff --git a/test/parallel/test-webcrypto-usages-species.js b/test/parallel/test-webcrypto-usages-species.js
new file mode 100644
index 00000000000..2e1aed7ec42
--- /dev/null
+++ b/test/parallel/test-webcrypto-usages-species.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const assert = require('assert');
+const { subtle } = globalThis.crypto;
+
+(async () => {
+  const key = await subtle.importKey('raw', new Uint8Array(16), 'AES-GCM', true,
+                                     ['encrypt', 'decrypt']);
+  const descriptor = Object.getOwnPropertyDescriptor(Array.prototype, 'constructor');
+  let usages;
+  let jwk;
+  try {
+    Object.defineProperty(Array.prototype, 'constructor', { get: common.mustNotCall() });
+    usages = key.usages;
+    jwk = await subtle.exportKey('jwk', key);
+  } finally {
+    Object.defineProperty(Array.prototype, 'constructor', descriptor);
+  }
+  assert.strictEqual(Object.getPrototypeOf(usages), Array.prototype);
+  assert.strictEqual(key.usages, usages);
+  assert.deepStrictEqual(usages, ['encrypt', 'decrypt']);
+  assert.deepStrictEqual(jwk.key_ops, usages);
+  usages.push('sign');
+  assert.deepStrictEqual((await subtle.exportKey('jwk', key)).key_ops,
+                         ['encrypt', 'decrypt']);
+})().then(common.mustCall());