Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: add flag for disabling globals #50554

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,32 @@ Affects the default output directory of:
* [`--heap-prof-dir`][]
* [`--redirect-warnings`][]

### `--disable-global=name`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.2 - Release candidate

Remove specified global variables from the global scope. This flag can be used
multiple times to remove multiple variables.

Not all global variables can be removed. The currently supported variables are:

<!-- Keep alphabetized -->
- `CustomEvent`
- `fetch`; removing this also removes `FormData`, `Headers`, `Request`, and
`Response`
- `navigator`
- `WebSocket`

```bash
node --disable-global=CustomEvent --disable-global=navigator \
--eval 'console.log(typeof CustomEvent, typeof navigator)'
undefined undefined
```

### `--disable-proto=mode`

<!-- YAML
Expand Down
4 changes: 0 additions & 4 deletions lib/internal/bootstrap/web/exposed-window-or-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ exposeLazyInterfaces(globalThis, 'perf_hooks', [

defineReplaceableLazyAttribute(globalThis, 'perf_hooks', ['performance']);

// https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object
exposeLazyInterfaces(globalThis, 'internal/navigator', ['Navigator']);
defineReplaceableLazyAttribute(globalThis, 'internal/navigator', ['navigator'], false);

// https://w3c.github.io/FileAPI/#creating-revoking
const { installObjectURLMethods } = require('internal/url');
installObjectURLMethods();
68 changes: 47 additions & 21 deletions lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const {
ArrayPrototypeForEach,
ArrayPrototypeMap,
Date,
DatePrototypeGetDate,
DatePrototypeGetFullYear,
Expand All @@ -14,8 +15,10 @@
ObjectDefineProperty,
ObjectGetOwnPropertyDescriptor,
SafeMap,
SafeSet,
String,
StringPrototypeStartsWith,
StringPrototypeToLowerCase,
Symbol,
SymbolAsyncDispose,
SymbolDispose,
Expand Down Expand Up @@ -78,9 +81,7 @@
setupTraceCategoryState();
setupInspectorHooks();
setupWarningHandler();
setupUndici();
setupWebCrypto();
setupCustomEvent();
setupWebGlobals();
setupCodeCoverage();
setupDebugEnv();
// Process initial diagnostic reporting configuration, if present.
Expand Down Expand Up @@ -278,13 +279,51 @@
}
}

// https://fetch.spec.whatwg.org/
// https://websockets.spec.whatwg.org/
function setupUndici() {
function setupWebGlobals() {
if (getEmbedderOptions().noBrowserGlobals) {
return;
}

if (!getOptionValue('--no-experimental-global-webcrypto')) {
// `--no-experimental-global-webcrypto` doesn't disable the `crypto` global, it just changes what
// `globalThis.crypto` refers to. There is currently no way to disable the `crypto` global.
setupWebCrypto();
}

const disabledGlobalsLowercased = new SafeSet(ArrayPrototypeMap(
getOptionValue('--disable-global'), value => StringPrototypeToLowerCase(value)));

Check failure on line 294 in lib/internal/process/pre_execution.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Expected parentheses around arrow function argument

// `CustomEvent`
if (!getOptionValue('--no-experimental-global-customevent') &&
!disabledGlobalsLowercased.has('customevent')) {
setupCustomEvent();
}

// `navigator`
if (!disabledGlobalsLowercased.has('navigator')) {
setupNavigator();
}

// `fetch`, `FormData`, `Headers`, `Request`, `Response`
const setupFetch = !getOptionValue('--no-experimental-fetch') &&
!disabledGlobalsLowercased.has('fetch');
// `WebSocket`
const setupWebSocket = getOptionValue('--experimental-websocket') &&
!disabledGlobalsLowercased.has('websocket');
if (setupFetch || setupWebSocket) {
setupUndici(setupFetch, setupWebSocket);
}
}

function setupNavigator() {
// https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object
exposeLazyInterfaces(globalThis, 'internal/navigator', ['Navigator']);
defineReplaceableLazyAttribute(globalThis, 'internal/navigator', ['navigator'], false);
}

// https://fetch.spec.whatwg.org/
// https://websockets.spec.whatwg.org/
function setupUndici(setupFetch = true, setupWebSocket = true) {
let undici;
function lazyUndici() {
if (undici) {
Expand All @@ -308,7 +347,7 @@
};
}

if (!getOptionValue('--no-experimental-fetch')) {
if (setupFetch) {
// Fetch is meant to return a Promise, but not be async.
function fetch(input, init = undefined) {
return lazyUndici().fetch(input, init);
Expand All @@ -329,21 +368,14 @@
});
}

if (getOptionValue('--experimental-websocket')) {
if (setupWebSocket) {
ObjectDefineProperties(globalThis, {
WebSocket: lazyInterface('WebSocket'),
});
}
}

// TODO(aduh95): move this to internal/bootstrap/web/* when the CLI flag is
// removed.
function setupWebCrypto() {
if (getEmbedderOptions().noBrowserGlobals ||
getOptionValue('--no-experimental-global-webcrypto')) {
return;
}

if (internalBinding('config').hasOpenSSL) {
defineReplaceableLazyAttribute(
globalThis,
Expand Down Expand Up @@ -384,13 +416,7 @@
}
}

// TODO(daeyeon): move this to internal/bootstrap/web/* when the CLI flag is
// removed.
function setupCustomEvent() {
if (getEmbedderOptions().noBrowserGlobals ||
getOptionValue('--no-experimental-global-customevent')) {
return;
}
const { CustomEvent } = require('internal/event_target');
exposeInterface(globalThis, 'CustomEvent', CustomEvent);
}
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
" (default: current working directory)",
&EnvironmentOptions::diagnostic_dir,
kAllowedInEnvvar);
AddOption("--disable-global",
"remove global variable",
&EnvironmentOptions::disable_global,
kAllowedInEnvvar);
AddOption("--dns-result-order",
"set default value of verbatim in dns.lookup. Options are "
"'ipv4first' (IPv4 addresses are placed before IPv6 addresses) "
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class EnvironmentOptions : public Options {
bool abort_on_uncaught_exception = false;
std::vector<std::string> conditions;
bool detect_module = false;
std::vector<std::string> disable_global;
std::string dns_result_order;
bool enable_source_maps = false;
bool experimental_fetch = true;
Expand Down
56 changes: 56 additions & 0 deletions test/parallel/test-cli-disable-global.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { spawnPromisified } from '../common/index.mjs';
import { describe, it } from 'node:test';
import { strictEqual } from 'node:assert';


describe('--disable-global', { concurrency: true }, () => {
it('disables one global', async () => {
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
'--disable-global', 'navigator',
'--eval', 'console.log(typeof navigator)',
]);

strictEqual(stderr, '');
strictEqual(stdout, 'undefined\n');
strictEqual(code, 0);
strictEqual(signal, null);
});

it('disables all the globals in the supported list', async () => {
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
'--disable-global', 'CustomEvent',
'--disable-global', 'fetch',
'--disable-global', 'navigator',
'--disable-global', 'WebSocket',
'--print',
`[
'CustomEvent',
'fetch', 'FormData', 'Headers', 'Request', 'Response',
'navigator',
'WebSocket',
].filter(name => typeof globalThis[name] !== 'undefined')`,
]);

strictEqual(stderr, '');
strictEqual(stdout, '[]\n');
strictEqual(code, 0);
strictEqual(signal, null);
});

it('is case insensitive', async () => {
const { stdout, stderr, code, signal } = await spawnPromisified(process.execPath, [
'--disable-global', 'customevent',
'--disable-global', 'websocket',
'--print',
`[
'CustomEvent',
'WebSocket',
].filter(name => typeof globalThis[name] !== 'undefined')`,
]);

strictEqual(stderr, '');
strictEqual(stdout, '[]\n');
strictEqual(code, 0);
strictEqual(signal, null);
});
});
Loading