Skip to content

Commit 2bf46ae

Browse files
boneskulltargos
authored andcommitted
process: add allowedNodeEnvironmentFlags property
`process.allowedNodeEnvironmentFlags` provides an API to validate and list flags as specified in `NODE_OPTIONS` from user code. Refs: #17740 Signed-off-by: Christopher Hiller <[email protected]> PR-URL: #19335 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Gibson Fahnestock <[email protected]> Reviewed-By: John-David Dalton <[email protected]> Reviewed-By: Sam Ruby <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 034ba73 commit 2bf46ae

File tree

7 files changed

+299
-1
lines changed

7 files changed

+299
-1
lines changed

doc/api/process.md

+51
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,55 @@ generate a core file.
416416

417417
This feature is not available in [`Worker`][] threads.
418418

419+
## process.allowedNodeEnvironmentFlags
420+
<!-- YAML
421+
added: REPLACEME
422+
-->
423+
424+
* {Set}
425+
426+
The `process.allowedNodeEnvironmentFlags` property is a special,
427+
read-only `Set` of flags allowable within the [`NODE_OPTIONS`][]
428+
environment variable.
429+
430+
`process.allowedNodeEnvironmentFlags` extends `Set`, but overrides
431+
`Set.prototype.has` to recognize several different possible flag
432+
representations. `process.allowedNodeEnvironmentFlags.has()` will
433+
return `true` in the following cases:
434+
435+
- Flags may omit leading single (`-`) or double (`--`) dashes; e.g.,
436+
`inspect-brk` for `--inspect-brk`, or `r` for `-r`.
437+
- Flags passed through to V8 (as listed in `--v8-options`) may replace
438+
one or more *non-leading* dashes for an underscore, or vice-versa;
439+
e.g., `--perf_basic_prof`, `--perf-basic-prof`, `--perf_basic-prof`,
440+
etc.
441+
- Flags may contain one or more equals (`=`) characters; all
442+
characters after and including the first equals will be ignored;
443+
e.g., `--stack-trace-limit=100`.
444+
- Flags *must* be allowable within [`NODE_OPTIONS`][].
445+
446+
When iterating over `process.allowedNodeEnvironmentFlags`, flags will
447+
appear only *once*; each will begin with one or more dashes. Flags
448+
passed through to V8 will contain underscores instead of non-leading
449+
dashes:
450+
451+
```js
452+
process.allowedNodeEnvironmentFlags.forEach((flag) => {
453+
// -r
454+
// --inspect-brk
455+
// --abort_on_uncaught_exception
456+
// ...
457+
});
458+
```
459+
460+
The methods `add()`, `clear()`, and `delete()` of
461+
`process.allowedNodeEnvironmentFlags` do nothing, and will fail
462+
silently.
463+
464+
If Node.js was compiled *without* [`NODE_OPTIONS`][] support (shown in
465+
[`process.config`][]), `process.allowedNodeEnvironmentFlags` will
466+
contain what *would have* been allowable.
467+
419468
## process.arch
420469
<!-- YAML
421470
added: v0.5.0
@@ -2061,8 +2110,10 @@ cases:
20612110
[`end()`]: stream.html#stream_writable_end_chunk_encoding_callback
20622111
[`net.Server`]: net.html#net_class_net_server
20632112
[`net.Socket`]: net.html#net_class_net_socket
2113+
[`NODE_OPTIONS`]: cli.html#cli_node_options_options
20642114
[`os.constants.dlopen`]: os.html#os_dlopen_constants
20652115
[`process.argv`]: #process_process_argv
2116+
[`process.config`]: #process_process_config
20662117
[`process.execPath`]: #process_process_execpath
20672118
[`process.exit()`]: #process_process_exit_code
20682119
[`process.exitCode`]: #process_process_exitcode

lib/internal/bootstrap/node.js

+89
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@
194194

195195
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE);
196196

197+
setupAllowedFlags();
198+
197199
// There are various modes that Node can run in. The most common two
198200
// are running from a script and running the REPL - but there are a few
199201
// others like the debugger or running --eval arguments. Here we decide
@@ -593,5 +595,92 @@
593595
new vm.Script(source, { displayErrors: true, filename });
594596
}
595597

598+
function setupAllowedFlags() {
599+
// This builds process.allowedNodeEnvironmentFlags
600+
// from data in the config binding
601+
602+
const replaceDashesRegex = /-/g;
603+
const leadingDashesRegex = /^--?/;
604+
const trailingValuesRegex = /=.*$/;
605+
606+
// Save references so user code does not interfere
607+
const replace = Function.call.bind(String.prototype.replace);
608+
const has = Function.call.bind(Set.prototype.has);
609+
const test = Function.call.bind(RegExp.prototype.test);
610+
611+
const {
612+
allowedV8EnvironmentFlags,
613+
allowedNodeEnvironmentFlags
614+
} = process.binding('config');
615+
616+
const trimLeadingDashes = (flag) => replace(flag, leadingDashesRegex, '');
617+
618+
// Save these for comparison against flags provided to
619+
// process.allowedNodeEnvironmentFlags.has() which lack leading dashes.
620+
// Avoid interference w/ user code by flattening `Set.prototype` into
621+
// each object.
622+
const [nodeFlags, v8Flags] = [
623+
allowedNodeEnvironmentFlags, allowedV8EnvironmentFlags
624+
].map((flags) => Object.defineProperties(
625+
new Set(flags.map(trimLeadingDashes)),
626+
Object.getOwnPropertyDescriptors(Set.prototype))
627+
);
628+
629+
class NodeEnvironmentFlagsSet extends Set {
630+
constructor(...args) {
631+
super(...args);
632+
633+
// the super constructor consumes `add`, but
634+
// disallow any future adds.
635+
this.add = () => this;
636+
}
637+
638+
delete() {
639+
// noop, `Set` API compatible
640+
return false;
641+
}
642+
643+
clear() {
644+
// noop
645+
}
646+
647+
has(key) {
648+
// This will return `true` based on various possible
649+
// permutations of a flag, including present/missing leading
650+
// dash(es) and/or underscores-for-dashes in the case of V8-specific
651+
// flags. Strips any values after `=`, inclusive.
652+
if (typeof key === 'string') {
653+
key = replace(key, trailingValuesRegex, '');
654+
if (test(leadingDashesRegex, key)) {
655+
return has(this, key) ||
656+
has(v8Flags,
657+
replace(
658+
replace(
659+
key,
660+
leadingDashesRegex,
661+
''
662+
),
663+
replaceDashesRegex,
664+
'_'
665+
)
666+
);
667+
}
668+
return has(nodeFlags, key) ||
669+
has(v8Flags, replace(key, replaceDashesRegex, '_'));
670+
}
671+
return false;
672+
}
673+
}
674+
675+
Object.freeze(NodeEnvironmentFlagsSet.prototype.constructor);
676+
Object.freeze(NodeEnvironmentFlagsSet.prototype);
677+
678+
process.allowedNodeEnvironmentFlags = Object.freeze(
679+
new NodeEnvironmentFlagsSet(
680+
allowedNodeEnvironmentFlags.concat(allowedV8EnvironmentFlags)
681+
)
682+
);
683+
}
684+
596685
startup();
597686
});

src/node.cc

+62
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,68 @@ const char* signo_string(int signo) {
587587
}
588588
}
589589

590+
// These are all flags available for use with NODE_OPTIONS.
591+
//
592+
// Disallowed flags:
593+
// These flags cause Node to do things other than run scripts:
594+
// --version / -v
595+
// --eval / -e
596+
// --print / -p
597+
// --check / -c
598+
// --interactive / -i
599+
// --prof-process
600+
// --v8-options
601+
// These flags are disallowed because security:
602+
// --preserve-symlinks
603+
const char* const environment_flags[] = {
604+
// Node options, sorted in `node --help` order for ease of comparison.
605+
"--enable-fips",
606+
"--experimental-modules",
607+
"--experimenatl-repl-await",
608+
"--experimental-vm-modules",
609+
"--experimental-worker",
610+
"--force-fips",
611+
"--icu-data-dir",
612+
"--inspect",
613+
"--inspect-brk",
614+
"--inspect-port",
615+
"--loader",
616+
"--napi-modules",
617+
"--no-deprecation",
618+
"--no-force-async-hooks-checks",
619+
"--no-warnings",
620+
"--openssl-config",
621+
"--pending-deprecation",
622+
"--redirect-warnings",
623+
"--require",
624+
"--throw-deprecation",
625+
"--tls-cipher-list",
626+
"--trace-deprecation",
627+
"--trace-event-categories",
628+
"--trace-event-file-pattern",
629+
"--trace-events-enabled",
630+
"--trace-sync-io",
631+
"--trace-warnings",
632+
"--track-heap-objects",
633+
"--use-bundled-ca",
634+
"--use-openssl-ca",
635+
"--v8-pool-size",
636+
"--zero-fill-buffers",
637+
"-r"
638+
};
639+
640+
// V8 options (define with '_', which allows '-' or '_')
641+
const char* const v8_environment_flags[] = {
642+
"--abort_on_uncaught_exception",
643+
"--max_old_space_size",
644+
"--perf_basic_prof",
645+
"--perf_prof",
646+
"--stack_trace_limit",
647+
};
648+
649+
int v8_environment_flags_count = arraysize(v8_environment_flags);
650+
int environment_flags_count = arraysize(environment_flags);
651+
590652
// Look up environment variable unless running as setuid root.
591653
bool SafeGetenv(const char* key, std::string* text) {
592654
#if !defined(__CloudABI__) && !defined(_WIN32)

src/node_config.cc

+17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
namespace node {
77

8+
using v8::Array;
89
using v8::Boolean;
910
using v8::Context;
1011
using v8::Integer;
@@ -132,6 +133,22 @@ static void Initialize(Local<Object> target,
132133
READONLY_PROPERTY(debug_options_obj,
133134
"inspectorEnabled",
134135
Boolean::New(isolate, debug_options->inspector_enabled));
136+
137+
Local<Array> environmentFlags = Array::New(env->isolate(),
138+
environment_flags_count);
139+
READONLY_PROPERTY(target, "allowedNodeEnvironmentFlags", environmentFlags);
140+
for (int i = 0; i < environment_flags_count; ++i) {
141+
environmentFlags->Set(i, OneByteString(env->isolate(),
142+
environment_flags[i]));
143+
}
144+
145+
Local<Array> v8EnvironmentFlags = Array::New(env->isolate(),
146+
v8_environment_flags_count);
147+
READONLY_PROPERTY(target, "allowedV8EnvironmentFlags", v8EnvironmentFlags);
148+
for (int i = 0; i < v8_environment_flags_count; ++i) {
149+
v8EnvironmentFlags->Set(i, OneByteString(env->isolate(),
150+
v8_environment_flags[i]));
151+
}
135152
} // InitConfig
136153

137154
} // namespace node

src/node_internals.h

+5
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ extern bool v8_initialized;
178178

179179
extern std::shared_ptr<PerProcessOptions> per_process_opts;
180180

181+
extern const char* const environment_flags[];
182+
extern int environment_flags_count;
183+
extern const char* const v8_environment_flags[];
184+
extern int v8_environment_flags_count;
185+
181186
// Forward declaration
182187
class Environment;
183188

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
require('../common');
5+
6+
// assert legit flags are allowed, and bogus flags are disallowed
7+
{
8+
const goodFlags = [
9+
'--inspect-brk',
10+
'inspect-brk',
11+
'--perf_basic_prof',
12+
'--perf-basic-prof',
13+
'perf-basic-prof',
14+
'--perf_basic-prof',
15+
'perf_basic-prof',
16+
'perf_basic_prof',
17+
'-r',
18+
'r',
19+
'--stack-trace-limit=100',
20+
'--stack-trace-limit=-=xX_nodejs_Xx=-'
21+
];
22+
23+
const badFlags = [
24+
'--inspect_brk',
25+
'INSPECT-BRK',
26+
'--INSPECT-BRK',
27+
'--r',
28+
'-R',
29+
'---inspect-brk',
30+
'--cheeseburgers'
31+
];
32+
33+
goodFlags.forEach((flag) => {
34+
assert.strictEqual(
35+
process.allowedNodeEnvironmentFlags.has(flag),
36+
true,
37+
`flag should be in set: ${flag}`
38+
);
39+
});
40+
41+
badFlags.forEach((flag) => {
42+
assert.strictEqual(
43+
process.allowedNodeEnvironmentFlags.has(flag),
44+
false,
45+
`flag should not be in set: ${flag}`
46+
);
47+
});
48+
}
49+
50+
// assert all "canonical" flags begin with dash(es)
51+
{
52+
process.allowedNodeEnvironmentFlags.forEach((flag) => {
53+
assert.strictEqual(/^--?[a-z8_-]+$/.test(flag), true);
54+
});
55+
}
56+
57+
// assert immutability of process.allowedNodeEnvironmentFlags
58+
{
59+
assert.strictEqual(Object.isFrozen(process.allowedNodeEnvironmentFlags),
60+
true);
61+
62+
process.allowedNodeEnvironmentFlags.add('foo');
63+
assert.strictEqual(process.allowedNodeEnvironmentFlags.has('foo'), false);
64+
process.allowedNodeEnvironmentFlags.forEach((flag) => {
65+
assert.strictEqual(flag === 'foo', false);
66+
});
67+
68+
process.allowedNodeEnvironmentFlags.clear();
69+
assert.strictEqual(process.allowedNodeEnvironmentFlags.size > 0, true);
70+
71+
const size = process.allowedNodeEnvironmentFlags.size;
72+
process.allowedNodeEnvironmentFlags.delete('-r');
73+
assert.strictEqual(process.allowedNodeEnvironmentFlags.size, size);
74+
}

tools/doc/type-parser.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const jsPrimitives = {
1616
const jsGlobalObjectsUrl = `${jsDocPrefix}Reference/Global_Objects/`;
1717
const jsGlobalTypes = [
1818
'Array', 'ArrayBuffer', 'DataView', 'Date', 'Error', 'EvalError', 'Function',
19-
'Object', 'Promise', 'RangeError', 'ReferenceError', 'RegExp',
19+
'Object', 'Promise', 'RangeError', 'ReferenceError', 'RegExp', 'Set',
2020
'SharedArrayBuffer', 'SyntaxError', 'TypeError', 'TypedArray', 'URIError',
2121
'Uint8Array',
2222
];

0 commit comments

Comments
 (0)