Commit 8a899d12315 for nodejs

commit 8a899d123159d50c60dd9445365654159c555767
Author: Filip Skokan <panva.ip@gmail.com>
Date:   Tue Sep 22 22:16:43 2026 +0200

    crypto: copy detached parameters as empty

    Treat detached BufferSource parameters as empty byte sequences during
    algorithm normalization, including detached DataViews.

    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/util.js b/lib/internal/crypto/util.js
index 05da8442ba2..b045696bd11 100644
--- a/lib/internal/crypto/util.js
+++ b/lib/internal/crypto/util.js
@@ -3,6 +3,7 @@
 const {
   ArrayBufferIsView,
   ArrayBufferPrototypeGetByteLength,
+  ArrayBufferPrototypeGetDetached,
   ArrayPrototypeIncludes,
   ArrayPrototypePush,
   ArrayPrototypeSlice,
@@ -107,6 +108,7 @@ const {

 const {
   isDataView,
+  isArrayBuffer,
   isArrayBufferView,
   isAnyArrayBuffer,
   isPromise,
@@ -874,19 +876,43 @@ function getDataViewOrTypedArrayByteLength(V) {
 }

 function getBufferSourceByteLength(V) {
-  return ArrayBufferIsView(V) ?
-    getDataViewOrTypedArrayByteLength(V) :
-    ArrayBufferPrototypeGetByteLength(V);
+  // ArrayBuffer and typed array lengths already become zero when detached.
+  if (!ArrayBufferIsView(V))
+    return ArrayBufferPrototypeGetByteLength(V);
+  if (!isDataView(V))
+    return TypedArrayPrototypeGetByteLength(V);
+  const buffer = DataViewPrototypeGetBuffer(V);
+  if (isArrayBuffer(buffer) && ArrayBufferPrototypeGetDetached(buffer))
+    return 0;
+  return DataViewPrototypeGetByteLength(V);
 }

 function getBufferSourceBytes(V) {
-  return ArrayBufferIsView(V) ?
-    new Uint8Array(
-      getDataViewOrTypedArrayBuffer(V),
-      getDataViewOrTypedArrayByteOffset(V),
-      getDataViewOrTypedArrayByteLength(V),
-    ) :
-    new Uint8Array(V, 0, ArrayBufferPrototypeGetByteLength(V));
+  if (!ArrayBufferIsView(V)) {
+    const length = ArrayBufferPrototypeGetByteLength(V);
+    if (length === 0 && ArrayBufferPrototypeGetDetached(V))
+      return new Uint8Array(0);
+    return new Uint8Array(V, 0, length);
+  }
+
+  let buffer;
+  let offset;
+  let length;
+  if (isDataView(V)) {
+    buffer = DataViewPrototypeGetBuffer(V);
+    if (isArrayBuffer(buffer) && ArrayBufferPrototypeGetDetached(buffer))
+      return new Uint8Array(0);
+    offset = DataViewPrototypeGetByteOffset(V);
+    length = DataViewPrototypeGetByteLength(V);
+  } else {
+    buffer = TypedArrayPrototypeGetBuffer(V);
+    offset = TypedArrayPrototypeGetByteOffset(V);
+    length = TypedArrayPrototypeGetByteLength(V);
+    if (length === 0 && isArrayBuffer(buffer) &&
+        ArrayBufferPrototypeGetDetached(buffer))
+      return new Uint8Array(0);
+  }
+  return new Uint8Array(buffer, offset, length);
 }

 function getOptionalByteLength(V) {
diff --git a/test/parallel/test-webcrypto-detached-parameters.js b/test/parallel/test-webcrypto-detached-parameters.js
new file mode 100644
index 00000000000..cf8b34cc9dc
--- /dev/null
+++ b/test/parallel/test-webcrypto-detached-parameters.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const assert = require('assert');
+const { subtle } = globalThis.crypto;
+
+function detached(kind) {
+  const buffer = new ArrayBuffer(16);
+  const value = kind === 'ArrayBuffer' ? buffer :
+    kind === 'DataView' ? new DataView(buffer) : new Uint8Array(buffer);
+  buffer.transfer();
+  return value;
+}
+
+(async () => {
+  const empty = new Uint8Array(0);
+  const input = new Uint8Array(16);
+  const key = await subtle.importKey('raw', input, 'HKDF', false, ['deriveBits']);
+  const algorithm = { name: 'HKDF', hash: 'SHA-256', salt: empty, info: empty };
+  const expected = await subtle.deriveBits(algorithm, key, 256);
+  const digest = await subtle.digest('SHA-256', empty);
+  for (const kind of ['ArrayBuffer', 'Uint8Array', 'DataView']) {
+    for (const member of ['salt', 'info']) {
+      assert.deepStrictEqual(await subtle.deriveBits({
+        ...algorithm, [member]: detached(kind),
+      }, key, 256), expected);
+    }
+    assert.deepStrictEqual(await subtle.digest('SHA-256', detached(kind)), digest);
+  }
+})().then(common.mustCall());