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());