diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce5caa58ee7642..36aa3280779cf4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,7 +41,8 @@ release.
+25.2.1
25.2.0
25.1.0
25.0.0
@@ -42,6 +43,19 @@
* [io.js](CHANGELOG_IOJS.md)
* [Archive](CHANGELOG_ARCHIVE.md)
+
+
+## 2025-11-17, Version 25.2.1 (Current), @aduh95
+
+### Notable Changes
+
+### Commits
+
+* \[[`ff89b7b6c7`](https://github.com/nodejs/node/commit/ff89b7b6c7)] - **crypto**: ensure documented RSA-PSS saltLength default is used (Filip Skokan) [#60662](https://github.com/nodejs/node/pull/60662)
+* \[[`5316b580eb`](https://github.com/nodejs/node/commit/5316b580eb)] - **deps**: V8: backport 2e4c5cf9b112 (Michaƫl Zasso) [#60654](https://github.com/nodejs/node/pull/60654)
+* \[[`ca878bc90e`](https://github.com/nodejs/node/commit/ca878bc90e)] - **doc,src,lib**: clarify experimental status of Web Storage support (Antoine du Hamel) [#60708](https://github.com/nodejs/node/pull/60708)
+* \[[`a4dee613fd`](https://github.com/nodejs/node/commit/a4dee613fd)] - _**Revert**_ "**lib**: throw from localStorage getter on missing storage path" (Antoine du Hamel) [#60750](https://github.com/nodejs/node/pull/60750)
+
## 2025-11-11, Version 25.2.0 (Current), @aduh95
diff --git a/doc/node.1 b/doc/node.1
index 72800cd364222b..540ce661a0d182 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -207,8 +207,8 @@ Enable experimental support for the EventSource Web API.
.It Fl -no-experimental-websocket
Disable experimental support for the WebSocket API.
.
-.It Fl -no-webstorage
-Disable webstorage.
+.It Fl -no-experimental-webstorage
+Disable experimental support for the Web Storage API.
.
.It Fl -no-experimental-repl-await
Disable top-level await keyword support in REPL.
diff --git a/lib/internal/crypto/sig.js b/lib/internal/crypto/sig.js
index 091a3f7bdcd6e9..a5bdaaf22b5ef0 100644
--- a/lib/internal/crypto/sig.js
+++ b/lib/internal/crypto/sig.js
@@ -50,6 +50,8 @@ const {
isArrayBufferView,
} = require('internal/util/types');
+const constants = internalBinding('constants').crypto;
+
function Sign(algorithm, options) {
if (!(this instanceof Sign))
return new Sign(algorithm, options);
@@ -85,7 +87,11 @@ function getPadding(options) {
}
function getSaltLength(options) {
- return getIntOption('saltLength', options);
+ let saltLength = getIntOption('saltLength', options);
+ if (options.padding === constants.RSA_PKCS1_PSS_PADDING && saltLength === undefined) {
+ saltLength = constants.RSA_PSS_SALTLEN_MAX_SIGN;
+ }
+ return saltLength;
}
function getDSASignatureEncoding(options) {
diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js
index 76ec7d821cb4cc..283ec72d388572 100644
--- a/lib/internal/process/pre_execution.js
+++ b/lib/internal/process/pre_execution.js
@@ -398,7 +398,7 @@ function setupQuic() {
function setupWebStorage() {
if (getEmbedderOptions().noBrowserGlobals ||
- !getOptionValue('--webstorage')) {
+ !getOptionValue('--experimental-webstorage')) {
return;
}
diff --git a/lib/internal/webstorage.js b/lib/internal/webstorage.js
index 989ccb2dd40eea..28b5df3f3da835 100644
--- a/lib/internal/webstorage.js
+++ b/lib/internal/webstorage.js
@@ -1,9 +1,9 @@
'use strict';
const {
ObjectDefineProperties,
+ Proxy,
} = primordials;
const { getOptionValue } = require('internal/options');
-const { lazyDOMException } = require('internal/util');
const { kConstructorKey, Storage } = internalBinding('webstorage');
const { getValidatedPath } = require('internal/fs/utils');
const kInMemoryPath = ':memory:';
@@ -21,17 +21,34 @@ ObjectDefineProperties(module.exports, {
enumerable: true,
get() {
if (lazyLocalStorage === undefined) {
- // For consistency with the web specification, throw from the accessor
- // if the local storage path is not provided.
const location = getOptionValue('--localstorage-file');
+
if (location === '') {
- throw lazyDOMException(
- 'Cannot initialize local storage without a `--localstorage-file` path',
- 'SecurityError',
- );
- }
+ let warningEmitted = false;
+ const handler = {
+ __proto__: null,
+ get(target, prop) {
+ if (!warningEmitted) {
+ process.emitWarning('`--localstorage-file` was provided without a valid path');
+ warningEmitted = true;
+ }
+
+ return undefined;
+ },
+ set(target, prop, value) {
+ if (!warningEmitted) {
+ process.emitWarning('`--localstorage-file` was provided without a valid path');
+ warningEmitted = true;
+ }
- lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location));
+ return false;
+ },
+ };
+
+ lazyLocalStorage = new Proxy({}, handler);
+ } else {
+ lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location));
+ }
}
return lazyLocalStorage;
diff --git a/src/node_options.cc b/src/node_options.cc
index 6f5475c3d9bd14..8dd186477c8875 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -567,12 +567,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
NoOp{},
#endif
kAllowedInEnvvar);
- AddOption("--webstorage",
- "Web Storage API",
+ AddOption("--experimental-webstorage",
+ "experimental Web Storage API",
&EnvironmentOptions::webstorage,
kAllowedInEnvvar,
true);
- AddAlias("--experimental-webstorage", "--webstorage");
+ AddAlias("--webstorage", "--experimental-webstorage");
AddOption("--localstorage-file",
"file used to persist localStorage data",
&EnvironmentOptions::localstorage_file,
diff --git a/src/node_version.h b/src/node_version.h
index f2e0e3fe7529e1..2eb3ac4b17d0c6 100644
--- a/src/node_version.h
+++ b/src/node_version.h
@@ -29,7 +29,7 @@
#define NODE_VERSION_IS_LTS 0
#define NODE_VERSION_LTS_CODENAME ""
-#define NODE_VERSION_IS_RELEASE 0
+#define NODE_VERSION_IS_RELEASE 1
#ifndef NODE_STRINGIFY
#define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)
diff --git a/test/common/index.js b/test/common/index.js
index 3f612afbff5ef9..11465d7b5d77be 100755
--- a/test/common/index.js
+++ b/test/common/index.js
@@ -59,14 +59,6 @@ const hasSQLite = Boolean(process.versions.sqlite);
const hasQuic = hasCrypto && !!process.features.quic;
-const hasLocalStorage = (() => {
- try {
- return hasSQLite && globalThis.localStorage !== undefined;
- } catch {
- return false;
- }
-})();
-
/**
* Parse test metadata from the specified file.
* @param {string} filename - The name of the file to parse.
@@ -359,6 +351,7 @@ const knownGlobals = new Set([
'CompressionStream',
'DecompressionStream',
'Storage',
+ 'localStorage',
'sessionStorage',
].forEach((i) => {
if (globalThis[i] !== undefined) {
@@ -373,10 +366,6 @@ if (hasCrypto) {
knownGlobals.add(globalThis.SubtleCrypto);
}
-if (hasLocalStorage) {
- knownGlobals.add(globalThis.localStorage);
-}
-
const { Worker } = require('node:worker_threads');
knownGlobals.add(Worker);
@@ -401,11 +390,6 @@ if (process.env.NODE_TEST_KNOWN_GLOBALS !== '0') {
if (val === 'crypto' && !hasCrypto) {
continue;
}
- // globalThis.localStorage is a getter that throws if Node.js was
- // executed without a --localstorage-file path.
- if (val === 'localStorage' && !hasLocalStorage) {
- continue;
- }
if (!knownGlobals.has(globalThis[val])) {
leaked.push(val);
}
@@ -956,7 +940,6 @@ const common = {
hasQuic,
hasInspector,
hasSQLite,
- hasLocalStorage,
invalidArgTypeHelper,
isAlive,
isASan,
diff --git a/test/common/index.mjs b/test/common/index.mjs
index 7516eb68a53b7a..471d58b7dbbfe0 100644
--- a/test/common/index.mjs
+++ b/test/common/index.mjs
@@ -19,7 +19,6 @@ const {
hasQuic,
hasInspector,
hasSQLite,
- hasLocalStorage,
hasIntl,
hasIPv6,
isAIX,
@@ -73,7 +72,6 @@ export {
hasQuic,
hasInspector,
hasSQLite,
- hasLocalStorage,
hasIntl,
hasIPv6,
isAIX,
diff --git a/test/parallel/test-assert-checktag.js b/test/parallel/test-assert-checktag.js
index b1c3660ab4a693..b86a1bde7f096d 100644
--- a/test/parallel/test-assert-checktag.js
+++ b/test/parallel/test-assert-checktag.js
@@ -1,5 +1,5 @@
'use strict';
-const { hasCrypto, hasLocalStorage } = require('../common');
+const { hasCrypto } = require('../common');
const { test } = require('node:test');
const assert = require('assert');
@@ -12,7 +12,7 @@ const assert = require('assert');
if (process.stdout.isTTY)
process.env.NODE_DISABLE_COLORS = '1';
-test({ skip: !hasCrypto || !hasLocalStorage }, () => {
+test('', { skip: !hasCrypto }, () => {
// See https://github.com/nodejs/node/issues/10258
{
const date = new Date('2016');
diff --git a/test/parallel/test-crypto-rsa-pss-default-salt-length.js b/test/parallel/test-crypto-rsa-pss-default-salt-length.js
new file mode 100644
index 00000000000000..fc86b52931fc74
--- /dev/null
+++ b/test/parallel/test-crypto-rsa-pss-default-salt-length.js
@@ -0,0 +1,37 @@
+'use strict';
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const crypto = require('crypto');
+
+const fixtures = require('../common/fixtures');
+
+const privateKey = crypto.createPrivateKey(fixtures.readKey('rsa_private.pem', 'ascii'));
+const publicKey = crypto.createPublicKey(fixtures.readKey('rsa_public.pem', 'ascii'));
+
+const data = crypto.randomBytes(32);
+
+for (const digest of ['sha256', 'sha384', 'sha512']) {
+ const hLen = crypto.hash(digest, data, 'buffer').byteLength;
+ const maxSaltLength =
+ privateKey.asymmetricKeyDetails.modulusLength / 8 - hLen - 2;
+
+ const sig = crypto.sign(digest, data, {
+ key: privateKey,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+ // No "saltLength" provided, documented default RSA_PSS_SALTLEN_MAX_SIGN expected
+ });
+
+ assert.strictEqual(crypto.verify(
+ digest,
+ data,
+ {
+ key: publicKey,
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: maxSaltLength,
+ },
+ sig
+ ), true);
+}
diff --git a/test/parallel/test-webstorage.js b/test/parallel/test-webstorage.js
index cc5f0366f7116e..bc48c9643773b5 100644
--- a/test/parallel/test-webstorage.js
+++ b/test/parallel/test-webstorage.js
@@ -41,13 +41,13 @@ test('sessionStorage is not persisted', async () => {
assert.strictEqual((await readdir(tmpdir.path)).length, 0);
});
-test('localStorage throws without --localstorage-file', async () => {
+test('localStorage emits a warning when used without --localstorage-file ', async () => {
const cp = await spawnPromisified(process.execPath, [
- '-e', 'localStorage',
+ '-pe', 'localStorage.length',
]);
- assert.strictEqual(cp.code, 1);
+ assert.strictEqual(cp.code, 0);
assert.strictEqual(cp.signal, null);
- assert.match(cp.stderr, /SecurityError:/);
+ assert.match(cp.stderr, /Warning: `--localstorage-file` was provided without a valid path/);
});
test('localStorage is not persisted if it is unused', async () => {
|