From 94cd600542d7af9792db63d4643ac4b0adec7e4c Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 30 Nov 2025 22:45:27 +0100 Subject: [PATCH 1/3] crypto: fix DOMException name for non-extractable key error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/60830 Reviewed-By: Chemi Atlow Reviewed-By: Luigi Pinca Reviewed-By: LiviaMedeiros Reviewed-By: Colin Ihrig Reviewed-By: Tobias Nießen Reviewed-By: Antoine du Hamel --- lib/internal/crypto/webcrypto.js | 2 +- .../test-webcrypto-export-import-cfrg.js | 12 ++++++++---- .../test-webcrypto-export-import-ec.js | 12 ++++++++---- .../test-webcrypto-export-import-ml-dsa.js | 18 ++++++++++++------ .../test-webcrypto-export-import-ml-kem.js | 12 ++++++++---- .../test-webcrypto-export-import-rsa.js | 12 ++++++++---- 6 files changed, 45 insertions(+), 23 deletions(-) diff --git a/lib/internal/crypto/webcrypto.js b/lib/internal/crypto/webcrypto.js index 0fba4e9108c329..2333919e06a2fe 100644 --- a/lib/internal/crypto/webcrypto.js +++ b/lib/internal/crypto/webcrypto.js @@ -676,7 +676,7 @@ async function exportKey(format, key) { } if (!key[kExtractable]) - throw lazyDOMException('key is not extractable', 'InvalidAccessException'); + throw lazyDOMException('key is not extractable', 'InvalidAccessError'); let result; switch (format) { diff --git a/test/parallel/test-webcrypto-export-import-cfrg.js b/test/parallel/test-webcrypto-export-import-cfrg.js index ae203e1005de0a..9cfc6e9e4ecf5a 100644 --- a/test/parallel/test-webcrypto-export-import-cfrg.js +++ b/test/parallel/test-webcrypto-export-import-cfrg.js @@ -134,7 +134,8 @@ async function testImportSpki({ name, publicUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('spki', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -172,7 +173,8 @@ async function testImportPkcs8({ name, privateUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -303,11 +305,13 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable) } else { await assert.rejects( subtle.exportKey('jwk', publicKey), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); await assert.rejects( subtle.exportKey('jwk', privateKey), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } diff --git a/test/parallel/test-webcrypto-export-import-ec.js b/test/parallel/test-webcrypto-export-import-ec.js index 46a7e9153f2668..a2e9df73bc2926 100644 --- a/test/parallel/test-webcrypto-export-import-ec.js +++ b/test/parallel/test-webcrypto-export-import-ec.js @@ -123,7 +123,8 @@ async function testImportSpki({ name, publicUsages }, namedCurve, extractable) { } else { await assert.rejects( subtle.exportKey('spki', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -165,7 +166,8 @@ async function testImportPkcs8( } else { await assert.rejects( subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -270,11 +272,13 @@ async function testImportJwk( } else { await assert.rejects( subtle.exportKey('jwk', publicKey), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); await assert.rejects( subtle.exportKey('jwk', privateKey), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } diff --git a/test/parallel/test-webcrypto-export-import-ml-dsa.js b/test/parallel/test-webcrypto-export-import-ml-dsa.js index 38d619eb8c00ec..5b1bafbd461999 100644 --- a/test/parallel/test-webcrypto-export-import-ml-dsa.js +++ b/test/parallel/test-webcrypto-export-import-ml-dsa.js @@ -106,7 +106,8 @@ async function testImportSpki({ name, publicUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('spki', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -144,7 +145,8 @@ async function testImportPkcs8({ name, privateUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -181,7 +183,8 @@ async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -219,7 +222,8 @@ async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -299,11 +303,13 @@ async function testImportJwk({ name, publicUsages, privateUsages }, extractable) } else { await assert.rejects( subtle.exportKey('jwk', publicKey), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); await assert.rejects( subtle.exportKey('jwk', privateKey), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } diff --git a/test/parallel/test-webcrypto-export-import-ml-kem.js b/test/parallel/test-webcrypto-export-import-ml-kem.js index c927b5571a69da..887875a2a8b580 100644 --- a/test/parallel/test-webcrypto-export-import-ml-kem.js +++ b/test/parallel/test-webcrypto-export-import-ml-kem.js @@ -89,7 +89,8 @@ async function testImportSpki({ name, publicUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('spki', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -127,7 +128,8 @@ async function testImportPkcs8({ name, privateUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -164,7 +166,8 @@ async function testImportPkcs8SeedOnly({ name, privateUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -202,7 +205,8 @@ async function testImportPkcs8PrivOnly({ name, privateUsages }, extractable) { } else { await assert.rejects( subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } diff --git a/test/parallel/test-webcrypto-export-import-rsa.js b/test/parallel/test-webcrypto-export-import-rsa.js index d3af8ec6c3adb9..e542365f52ded5 100644 --- a/test/parallel/test-webcrypto-export-import-rsa.js +++ b/test/parallel/test-webcrypto-export-import-rsa.js @@ -336,7 +336,8 @@ async function testImportSpki({ name, publicUsages }, size, hash, extractable) { } else { await assert.rejects( subtle.exportKey('spki', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } } @@ -372,7 +373,8 @@ async function testImportPkcs8( } else { await assert.rejects( subtle.exportKey('pkcs8', key), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } @@ -479,11 +481,13 @@ async function testImportJwk( } else { await assert.rejects( subtle.exportKey('jwk', publicKey), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); await assert.rejects( subtle.exportKey('jwk', privateKey), { - message: /key is not extractable/ + message: /key is not extractable/, + name: 'InvalidAccessError', }); } From 6f7f51b8f104dcebb96ac407b489719cc51e124a Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Sun, 30 Nov 2025 19:02:12 -0300 Subject: [PATCH 2/3] doc: clarify fileURLToPath security considerations Add clarification that fileURLToPath() decodes encoded dot-segments (%2e%2e) which are normalized as path traversal. Applications must perform their own path validation to prevent directory traversal attacks. Also applies to fileURLToPathBuffer(). PR-URL: https://github.com/nodejs/node/pull/60887 Reviewed-By: Marco Ippolito Reviewed-By: Luigi Pinca Reviewed-By: Colin Ihrig Reviewed-By: Matteo Collina --- doc/api/url.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/api/url.md b/doc/api/url.md index 7148a3a33e1975..29dd74479bd2ad 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -1329,6 +1329,19 @@ changes: This function ensures the correct decodings of percent-encoded characters as well as ensuring a cross-platform valid absolute path string. +**Security Considerations:** + +This function decodes percent-encoded characters, including encoded dot-segments +(`%2e` as `.` and `%2e%2e` as `..`), and then normalizes the resulting path. +This means that encoded directory traversal sequences (such as `%2e%2e`) are +decoded and processed as actual path traversal, even though encoded slashes +(`%2F`, `%5C`) are correctly rejected. + +**Applications must not rely on `fileURLToPath()` alone to prevent directory +traversal attacks.** Always perform explicit path validation and security checks +on the returned path value to ensure it remains within expected boundaries +before using it for file system operations. + ```mjs import { fileURLToPath } from 'node:url'; @@ -1384,6 +1397,15 @@ representation of the path, a `Buffer` is returned. This conversion is helpful when the input URL contains percent-encoded segments that are not valid UTF-8 / Unicode sequences. +**Security Considerations:** + +This function has the same security considerations as [`url.fileURLToPath()`][]. +It decodes percent-encoded characters, including encoded dot-segments +(`%2e` as `.` and `%2e%2e` as `..`), and normalizes the path. **Applications +must not rely on this function alone to prevent directory traversal attacks.** +Always perform explicit path validation on the returned buffer value before +using it for file system operations. + ### `url.format(URL[, options])`