Skip to content

Commit 413c16e

Browse files
authored
src,permission: add multiple allow-fs-* flags
Support for a single comma separates list for allow-fs-* flags is removed. Instead now multiple flags can be passed to allow multiple paths. Fixes: nodejs/security-wg#1039 PR-URL: #49047 Reviewed-By: Rafael Gonzaga <[email protected]> Reviewed-By: Marco Ippolito <[email protected]>
1 parent 3c6a1c6 commit 413c16e

24 files changed

+161
-34
lines changed

doc/api/cli.md

+18-4
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ Error: Access to this API has been restricted
145145

146146
<!-- YAML
147147
added: v20.0.0
148+
changes:
149+
- version: REPLACEME
150+
pr-url: https://github.com/nodejs/node/pull/49047
151+
description: Paths delimited by comma (`,`) are no longer allowed.
148152
-->
149153

150154
> Stability: 1 - Experimental
@@ -155,8 +159,11 @@ the [Permission Model][].
155159
The valid arguments for the `--allow-fs-read` flag are:
156160

157161
* `*` - To allow all `FileSystemRead` operations.
158-
* Paths delimited by comma (`,`) to allow only matching `FileSystemRead`
159-
operations.
162+
* Multiple paths can be allowed using multiple `--allow-fs-read` flags.
163+
Example `--allow-fs-read=/folder1/ --allow-fs-read=/folder1/`
164+
165+
Paths delimited by comma (`,`) are no longer allowed.
166+
When passing a single flag with a comma a warning will be diplayed
160167

161168
Examples can be found in the [File System Permissions][] documentation.
162169

@@ -192,6 +199,10 @@ node --experimental-permission --allow-fs-read=/path/to/index.js index.js
192199

193200
<!-- YAML
194201
added: v20.0.0
202+
changes:
203+
- version: REPLACEME
204+
pr-url: https://github.com/nodejs/node/pull/49047
205+
description: Paths delimited by comma (`,`) are no longer allowed.
195206
-->
196207

197208
> Stability: 1 - Experimental
@@ -202,8 +213,11 @@ the [Permission Model][].
202213
The valid arguments for the `--allow-fs-write` flag are:
203214

204215
* `*` - To allow all `FileSystemWrite` operations.
205-
* Paths delimited by comma (`,`) to allow only matching `FileSystemWrite`
206-
operations.
216+
* Multiple paths can be allowed using multiple `--allow-fs-read` flags.
217+
Example `--allow-fs-read=/folder1/ --allow-fs-read=/folder1/`
218+
219+
Paths delimited by comma (`,`) are no longer allowed.
220+
When passing a single flag with a comma a warning will be diplayed
207221

208222
Examples can be found in the [File System Permissions][] documentation.
209223

doc/api/permissions.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ Example:
532532
* `--allow-fs-write=*` - It will allow all `FileSystemWrite` operations.
533533
* `--allow-fs-write=/tmp/` - It will allow `FileSystemWrite` access to the `/tmp/`
534534
folder.
535-
* `--allow-fs-read=/tmp/,/home/.gitignore` - It allows `FileSystemRead` access
535+
* `--allow-fs-read=/tmp/ --allow-fs-read=/home/.gitignore` - It allows `FileSystemRead` access
536536
to the `/tmp/` folder **and** the `/home/.gitignore` path.
537537

538538
Wildcards are supported too:

lib/internal/process/pre_execution.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,22 @@ function initializePermission() {
554554
'It could invalidate the permission model.', 'SecurityWarning');
555555
}
556556
}
557+
const warnCommaFlags = [
558+
'--allow-fs-read',
559+
'--allow-fs-write',
560+
];
561+
for (const flag of warnCommaFlags) {
562+
const value = getOptionValue(flag);
563+
if (value.length === 1 && value[0].includes(',')) {
564+
process.emitWarning(
565+
`The ${flag} CLI flag has changed. ` +
566+
'Passing a comma-separated list of paths is no longer valid. ' +
567+
'Documentation can be found at ' +
568+
'https://nodejs.org/api/permissions.html#file-system-permissions',
569+
'Warning',
570+
);
571+
}
572+
}
557573

558574
ObjectDefineProperty(process, 'permission', {
559575
__proto__: null,
@@ -572,7 +588,8 @@ function initializePermission() {
572588
'--allow-worker',
573589
];
574590
ArrayPrototypeForEach(availablePermissionFlags, (flag) => {
575-
if (getOptionValue(flag)) {
591+
const value = getOptionValue(flag);
592+
if (value.length) {
576593
throw new ERR_MISSING_OPTION('--experimental-permission');
577594
}
578595
});

src/env.cc

+3-3
Original file line numberDiff line numberDiff line change
@@ -875,12 +875,12 @@ Environment::Environment(IsolateData* isolate_data,
875875
// unless explicitly allowed by the user
876876
options_->allow_native_addons = false;
877877
flags_ = flags_ | EnvironmentFlags::kNoCreateInspector;
878-
permission()->Apply("*", permission::PermissionScope::kInspector);
878+
permission()->Apply({"*"}, permission::PermissionScope::kInspector);
879879
if (!options_->allow_child_process) {
880-
permission()->Apply("*", permission::PermissionScope::kChildProcess);
880+
permission()->Apply({"*"}, permission::PermissionScope::kChildProcess);
881881
}
882882
if (!options_->allow_worker_threads) {
883-
permission()->Apply("*", permission::PermissionScope::kWorkerThreads);
883+
permission()->Apply({"*"}, permission::PermissionScope::kWorkerThreads);
884884
}
885885

886886
if (!options_->allow_fs_read.empty()) {

src/node_options.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ class EnvironmentOptions : public Options {
121121
std::string experimental_policy_integrity;
122122
bool has_policy_integrity_string = false;
123123
bool experimental_permission = false;
124-
std::string allow_fs_read;
125-
std::string allow_fs_write;
124+
std::vector<std::string> allow_fs_read;
125+
std::vector<std::string> allow_fs_write;
126126
bool allow_child_process = false;
127127
bool allow_worker_threads = false;
128128
bool experimental_repl_await = true;

src/permission/child_process_permission.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace permission {
99

1010
// Currently, ChildProcess manage a single state
1111
// Once denied, it's always denied
12-
void ChildProcessPermission::Apply(const std::string& allow,
12+
void ChildProcessPermission::Apply(const std::vector<std::string>& allow,
1313
PermissionScope scope) {
1414
deny_all_ = true;
1515
}

src/permission/child_process_permission.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace permission {
1212

1313
class ChildProcessPermission final : public PermissionBase {
1414
public:
15-
void Apply(const std::string& allow, PermissionScope scope) override;
15+
void Apply(const std::vector<std::string>& allow,
16+
PermissionScope scope) override;
1617
bool is_granted(PermissionScope perm,
1718
const std::string_view& param = "") override;
1819

src/permission/fs_permission.cc

+4-2
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,11 @@ namespace permission {
116116

117117
// allow = '*'
118118
// allow = '/tmp/,/home/example.js'
119-
void FSPermission::Apply(const std::string& allow, PermissionScope scope) {
119+
void FSPermission::Apply(const std::vector<std::string>& allow,
120+
PermissionScope scope) {
120121
using std::string_view_literals::operator""sv;
121-
for (const std::string_view res : SplitString(allow, ","sv)) {
122+
123+
for (const std::string_view res : allow) {
122124
if (res == "*"sv) {
123125
if (scope == PermissionScope::kFileSystemRead) {
124126
deny_all_in_ = false;

src/permission/fs_permission.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ namespace permission {
1515

1616
class FSPermission final : public PermissionBase {
1717
public:
18-
void Apply(const std::string& allow, PermissionScope scope) override;
18+
void Apply(const std::vector<std::string>& allow,
19+
PermissionScope scope) override;
1920
bool is_granted(PermissionScope perm, const std::string_view& param) override;
2021

2122
struct RadixTree {

src/permission/inspector_permission.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace permission {
88

99
// Currently, Inspector manage a single state
1010
// Once denied, it's always denied
11-
void InspectorPermission::Apply(const std::string& allow,
11+
void InspectorPermission::Apply(const std::vector<std::string>& allow,
1212
PermissionScope scope) {
1313
deny_all_ = true;
1414
}

src/permission/inspector_permission.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace permission {
1212

1313
class InspectorPermission final : public PermissionBase {
1414
public:
15-
void Apply(const std::string& allow, PermissionScope scope) override;
15+
void Apply(const std::vector<std::string>& allow,
16+
PermissionScope scope) override;
1617
bool is_granted(PermissionScope perm,
1718
const std::string_view& param = "") override;
1819

src/permission/permission.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ void Permission::EnablePermissions() {
130130
}
131131
}
132132

133-
void Permission::Apply(const std::string& allow, PermissionScope scope) {
133+
void Permission::Apply(const std::vector<std::string>& allow,
134+
PermissionScope scope) {
134135
auto permission = nodes_.find(scope);
135136
if (permission != nodes_.end()) {
136137
permission->second->Apply(allow, scope);

src/permission/permission.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class Permission {
4949
const std::string_view& res);
5050

5151
// CLI Call
52-
void Apply(const std::string& allow, PermissionScope scope);
52+
void Apply(const std::vector<std::string>& allow, PermissionScope scope);
5353
void EnablePermissions();
5454

5555
private:

src/permission/permission_base.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ enum class PermissionScope {
3939

4040
class PermissionBase {
4141
public:
42-
virtual void Apply(const std::string& allow, PermissionScope scope) = 0;
42+
virtual void Apply(const std::vector<std::string>& allow,
43+
PermissionScope scope) = 0;
4344
virtual bool is_granted(PermissionScope perm,
4445
const std::string_view& param = "") = 0;
4546
};

src/permission/worker_permission.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ namespace permission {
99

1010
// Currently, PolicyDenyWorker manage a single state
1111
// Once denied, it's always denied
12-
void WorkerPermission::Apply(const std::string& allow, PermissionScope scope) {
12+
void WorkerPermission::Apply(const std::vector<std::string>& allow,
13+
PermissionScope scope) {
1314
deny_all_ = true;
1415
}
1516

src/permission/worker_permission.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace permission {
1212

1313
class WorkerPermission final : public PermissionBase {
1414
public:
15-
void Apply(const std::string& allow, PermissionScope scope) override;
15+
void Apply(const std::vector<std::string>& allow,
16+
PermissionScope scope) override;
1617
bool is_granted(PermissionScope perm,
1718
const std::string_view& param = "") override;
1819

test/es-module/test-cjs-legacyMainResolve-permission.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ describe('legacyMainResolve', () => {
3131

3232
for (const [mainOrFolder, allowReads] of paths) {
3333
const allowReadFilePaths = allowReads.map((filepath) => path.resolve(fixtextureFolder, filepath));
34-
const allowReadFiles = allowReads?.length > 0 ? ['--allow-fs-read', allowReadFilePaths.join(',')] : [];
34+
const allowReadFiles = allowReads?.length > 0 ?
35+
allowReadFilePaths.flatMap((path) => ['--allow-fs-read', path]) :
36+
[];
3537
const fixtextureFolderEscaped = escapeWhenSepIsBackSlash(fixtextureFolder);
3638

3739
const { status, stderr } = spawnSync(
@@ -85,7 +87,9 @@ describe('legacyMainResolve', () => {
8587

8688
for (const [folder, expectedFile, allowReads] of paths) {
8789
const allowReadFilePaths = allowReads.map((filepath) => path.resolve(fixtextureFolder, folder, filepath));
88-
const allowReadFiles = allowReads?.length > 0 ? ['--allow-fs-read', allowReadFilePaths.join(',')] : [];
90+
const allowReadFiles = allowReads?.length > 0 ?
91+
allowReadFilePaths.flatMap((path) => ['--allow-fs-read', path]) :
92+
[];
8993
const fixtextureFolderEscaped = escapeWhenSepIsBackSlash(fixtextureFolder);
9094

9195
const { status, stderr } = spawnSync(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
const { spawnSync } = require('child_process');
6+
const assert = require('assert');
7+
const path = require('path');
8+
9+
{
10+
const tmpPath = path.resolve('/tmp/');
11+
const otherPath = path.resolve('/other-path/');
12+
const { status, stdout } = spawnSync(
13+
process.execPath,
14+
[
15+
'--experimental-permission',
16+
'--allow-fs-write', tmpPath, '--allow-fs-write', otherPath, '-e',
17+
`console.log(process.permission.has("fs"));
18+
console.log(process.permission.has("fs.read"));
19+
console.log(process.permission.has("fs.write"));
20+
console.log(process.permission.has("fs.write", "/tmp/"));
21+
console.log(process.permission.has("fs.write", "/other-path/"));`,
22+
]
23+
);
24+
const [fs, fsIn, fsOut, fsOutAllowed1, fsOutAllowed2] = stdout.toString().split('\n');
25+
assert.strictEqual(fs, 'false');
26+
assert.strictEqual(fsIn, 'false');
27+
assert.strictEqual(fsOut, 'false');
28+
assert.strictEqual(fsOutAllowed1, 'true');
29+
assert.strictEqual(fsOutAllowed2, 'true');
30+
assert.strictEqual(status, 0);
31+
}
32+
33+
{
34+
const tmpPath = path.resolve('/tmp/');
35+
const pathWithComma = path.resolve('/other,path/');
36+
const { status, stdout } = spawnSync(
37+
process.execPath,
38+
[
39+
'--experimental-permission',
40+
'--allow-fs-write',
41+
tmpPath,
42+
'--allow-fs-write',
43+
pathWithComma,
44+
'-e',
45+
`console.log(process.permission.has("fs"));
46+
console.log(process.permission.has("fs.read"));
47+
console.log(process.permission.has("fs.write"));
48+
console.log(process.permission.has("fs.write", "/tmp/"));
49+
console.log(process.permission.has("fs.write", "/other,path/"));`,
50+
]
51+
);
52+
const [fs, fsIn, fsOut, fsOutAllowed1, fsOutAllowed2] = stdout.toString().split('\n');
53+
assert.strictEqual(fs, 'false');
54+
assert.strictEqual(fsIn, 'false');
55+
assert.strictEqual(fsOut, 'false');
56+
assert.strictEqual(fsOutAllowed1, 'true');
57+
assert.strictEqual(fsOutAllowed2, 'true');
58+
assert.strictEqual(status, 0);
59+
}
60+
61+
{
62+
const filePath = path.resolve('/tmp/file,with,comma.txt');
63+
const { status, stdout, stderr } = spawnSync(
64+
process.execPath,
65+
[
66+
'--experimental-permission',
67+
'--allow-fs-read=*',
68+
`--allow-fs-write=${filePath}`,
69+
'-e',
70+
`console.log(process.permission.has("fs"));
71+
console.log(process.permission.has("fs.read"));
72+
console.log(process.permission.has("fs.write"));
73+
console.log(process.permission.has("fs.write", "/tmp/file,with,comma.txt"));`,
74+
]
75+
);
76+
const [fs, fsIn, fsOut, fsOutAllowed] = stdout.toString().split('\n');
77+
assert.strictEqual(fs, 'false');
78+
assert.strictEqual(fsIn, 'true');
79+
assert.strictEqual(fsOut, 'false');
80+
assert.strictEqual(fsOutAllowed, 'true');
81+
assert.strictEqual(status, 0);
82+
assert.ok(stderr.toString().includes('Warning: The --allow-fs-write CLI flag has changed.'));
83+
}

test/parallel/test-permission-fs-read.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const commonPath = path.join(__filename, '../../common');
2828
const { status, stderr } = spawnSync(
2929
process.execPath,
3030
[
31-
'--experimental-permission', `--allow-fs-read=${file},${commonPathWildcard}`, file,
31+
'--experimental-permission', `--allow-fs-read=${file}`, `--allow-fs-read=${commonPathWildcard}`, file,
3232
],
3333
{
3434
env: {

test/parallel/test-permission-fs-symlink-target-write.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ fs.writeFileSync(path.join(readWriteFolder, 'file'), 'NO evil file contents');
3636
process.execPath,
3737
[
3838
'--experimental-permission',
39-
`--allow-fs-read=${file},${commonPathWildcard},${readOnlyFolder},${readWriteFolder}`,
40-
`--allow-fs-write=${readWriteFolder},${writeOnlyFolder}`,
39+
`--allow-fs-read=${file}`, `--allow-fs-read=${commonPathWildcard}`, `--allow-fs-read=${readOnlyFolder}`, `--allow-fs-read=${readWriteFolder}`,
40+
`--allow-fs-write=${readWriteFolder}`, `--allow-fs-write=${writeOnlyFolder}`,
4141
file,
4242
],
4343
{

test/parallel/test-permission-fs-symlink.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const symlinkFromBlockedFile = tmpdir.resolve('example-symlink.md');
3737
process.execPath,
3838
[
3939
'--experimental-permission',
40-
`--allow-fs-read=${file},${commonPathWildcard},${symlinkFromBlockedFile}`,
40+
`--allow-fs-read=${file}`, `--allow-fs-read=${commonPathWildcard}`, `--allow-fs-read=${symlinkFromBlockedFile}`,
4141
`--allow-fs-write=${symlinkFromBlockedFile}`,
4242
file,
4343
],

test/parallel/test-permission-fs-traversal-path.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const commonPathWildcard = path.join(__filename, '../../common*');
3131
process.execPath,
3232
[
3333
'--experimental-permission',
34-
`--allow-fs-read=${file},${commonPathWildcard},${allowedFolder}`,
34+
`--allow-fs-read=${file}`, `--allow-fs-read=${commonPathWildcard}`, `--allow-fs-read=${allowedFolder}`,
3535
`--allow-fs-write=${allowedFolder}`,
3636
file,
3737
],

0 commit comments

Comments
 (0)