lib: throw from localStorage getter on missing storage path

PR-URL: https://github.com/nodejs/node/pull/60351
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Matthew Aitken <maitken033380023@gmail.com>
This commit is contained in:
René 2025-10-31 19:48:29 +00:00 committed by GitHub
parent 1bb853c9d9
commit 2fb3c7eef5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 35 additions and 33 deletions

View File

@ -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,34 +21,17 @@ 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 === '') {
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;
}
return false;
},
};
lazyLocalStorage = new Proxy({}, handler);
} else {
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location));
throw lazyDOMException(
'Cannot initialize local storage without a `--localstorage-file` path',
'SecurityError',
);
}
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location));
}
return lazyLocalStorage;

View File

@ -59,6 +59,14 @@ 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.
@ -350,7 +358,6 @@ const knownGlobals = new Set([
'CompressionStream',
'DecompressionStream',
'Storage',
'localStorage',
'sessionStorage',
].forEach((i) => {
if (globalThis[i] !== undefined) {
@ -365,6 +372,10 @@ if (hasCrypto) {
knownGlobals.add(globalThis.SubtleCrypto);
}
if (hasLocalStorage) {
knownGlobals.add(globalThis.localStorage);
}
const { Worker } = require('node:worker_threads');
knownGlobals.add(Worker);
@ -389,6 +400,11 @@ 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);
}
@ -933,6 +949,7 @@ const common = {
hasQuic,
hasInspector,
hasSQLite,
hasLocalStorage,
invalidArgTypeHelper,
isAlive,
isASan,

View File

@ -19,6 +19,7 @@ const {
hasQuic,
hasInspector,
hasSQLite,
hasLocalStorage,
hasIntl,
hasIPv6,
isAIX,
@ -71,6 +72,7 @@ export {
hasQuic,
hasInspector,
hasSQLite,
hasLocalStorage,
hasIntl,
hasIPv6,
isAIX,

View File

@ -1,5 +1,5 @@
'use strict';
const { hasCrypto } = require('../common');
const { hasCrypto, hasLocalStorage } = 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 }, () => {
test({ skip: !hasCrypto || !hasLocalStorage }, () => {
// See https://github.com/nodejs/node/issues/10258
{
const date = new Date('2016');

View File

@ -41,13 +41,13 @@ test('sessionStorage is not persisted', async () => {
assert.strictEqual((await readdir(tmpdir.path)).length, 0);
});
test('localStorage emits a warning when used without --localstorage-file ', async () => {
test('localStorage throws without --localstorage-file', async () => {
const cp = await spawnPromisified(process.execPath, [
'-pe', 'localStorage.length',
'-e', 'localStorage',
]);
assert.strictEqual(cp.code, 0);
assert.strictEqual(cp.code, 1);
assert.strictEqual(cp.signal, null);
assert.match(cp.stderr, /Warning: `--localstorage-file` was provided without a valid path/);
assert.match(cp.stderr, /SecurityError:/);
});
test('localStorage is not persisted if it is unused', async () => {