mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
permission: drop process.permission.deny
PR-URL: https://github.com/nodejs/node/pull/47335 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Beth Griggs <bethanyngriggs@gmail.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
This commit is contained in:
1 parent
7ec93fb296
commit
6fd147c4b0
36 files changed
+451
-797
No files matched your search
@@ -1,19 +0,0 @@
|
||||
'use strict';
|
||||
const common = require('../common.js');
|
||||
|
||||
const configs = {
|
||||
n: [1e5],
|
||||
concurrent: [1, 10],
|
||||
};
|
||||
|
||||
const options = { flags: ['--experimental-permission'] };
|
||||
|
||||
const bench = common.createBenchmark(main, configs, options);
|
||||
|
||||
async function main(conf) {
|
||||
bench.start();
|
||||
for (let i = 0; i < conf.n; i++) {
|
||||
process.permission.deny('fs.read', ['/home/example-file-' + i]);
|
||||
}
|
||||
bench.end(conf.n);
|
||||
}
|
||||
+3
-15
@@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
const common = require('../common.js');
|
||||
const fs = require('fs/promises');
|
||||
const path = require('path');
|
||||
|
||||
const configs = {
|
||||
@@ -19,22 +18,11 @@ const options = {
|
||||
|
||||
const bench = common.createBenchmark(main, configs, options);
|
||||
|
||||
const recursivelyDenyFiles = async (dir) => {
|
||||
const files = await fs.readdir(dir, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
if (file.isDirectory()) {
|
||||
await recursivelyDenyFiles(path.join(dir, file.name));
|
||||
} else if (file.isFile()) {
|
||||
process.permission.deny('fs.read', [path.join(dir, file.name)]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This is a naive benchmark and might not demonstrate real-world use cases.
|
||||
// New benchmarks will be created once the permission model config is available
|
||||
// through a config file.
|
||||
async function main(conf) {
|
||||
const benchmarkDir = path.join(__dirname, '../..');
|
||||
// Get all the benchmark files and deny access to it
|
||||
await recursivelyDenyFiles(benchmarkDir);
|
||||
|
||||
bench.start();
|
||||
|
||||
for (let i = 0; i < conf.n; i++) {
|
||||
+3
-43
@@ -492,24 +492,7 @@ using the [`--allow-child-process`][] and [`--allow-worker`][] respectively.
|
||||
|
||||
When enabling the Permission Model through the [`--experimental-permission`][]
|
||||
flag a new property `permission` is added to the `process` object.
|
||||
This property contains two functions:
|
||||
|
||||
##### `permission.deny(scope [,parameters])`
|
||||
|
||||
API call to deny permissions at runtime ([`permission.deny()`][])
|
||||
|
||||
```js
|
||||
process.permission.deny('fs'); // Deny permissions to ALL fs operations
|
||||
|
||||
// Deny permissions to ALL FileSystemWrite operations
|
||||
process.permission.deny('fs.write');
|
||||
// deny FileSystemWrite permissions to the protected-folder
|
||||
process.permission.deny('fs.write', ['/home/rafaelgss/protected-folder']);
|
||||
// Deny permissions to ALL FileSystemRead operations
|
||||
process.permission.deny('fs.read');
|
||||
// deny FileSystemRead permissions to the protected-folder
|
||||
process.permission.deny('fs.read', ['/home/rafaelgss/protected-folder']);
|
||||
```
|
||||
This property contains one function:
|
||||
|
||||
##### `permission.has(scope ,parameters)`
|
||||
|
||||
@@ -519,10 +502,8 @@ API call to check permissions at runtime ([`permission.has()`][])
|
||||
process.permission.has('fs.write'); // true
|
||||
process.permission.has('fs.write', '/home/rafaelgss/protected-folder'); // true
|
||||
|
||||
process.permission.deny('fs.write', '/home/rafaelgss/protected-folder');
|
||||
|
||||
process.permission.has('fs.write'); // true
|
||||
process.permission.has('fs.write', '/home/rafaelgss/protected-folder'); // false
|
||||
process.permission.has('fs.read'); // true
|
||||
process.permission.has('fs.read', '/home/rafaelgss/protected-folder'); // false
|
||||
```
|
||||
|
||||
#### File System Permissions
|
||||
@@ -560,31 +541,11 @@ There are constraints you need to know before using this system:
|
||||
|
||||
* Native modules are restricted by default when using the Permission Model.
|
||||
* Relative paths are not supported through the CLI (`--allow-fs-*`).
|
||||
The runtime API supports relative paths.
|
||||
* The model does not inherit to a child node process.
|
||||
* The model does not inherit to a worker thread.
|
||||
* When creating symlinks the target (first argument) should have read and
|
||||
write access.
|
||||
* Permission changes are not retroactively applied to existing resources.
|
||||
Consider the following snippet:
|
||||
```js
|
||||
const fs = require('node:fs');
|
||||
|
||||
// Open a fd
|
||||
const fd = fs.openSync('./README.md', 'r');
|
||||
// Then, deny access to all fs.read operations
|
||||
process.permission.deny('fs.read');
|
||||
// This call will NOT fail and the file will be read
|
||||
const data = fs.readFileSync(fd);
|
||||
```
|
||||
|
||||
Therefore, when possible, apply the permissions rules before any statement:
|
||||
|
||||
```js
|
||||
process.permission.deny('fs.read');
|
||||
const fd = fs.openSync('./README.md', 'r');
|
||||
// Error: Access to this API has been restricted
|
||||
```
|
||||
|
||||
[Security Policy]: https://github.com/nodejs/node/blob/main/SECURITY.md
|
||||
[`--allow-child-process`]: cli.md#--allow-child-process
|
||||
@@ -592,7 +553,6 @@ const fd = fs.openSync('./README.md', 'r');
|
||||
[`--allow-fs-write`]: cli.md#--allow-fs-write
|
||||
[`--allow-worker`]: cli.md#--allow-worker
|
||||
[`--experimental-permission`]: cli.md#--experimental-permission
|
||||
[`permission.deny()`]: process.md#processpermissiondenyscope-reference
|
||||
[`permission.has()`]: process.md#processpermissionhasscope-reference
|
||||
[import maps]: https://url.spec.whatwg.org/#relative-url-with-fragment-string
|
||||
[relative-url string]: https://url.spec.whatwg.org/#relative-url-with-fragment-string
|
||||
|
||||
@@ -2634,34 +2634,6 @@ This API is available through the [`--experimental-permission`][] flag.
|
||||
for the current process. Additional documentation is available in the
|
||||
[Permission Model][].
|
||||
|
||||
### `process.permission.deny(scope[, reference])`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `scopes` {string}
|
||||
* `reference` {Array}
|
||||
* Returns: {boolean}
|
||||
|
||||
Deny permissions at runtime.
|
||||
|
||||
The available scopes are:
|
||||
|
||||
* `fs` - All File System
|
||||
* `fs.read` - File System read operations
|
||||
* `fs.write` - File System write operations
|
||||
|
||||
The reference has a meaning based on the provided scope. For example,
|
||||
the reference when the scope is File System means files and folders.
|
||||
|
||||
```js
|
||||
// Deny READ operations to the ./README.md file
|
||||
process.permission.deny('fs.read', ['./README.md']);
|
||||
// Deny ALL WRITE operations
|
||||
process.permission.deny('fs.write');
|
||||
```
|
||||
|
||||
### `process.permission.has(scope[, reference])`
|
||||
|
||||
<!-- YAML
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
const {
|
||||
ObjectFreeze,
|
||||
ArrayPrototypePush,
|
||||
} = primordials;
|
||||
|
||||
const permission = internalBinding('permission');
|
||||
const { validateString, validateArray } = require('internal/validators');
|
||||
const { validateString } = require('internal/validators');
|
||||
const { isAbsolute, resolve } = require('path');
|
||||
|
||||
let experimentalPermission;
|
||||
@@ -20,27 +19,6 @@ module.exports = ObjectFreeze({
|
||||
}
|
||||
return experimentalPermission;
|
||||
},
|
||||
deny(scope, references) {
|
||||
validateString(scope, 'scope');
|
||||
if (references == null) {
|
||||
return permission.deny(scope, references);
|
||||
}
|
||||
|
||||
validateArray(references, 'references');
|
||||
// TODO(rafaelgss): change to call fs_permission.resolve when available
|
||||
const normalizedParams = [];
|
||||
for (let i = 0; i < references.length; ++i) {
|
||||
if (isAbsolute(references[i])) {
|
||||
ArrayPrototypePush(normalizedParams, references[i]);
|
||||
} else {
|
||||
// TODO(aduh95): add support for WHATWG URLs and Uint8Arrays.
|
||||
ArrayPrototypePush(normalizedParams, resolve(references[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return permission.deny(scope, normalizedParams);
|
||||
},
|
||||
|
||||
has(scope, reference) {
|
||||
validateString(scope, 'scope');
|
||||
if (reference != null) {
|
||||
|
||||
+2
-2
@@ -784,10 +784,10 @@ Environment::Environment(IsolateData* isolate_data,
|
||||
if (!options_->allow_fs_read.empty() || !options_->allow_fs_write.empty()) {
|
||||
options_->allow_native_addons = false;
|
||||
if (!options_->allow_child_process) {
|
||||
permission()->Deny(permission::PermissionScope::kChildProcess, {});
|
||||
permission()->Apply("*", permission::PermissionScope::kChildProcess);
|
||||
}
|
||||
if (!options_->allow_worker_threads) {
|
||||
permission()->Deny(permission::PermissionScope::kWorkerThreads, {});
|
||||
permission()->Apply("*", permission::PermissionScope::kWorkerThreads);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,8 @@ namespace permission {
|
||||
// Currently, ChildProcess manage a single state
|
||||
// Once denied, it's always denied
|
||||
void ChildProcessPermission::Apply(const std::string& deny,
|
||||
PermissionScope scope) {}
|
||||
|
||||
bool ChildProcessPermission::Deny(PermissionScope perm,
|
||||
const std::vector<std::string>& params) {
|
||||
PermissionScope scope) {
|
||||
deny_all_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChildProcessPermission::is_granted(PermissionScope perm,
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace permission {
|
||||
class ChildProcessPermission final : public PermissionBase {
|
||||
public:
|
||||
void Apply(const std::string& deny, PermissionScope scope) override;
|
||||
bool Deny(PermissionScope scope,
|
||||
const std::vector<std::string>& params) override;
|
||||
bool is_granted(PermissionScope perm,
|
||||
const std::string_view& param = "") override;
|
||||
|
||||
|
||||
@@ -48,8 +48,7 @@ void FreeRecursivelyNode(
|
||||
delete node;
|
||||
}
|
||||
|
||||
bool is_tree_granted(node::permission::FSPermission::RadixTree* deny_tree,
|
||||
node::permission::FSPermission::RadixTree* granted_tree,
|
||||
bool is_tree_granted(node::permission::FSPermission::RadixTree* granted_tree,
|
||||
const std::string_view& param) {
|
||||
#ifdef _WIN32
|
||||
// is UNC file path
|
||||
@@ -60,11 +59,10 @@ bool is_tree_granted(node::permission::FSPermission::RadixTree* deny_tree,
|
||||
starting_pos += 4; // "UNC\"
|
||||
}
|
||||
auto normalized = param.substr(starting_pos);
|
||||
return !deny_tree->Lookup(normalized) &&
|
||||
granted_tree->Lookup(normalized, true);
|
||||
return granted_tree->Lookup(normalized, true);
|
||||
}
|
||||
#endif
|
||||
return !deny_tree->Lookup(param) && granted_tree->Lookup(param, true);
|
||||
return granted_tree->Lookup(param, true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -91,40 +89,6 @@ void FSPermission::Apply(const std::string& allow, PermissionScope scope) {
|
||||
}
|
||||
}
|
||||
|
||||
bool FSPermission::Deny(PermissionScope perm,
|
||||
const std::vector<std::string>& params) {
|
||||
if (perm == PermissionScope::kFileSystem) {
|
||||
deny_all_in_ = true;
|
||||
deny_all_out_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool deny_all = params.size() == 0;
|
||||
if (perm == PermissionScope::kFileSystemRead) {
|
||||
if (deny_all) deny_all_in_ = true;
|
||||
// when deny_all_in is already true permission.deny should be idempotent
|
||||
if (deny_all_in_) return true;
|
||||
allow_all_in_ = false;
|
||||
for (auto& param : params) {
|
||||
deny_in_fs_.Insert(WildcardIfDir(param));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (perm == PermissionScope::kFileSystemWrite) {
|
||||
if (deny_all) deny_all_out_ = true;
|
||||
// when deny_all_out is already true permission.deny should be idempotent
|
||||
if (deny_all_out_) return true;
|
||||
allow_all_out_ = false;
|
||||
|
||||
for (auto& param : params) {
|
||||
deny_out_fs_.Insert(WildcardIfDir(param));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FSPermission::GrantAccess(PermissionScope perm, std::string res) {
|
||||
const std::string path = WildcardIfDir(res);
|
||||
if (perm == PermissionScope::kFileSystemRead) {
|
||||
@@ -144,11 +108,11 @@ bool FSPermission::is_granted(PermissionScope perm,
|
||||
case PermissionScope::kFileSystemRead:
|
||||
return !deny_all_in_ &&
|
||||
((param.empty() && allow_all_in_) || allow_all_in_ ||
|
||||
is_tree_granted(&deny_in_fs_, &granted_in_fs_, param));
|
||||
is_tree_granted(&granted_in_fs_, param));
|
||||
case PermissionScope::kFileSystemWrite:
|
||||
return !deny_all_out_ &&
|
||||
((param.empty() && allow_all_out_) || allow_all_out_ ||
|
||||
is_tree_granted(&deny_out_fs_, &granted_out_fs_, param));
|
||||
is_tree_granted(&granted_out_fs_, param));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ namespace permission {
|
||||
class FSPermission final : public PermissionBase {
|
||||
public:
|
||||
void Apply(const std::string& deny, PermissionScope scope) override;
|
||||
bool Deny(PermissionScope scope,
|
||||
const std::vector<std::string>& params) override;
|
||||
bool is_granted(PermissionScope perm, const std::string_view& param) override;
|
||||
|
||||
// For debugging purposes, use the gist function to print the whole tree
|
||||
@@ -135,18 +133,9 @@ class FSPermission final : public PermissionBase {
|
||||
void GrantAccess(PermissionScope scope, std::string param);
|
||||
void RestrictAccess(PermissionScope scope,
|
||||
const std::vector<std::string>& params);
|
||||
// /tmp/* --grant
|
||||
// /tmp/dsadsa/t.js denied in runtime
|
||||
//
|
||||
// /tmp/text.txt -- grant
|
||||
// /tmp/text.txt -- denied in runtime
|
||||
//
|
||||
// fs granted on startup
|
||||
RadixTree granted_in_fs_;
|
||||
RadixTree granted_out_fs_;
|
||||
// fs denied in runtime
|
||||
RadixTree deny_in_fs_;
|
||||
RadixTree deny_out_fs_;
|
||||
|
||||
bool deny_all_in_ = true;
|
||||
bool deny_all_out_ = true;
|
||||
|
||||
@@ -14,10 +14,8 @@
|
||||
|
||||
namespace node {
|
||||
|
||||
using v8::Array;
|
||||
using v8::Context;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::Integer;
|
||||
using v8::Local;
|
||||
using v8::Object;
|
||||
using v8::String;
|
||||
@@ -27,42 +25,6 @@ namespace permission {
|
||||
|
||||
namespace {
|
||||
|
||||
// permission.deny('fs.read', ['/tmp/'])
|
||||
// permission.deny('fs.read')
|
||||
static void Deny(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
v8::Isolate* isolate = env->isolate();
|
||||
CHECK(args[0]->IsString());
|
||||
std::string deny_scope = *String::Utf8Value(isolate, args[0]);
|
||||
PermissionScope scope = Permission::StringToPermission(deny_scope);
|
||||
if (scope == PermissionScope::kPermissionsRoot) {
|
||||
return args.GetReturnValue().Set(false);
|
||||
}
|
||||
|
||||
std::vector<std::string> params;
|
||||
if (args.Length() == 1 || args[1]->IsUndefined()) {
|
||||
return args.GetReturnValue().Set(env->permission()->Deny(scope, params));
|
||||
}
|
||||
|
||||
CHECK(args[1]->IsArray());
|
||||
Local<Array> js_params = Local<Array>::Cast(args[1]);
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
|
||||
for (uint32_t i = 0; i < js_params->Length(); ++i) {
|
||||
Local<Value> arg;
|
||||
if (!js_params->Get(context, Integer::New(isolate, i)).ToLocal(&arg)) {
|
||||
return;
|
||||
}
|
||||
String::Utf8Value utf8_arg(isolate, arg);
|
||||
if (*utf8_arg == nullptr) {
|
||||
return;
|
||||
}
|
||||
params.push_back(*utf8_arg);
|
||||
}
|
||||
|
||||
return args.GetReturnValue().Set(env->permission()->Deny(scope, params));
|
||||
}
|
||||
|
||||
// permission.has('fs.in', '/tmp/')
|
||||
// permission.has('fs.in')
|
||||
static void Has(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -169,27 +131,16 @@ void Permission::Apply(const std::string& allow, PermissionScope scope) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Permission::Deny(PermissionScope scope,
|
||||
const std::vector<std::string>& params) {
|
||||
auto permission = nodes_.find(scope);
|
||||
if (permission != nodes_.end()) {
|
||||
return permission->second->Deny(scope, params);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Initialize(Local<Object> target,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
SetMethod(context, target, "deny", Deny);
|
||||
SetMethodNoSideEffect(context, target, "has", Has);
|
||||
|
||||
target->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen).FromJust();
|
||||
}
|
||||
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(Deny);
|
||||
registry->Register(Has);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,6 @@ class Permission {
|
||||
|
||||
// CLI Call
|
||||
void Apply(const std::string& deny, PermissionScope scope);
|
||||
// Permission.Deny API
|
||||
bool Deny(PermissionScope scope, const std::vector<std::string>& params);
|
||||
void EnablePermissions();
|
||||
|
||||
private:
|
||||
|
||||
@@ -37,8 +37,6 @@ enum class PermissionScope {
|
||||
class PermissionBase {
|
||||
public:
|
||||
virtual void Apply(const std::string& deny, PermissionScope scope) = 0;
|
||||
virtual bool Deny(PermissionScope scope,
|
||||
const std::vector<std::string>& params) = 0;
|
||||
virtual bool is_granted(PermissionScope perm,
|
||||
const std::string_view& param = "") = 0;
|
||||
};
|
||||
|
||||
@@ -9,12 +9,8 @@ namespace permission {
|
||||
|
||||
// Currently, PolicyDenyWorker manage a single state
|
||||
// Once denied, it's always denied
|
||||
void WorkerPermission::Apply(const std::string& deny, PermissionScope scope) {}
|
||||
|
||||
bool WorkerPermission::Deny(PermissionScope perm,
|
||||
const std::vector<std::string>& params) {
|
||||
void WorkerPermission::Apply(const std::string& deny, PermissionScope scope) {
|
||||
deny_all_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WorkerPermission::is_granted(PermissionScope perm,
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace permission {
|
||||
class WorkerPermission final : public PermissionBase {
|
||||
public:
|
||||
void Apply(const std::string& deny, PermissionScope scope) override;
|
||||
bool Deny(PermissionScope scope,
|
||||
const std::vector<std::string>& params) override;
|
||||
bool is_granted(PermissionScope perm,
|
||||
const std::string_view& param = "") override;
|
||||
|
||||
|
||||
+8
-95
@@ -1,29 +1,19 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=*
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
const common = require('../../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md');
|
||||
const relativeProtectedFile = './test/fixtures/permission/deny/protected-file.md';
|
||||
const absoluteProtectedFile = path.resolve(relativeProtectedFile);
|
||||
const blockedFolder = tmpdir.path;
|
||||
const blockedFile = process.env.BLOCKEDFILE;
|
||||
const blockedFolder = process.env.BLOCKEDFOLDER;
|
||||
const allowedFolder = process.env.ALLOWEDFOLDER;
|
||||
const regularFile = __filename;
|
||||
const uid = os.userInfo().uid;
|
||||
const gid = os.userInfo().gid;
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
assert.ok(process.permission.deny('fs.read', [blockedFile, blockedFolder]));
|
||||
}
|
||||
|
||||
// fs.readFile
|
||||
{
|
||||
assert.throws(() => {
|
||||
@@ -33,13 +23,6 @@ const gid = os.userInfo().gid;
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readFile(relativeProtectedFile, () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readFile(path.join(blockedFolder, 'anyfile'), () => {});
|
||||
}, common.expectsError({
|
||||
@@ -65,17 +48,6 @@ const gid = os.userInfo().gid;
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
})).then(common.mustCall());
|
||||
|
||||
assert.rejects(() => {
|
||||
return new Promise((_resolve, reject) => {
|
||||
const stream = fs.createReadStream(relativeProtectedFile);
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFile),
|
||||
})).then(common.mustCall());
|
||||
|
||||
assert.rejects(() => {
|
||||
return new Promise((_resolve, reject) => {
|
||||
const stream = fs.createReadStream(blockedFile);
|
||||
@@ -97,13 +69,6 @@ const gid = os.userInfo().gid;
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.stat(relativeProtectedFile, () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.stat(path.join(blockedFolder, 'anyfile'), () => {});
|
||||
}, common.expectsError({
|
||||
@@ -127,13 +92,6 @@ const gid = os.userInfo().gid;
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.access(relativeProtectedFile, fs.constants.R_OK, () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.access(path.join(blockedFolder, 'anyfile'), fs.constants.R_OK, () => {});
|
||||
}, common.expectsError({
|
||||
@@ -159,15 +117,6 @@ const gid = os.userInfo().gid;
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
// This operation will work fine
|
||||
fs.chownSync(relativeProtectedFile, uid, gid);
|
||||
fs.readFileSync(relativeProtectedFile);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFile),
|
||||
}));
|
||||
}
|
||||
|
||||
// fs.copyFile
|
||||
@@ -179,13 +128,6 @@ const gid = os.userInfo().gid;
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.copyFile(relativeProtectedFile, path.join(blockedFolder, 'any-other-file'), () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.copyFile(blockedFile, path.join(__dirname, 'any-other-file'), () => {});
|
||||
}, common.expectsError({
|
||||
@@ -205,19 +147,12 @@ const gid = os.userInfo().gid;
|
||||
// cpSync calls statSync before reading blockedFile
|
||||
resource: path.toNamespacedPath(blockedFolder),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.cpSync(relativeProtectedFile, path.join(blockedFolder, 'any-other-file'));
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFolder),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.cpSync(blockedFile, path.join(__dirname, 'any-other-file'));
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
resource: path.toNamespacedPath(__dirname),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -230,13 +165,6 @@ const gid = os.userInfo().gid;
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.open(relativeProtectedFile, 'r', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.open(path.join(blockedFolder, 'anyfile'), 'r', () => {});
|
||||
}, common.expectsError({
|
||||
@@ -278,7 +206,7 @@ const gid = os.userInfo().gid;
|
||||
resource: path.toNamespacedPath(blockedFolder),
|
||||
}));
|
||||
// doesNotThrow
|
||||
fs.opendir(__dirname, (err, dir) => {
|
||||
fs.opendir(allowedFolder, (err, dir) => {
|
||||
assert.ifError(err);
|
||||
dir.closeSync();
|
||||
});
|
||||
@@ -295,7 +223,7 @@ const gid = os.userInfo().gid;
|
||||
}));
|
||||
|
||||
// doesNotThrow
|
||||
fs.readdir(__dirname, (err) => {
|
||||
fs.readdir(allowedFolder, (err) => {
|
||||
assert.ifError(err);
|
||||
});
|
||||
}
|
||||
@@ -309,16 +237,9 @@ const gid = os.userInfo().gid;
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.watch(relativeProtectedFile, () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFile),
|
||||
}));
|
||||
|
||||
// doesNotThrow
|
||||
fs.readdir(__dirname, (err) => {
|
||||
fs.readdir(allowedFolder, (err) => {
|
||||
assert.ifError(err);
|
||||
});
|
||||
}
|
||||
@@ -332,12 +253,4 @@ const gid = os.userInfo().gid;
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.rename(relativeProtectedFile, 'newfile', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFile),
|
||||
}));
|
||||
}
|
||||
tmpdir.refresh();
|
||||
+12
-19
@@ -1,30 +1,23 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=* --allow-child-process
|
||||
'use strict';
|
||||
'use strict'
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
if (!common.canCreateSymLink())
|
||||
common.skip('insufficient privileges');
|
||||
const common = require('../../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh(true);
|
||||
|
||||
const readOnlyFolder = path.join(tmpdir.path, 'read-only');
|
||||
const readWriteFolder = path.join(tmpdir.path, 'read-write');
|
||||
const writeOnlyFolder = path.join(tmpdir.path, 'write-only');
|
||||
|
||||
fs.mkdirSync(readOnlyFolder);
|
||||
fs.mkdirSync(readWriteFolder);
|
||||
fs.mkdirSync(writeOnlyFolder);
|
||||
fs.writeFileSync(path.join(readOnlyFolder, 'file'), 'evil file contents');
|
||||
fs.writeFileSync(path.join(readWriteFolder, 'file'), 'NO evil file contents');
|
||||
const readOnlyFolder = process.env.READONLYFOLDER;
|
||||
const readWriteFolder = process.env.READWRITEFOLDER;
|
||||
const writeOnlyFolder = process.env.WRITEONLYFOLDER;
|
||||
|
||||
{
|
||||
assert.ok(process.permission.deny('fs.write', [readOnlyFolder]));
|
||||
assert.ok(process.permission.deny('fs.read', [writeOnlyFolder]));
|
||||
assert.ok(!process.permission.has('fs.write', readOnlyFolder));
|
||||
assert.ok(!process.permission.has('fs.read', writeOnlyFolder));
|
||||
assert.ok(process.permission.has('fs.write', readWriteFolder));
|
||||
|
||||
assert.ok(process.permission.has('fs.write', writeOnlyFolder));
|
||||
assert.ok(process.permission.has('fs.read', readOnlyFolder));
|
||||
assert.ok(process.permission.has('fs.read', readWriteFolder));
|
||||
}
|
||||
|
||||
{
|
||||
+9
-20
@@ -1,31 +1,21 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=* --allow-child-process
|
||||
'use strict';
|
||||
'use strict'
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
const fixtures = require('../common/fixtures');
|
||||
if (!common.canCreateSymLink())
|
||||
common.skip('insufficient privileges');
|
||||
const common = require('../../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
const path = require('path');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh(true);
|
||||
|
||||
const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md');
|
||||
const blockedFolder = path.join(tmpdir.path, 'subdirectory');
|
||||
const blockedFolder = process.env.BLOCKEDFOLDER;
|
||||
const blockedFile = process.env.BLOCKEDFILE;
|
||||
const regularFile = __filename;
|
||||
const symlinkFromBlockedFile = path.join(tmpdir.path, 'example-symlink.md');
|
||||
|
||||
fs.mkdirSync(blockedFolder);
|
||||
const symlinkFromBlockedFile = process.env.EXISTINGSYMLINK;
|
||||
|
||||
{
|
||||
// Symlink previously created
|
||||
fs.symlinkSync(blockedFile, symlinkFromBlockedFile);
|
||||
assert.ok(process.permission.deny('fs.read', [blockedFile, blockedFolder]));
|
||||
assert.ok(process.permission.deny('fs.write', [blockedFile, blockedFolder]));
|
||||
assert.ok(!process.permission.has('fs.read', blockedFile))
|
||||
assert.ok(!process.permission.has('fs.read', blockedFolder))
|
||||
assert.ok(!process.permission.has('fs.write', blockedFile))
|
||||
assert.ok(!process.permission.has('fs.write', blockedFolder))
|
||||
}
|
||||
|
||||
{
|
||||
@@ -101,4 +91,3 @@ fs.mkdirSync(blockedFolder);
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
}
|
||||
tmpdir.refresh(true);
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
'use strict'
|
||||
|
||||
const common = require('../../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
{
|
||||
assert.throws(() => {
|
||||
fs.readFile('/test.txt', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
// doesNotThrow
|
||||
fs.readFile('/tmp/foo/file', () => {});
|
||||
}
|
||||
|
||||
{
|
||||
// doesNotThrow
|
||||
fs.readFile('/example/foo/file', () => {});
|
||||
fs.readFile('/example/foo2/file', () => {});
|
||||
fs.readFile('/example/foo2', () => {});
|
||||
|
||||
assert.throws(() => {
|
||||
fs.readFile('/example/fo/foo2.js', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readFile('/example/for', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// doesNotThrow
|
||||
fs.readFile('/example/bar/file', () => {});
|
||||
fs.readFile('/example/bar2/file', () => {});
|
||||
fs.readFile('/example/bar', () => {});
|
||||
|
||||
assert.throws(() => {
|
||||
fs.readFile('/example/ba/foo2.js', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
fs.readFile('/folder/a/subfolder/b', () => {});
|
||||
fs.readFile('/folder/a/subfolder/b/c.txt', () => {});
|
||||
fs.readFile('/folder/a/foo2.js', () => {});
|
||||
}
|
||||
+9
-23
@@ -1,27 +1,24 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=*
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const common = require('../../common');
|
||||
common.skipIfWorker();
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const blockedFolder = fixtures.path('permission', 'deny', 'protected-folder');
|
||||
const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md');
|
||||
const relativeProtectedFile = './test/fixtures/permission/deny/protected-file.md';
|
||||
const relativeProtectedFolder = './test/fixtures/permission/deny/protected-folder';
|
||||
const regularFolder = process.env.ALLOWEDFOLDER;
|
||||
const regularFile = process.env.ALLOWEDFILE;
|
||||
const blockedFolder = process.env.BLOCKEDFOLDER;
|
||||
const blockedFile = process.env.BLOCKEDFILE;
|
||||
const relativeProtectedFile = process.env.RELATIVEBLOCKEDFILE;
|
||||
const relativeProtectedFolder = process.env.RELATIVEBLOCKEDFOLDER;
|
||||
const absoluteProtectedFile = path.resolve(relativeProtectedFile);
|
||||
const absoluteProtectedFolder = path.resolve(relativeProtectedFolder);
|
||||
|
||||
const regularFolder = fixtures.path('permission', 'deny');
|
||||
const regularFile = fixtures.path('permission', 'deny', 'regular-file.md');
|
||||
|
||||
{
|
||||
assert.ok(process.permission.deny('fs.write', [blockedFolder]));
|
||||
assert.ok(process.permission.deny('fs.write', [blockedFile]));
|
||||
assert.ok(!process.permission.has('fs.write', blockedFolder));
|
||||
assert.ok(!process.permission.has('fs.write', blockedFile));
|
||||
}
|
||||
|
||||
// fs.writeFile
|
||||
@@ -226,17 +223,6 @@ const regularFile = fixtures.path('permission', 'deny', 'regular-file.md');
|
||||
permission: 'FileSystemWrite',
|
||||
resource: path.toNamespacedPath(absoluteProtectedFolder),
|
||||
}));
|
||||
|
||||
// The user shouldn't be capable to rmdir of a non-protected folder
|
||||
// but that contains a protected file.
|
||||
// The regularFolder contains a protected file
|
||||
assert.throws(() => {
|
||||
fs.rmSync(regularFolder, { recursive: true });
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemWrite',
|
||||
resource: path.toNamespacedPath(blockedFile),
|
||||
}));
|
||||
}
|
||||
|
||||
// fs.open
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,70 +0,0 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-child-process
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
const assert = require('assert');
|
||||
const childProcess = require('child_process');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
{
|
||||
// doesNotThrow
|
||||
const spawn = childProcess.spawn(process.execPath, ['--version']);
|
||||
spawn.kill();
|
||||
const exec = childProcess.exec(process.execPath, ['--version']);
|
||||
exec.kill();
|
||||
const fork = childProcess.fork(__filename, ['child']);
|
||||
fork.kill();
|
||||
const execFile = childProcess.execFile(process.execPath, ['--version']);
|
||||
execFile.kill();
|
||||
|
||||
assert.ok(process.permission.deny('child'));
|
||||
|
||||
// When a permission is set by API, the process shouldn't be able
|
||||
// to spawn
|
||||
assert.throws(() => {
|
||||
childProcess.spawn(process.execPath, ['--version']);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'ChildProcess',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
childProcess.spawnSync(process.execPath, ['--version']);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'ChildProcess',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
childProcess.exec(process.execPath, ['--version']);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'ChildProcess',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
childProcess.execSync(process.execPath, ['--version']);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'ChildProcess',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
childProcess.fork(__filename, ['child']);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'ChildProcess',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
childProcess.execFile(process.execPath, ['--version']);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'ChildProcess',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
childProcess.execFileSync(process.execPath, ['--version']);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'ChildProcess',
|
||||
}));
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=*
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
if (common.isWindows) {
|
||||
const { root } = path.parse(process.cwd());
|
||||
const abs = (p) => path.join(root, p);
|
||||
const denyList = [
|
||||
'tmp\\*',
|
||||
'example\\foo*',
|
||||
'example\\bar*',
|
||||
'folder\\*',
|
||||
'show',
|
||||
'slower',
|
||||
'slown',
|
||||
'home\\foo\\*',
|
||||
].map(abs);
|
||||
assert.ok(process.permission.deny('fs.read', denyList));
|
||||
assert.ok(process.permission.has('fs.read', abs('slow')));
|
||||
assert.ok(process.permission.has('fs.read', abs('slows')));
|
||||
assert.ok(!process.permission.has('fs.read', abs('slown')));
|
||||
assert.ok(!process.permission.has('fs.read', abs('home\\foo')));
|
||||
assert.ok(!process.permission.has('fs.read', abs('home\\foo\\')));
|
||||
assert.ok(process.permission.has('fs.read', abs('home\\fo')));
|
||||
} else {
|
||||
const denyList = [
|
||||
'/tmp/*',
|
||||
'/example/foo*',
|
||||
'/example/bar*',
|
||||
'/folder/*',
|
||||
'/show',
|
||||
'/slower',
|
||||
'/slown',
|
||||
'/home/foo/*',
|
||||
];
|
||||
assert.ok(process.permission.deny('fs.read', denyList));
|
||||
assert.ok(process.permission.has('fs.read', '/slow'));
|
||||
assert.ok(process.permission.has('fs.read', '/slows'));
|
||||
assert.ok(!process.permission.has('fs.read', '/slown'));
|
||||
assert.ok(!process.permission.has('fs.read', '/home/foo'));
|
||||
assert.ok(!process.permission.has('fs.read', '/home/foo/'));
|
||||
assert.ok(process.permission.has('fs.read', '/home/fo'));
|
||||
}
|
||||
|
||||
{
|
||||
assert.throws(() => {
|
||||
fs.readFile('/tmp/foo/file', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
// doesNotThrow
|
||||
fs.readFile('/test.txt', () => {});
|
||||
fs.readFile('/tmpd', () => {});
|
||||
}
|
||||
|
||||
{
|
||||
assert.throws(() => {
|
||||
fs.readFile('/example/foo/file', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readFile('/example/foo2/file', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readFile('/example/foo2', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
|
||||
// doesNotThrow
|
||||
fs.readFile('/example/fo/foo2.js', () => {});
|
||||
fs.readFile('/example/for', () => {});
|
||||
}
|
||||
|
||||
{
|
||||
assert.throws(() => {
|
||||
fs.readFile('/example/bar/file', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readFile('/example/bar2/file', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readFile('/example/bar', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
|
||||
// doesNotThrow
|
||||
fs.readFile('/example/ba/foo2.js', () => {});
|
||||
}
|
||||
|
||||
{
|
||||
assert.throws(() => {
|
||||
fs.readFile('/folder/a/subfolder/b', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readFile('/folder/a/subfolder/b/c.txt', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
assert.throws(() => {
|
||||
fs.readFile('/folder/a/foo2.js', () => {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
}));
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-worker
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const {
|
||||
Worker,
|
||||
isMainThread,
|
||||
} = require('worker_threads');
|
||||
const { once } = require('events');
|
||||
|
||||
async function createWorker() {
|
||||
// doesNotThrow
|
||||
const worker = new Worker(__filename);
|
||||
await once(worker, 'exit');
|
||||
// When a permission is set by API, the process shouldn't be able
|
||||
// to create worker threads
|
||||
assert.ok(process.permission.deny('worker'));
|
||||
assert.throws(() => {
|
||||
new Worker(__filename);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'WorkerThreads',
|
||||
}));
|
||||
}
|
||||
|
||||
if (isMainThread) {
|
||||
createWorker();
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=*
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
|
||||
const fs = require('fs');
|
||||
const fsPromises = require('node:fs/promises');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const protectedFolder = fixtures.path('permission', 'deny');
|
||||
const protectedFile = fixtures.path('permission', 'deny', 'protected-file.md');
|
||||
const regularFile = fixtures.path('permission', 'deny', 'regular-file.md');
|
||||
|
||||
// Assert has and deny exists
|
||||
{
|
||||
assert.ok(typeof process.permission.has === 'function');
|
||||
assert.ok(typeof process.permission.deny === 'function');
|
||||
}
|
||||
|
||||
// Guarantee the initial state when no flags
|
||||
{
|
||||
assert.ok(process.permission.has('fs.read'));
|
||||
assert.ok(process.permission.has('fs.write'));
|
||||
|
||||
assert.ok(process.permission.has('fs.read', protectedFile));
|
||||
assert.ok(process.permission.has('fs.read', regularFile));
|
||||
|
||||
assert.ok(process.permission.has('fs.write', protectedFolder));
|
||||
assert.ok(process.permission.has('fs.write', regularFile));
|
||||
|
||||
// doesNotThrow
|
||||
fs.readFileSync(protectedFile);
|
||||
}
|
||||
|
||||
// Deny access to fs.read
|
||||
{
|
||||
assert.ok(process.permission.deny('fs.read', [protectedFile]));
|
||||
assert.ok(process.permission.has('fs.read'));
|
||||
assert.ok(process.permission.has('fs.write'));
|
||||
|
||||
assert.ok(process.permission.has('fs.read', regularFile));
|
||||
assert.ok(!process.permission.has('fs.read', protectedFile));
|
||||
|
||||
assert.ok(process.permission.has('fs.write', protectedFolder));
|
||||
assert.ok(process.permission.has('fs.write', regularFile));
|
||||
|
||||
assert.rejects(() => {
|
||||
return fsPromises.readFile(protectedFile);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemRead',
|
||||
})).then(common.mustCall());
|
||||
|
||||
// doesNotThrow
|
||||
fs.openSync(regularFile, 'w');
|
||||
}
|
||||
|
||||
// Deny access to fs.write
|
||||
{
|
||||
assert.ok(process.permission.deny('fs.write', [protectedFolder]));
|
||||
assert.ok(process.permission.has('fs.read'));
|
||||
assert.ok(process.permission.has('fs.write'));
|
||||
|
||||
assert.ok(!process.permission.has('fs.read', protectedFile));
|
||||
assert.ok(process.permission.has('fs.read', regularFile));
|
||||
|
||||
assert.ok(!process.permission.has('fs.write', protectedFolder));
|
||||
assert.ok(!process.permission.has('fs.write', regularFile));
|
||||
|
||||
assert.rejects(() => {
|
||||
return fsPromises
|
||||
.writeFile(path.join(protectedFolder, 'new-file'), 'data');
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemWrite',
|
||||
})).then(common.mustCall());
|
||||
|
||||
assert.throws(() => {
|
||||
fs.openSync(regularFile, 'w');
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemWrite',
|
||||
}));
|
||||
}
|
||||
|
||||
// Should not crash if wrong parameter is provided
|
||||
{
|
||||
// Array is expected as second parameter
|
||||
assert.throws(() => {
|
||||
process.permission.deny('fs.read', protectedFolder);
|
||||
}, common.expectsError({
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=* --allow-child-process
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('no crypto');
|
||||
}
|
||||
|
||||
const assert = require('assert');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const { spawnSync } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md');
|
||||
const blockedFolder = tmpdir.path;
|
||||
const file = fixtures.path('permission', 'fs-read.js');
|
||||
const commonPathWildcard = path.join(__filename, '../../common*');
|
||||
const commonPath = path.join(__filename, '../../common');
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
}
|
||||
|
||||
{
|
||||
const { status, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission', `--allow-fs-read=${file},${commonPathWildcard}`, file,
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
BLOCKEDFILE: blockedFile,
|
||||
BLOCKEDFOLDER: blockedFolder,
|
||||
ALLOWEDFOLDER: commonPath,
|
||||
},
|
||||
}
|
||||
);
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
}
|
||||
@@ -5,26 +5,8 @@ const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
|
||||
const assert = require('assert');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const protectedFile = fixtures.path('permission', 'deny', 'protected-file.md');
|
||||
const relativeProtectedFile = './test/fixtures/permission/deny/protected-file.md';
|
||||
|
||||
// Note: for relative path on fs.* calls, check test-permission-deny-fs-[read/write].js files
|
||||
|
||||
{
|
||||
// permission.deny relative path should work
|
||||
assert.ok(process.permission.has('fs.read', protectedFile));
|
||||
assert.ok(process.permission.deny('fs.read', [relativeProtectedFile]));
|
||||
assert.ok(!process.permission.has('fs.read', protectedFile));
|
||||
}
|
||||
|
||||
{
|
||||
// permission.has relative path should work
|
||||
assert.ok(!process.permission.has('fs.read', relativeProtectedFile));
|
||||
}
|
||||
|
||||
{
|
||||
// Relative path as CLI args are NOT supported yet
|
||||
const { status, stdout } = spawnSync(
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=* --allow-child-process
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
if (!common.canCreateSymLink())
|
||||
common.skip('insufficient privileges');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('no crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
}
|
||||
|
||||
const readOnlyFolder = path.join(tmpdir.path, 'read-only');
|
||||
const readWriteFolder = path.join(tmpdir.path, 'read-write');
|
||||
const writeOnlyFolder = path.join(tmpdir.path, 'write-only');
|
||||
const file = fixtures.path('permission', 'fs-symlink-target-write.js');
|
||||
const commonPathWildcard = path.join(__filename, '../../common*');
|
||||
|
||||
fs.mkdirSync(readOnlyFolder);
|
||||
fs.mkdirSync(readWriteFolder);
|
||||
fs.mkdirSync(writeOnlyFolder);
|
||||
fs.writeFileSync(path.join(readOnlyFolder, 'file'), 'evil file contents');
|
||||
fs.writeFileSync(path.join(readWriteFolder, 'file'), 'NO evil file contents');
|
||||
|
||||
{
|
||||
const { status, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
`--allow-fs-read=${file},${commonPathWildcard},${readOnlyFolder},${readWriteFolder}`,
|
||||
`--allow-fs-write=${readWriteFolder},${writeOnlyFolder}`,
|
||||
file,
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
READONLYFOLDER: readOnlyFolder,
|
||||
WRITEONLYFOLDER: writeOnlyFolder,
|
||||
READWRITEFOLDER: readWriteFolder,
|
||||
},
|
||||
}
|
||||
);
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=* --allow-child-process
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
if (!common.canCreateSymLink())
|
||||
common.skip('insufficient privileges');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('no crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const { spawnSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
|
||||
const file = fixtures.path('permission', 'fs-symlink.js');
|
||||
const commonPathWildcard = path.join(__filename, '../../common*');
|
||||
const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md');
|
||||
const blockedFolder = path.join(tmpdir.path, 'subdirectory');
|
||||
const symlinkFromBlockedFile = path.join(tmpdir.path, 'example-symlink.md');
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
fs.mkdirSync(blockedFolder);
|
||||
}
|
||||
|
||||
{
|
||||
// Symlink previously created
|
||||
fs.symlinkSync(blockedFile, symlinkFromBlockedFile);
|
||||
}
|
||||
|
||||
{
|
||||
const { status, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
`--allow-fs-read=${file},${commonPathWildcard},${symlinkFromBlockedFile}`,
|
||||
`--allow-fs-write=${symlinkFromBlockedFile}`,
|
||||
file,
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
BLOCKEDFOLDER: blockedFolder,
|
||||
BLOCKEDFILE: blockedFile,
|
||||
EXISTINGSYMLINK: symlinkFromBlockedFile,
|
||||
},
|
||||
}
|
||||
);
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
}
|
||||
|
||||
{
|
||||
tmpdir.refresh();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-child-process
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const commonPathWildcard = path.join(__filename, '../../common*');
|
||||
const file = fixtures.path('permission', 'fs-wildcard.js');
|
||||
|
||||
let allowList = [];
|
||||
|
||||
if (common.isWindows) {
|
||||
const { root } = path.parse(process.cwd());
|
||||
const abs = (p) => path.join(root, p);
|
||||
allowList = [
|
||||
'tmp\\*',
|
||||
'example\\foo*',
|
||||
'example\\bar*',
|
||||
'folder\\*',
|
||||
'show',
|
||||
'slower',
|
||||
'slown',
|
||||
'home\\foo\\*',
|
||||
].map(abs);
|
||||
const { status, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
`--allow-fs-read=${allowList.join(',')}`,
|
||||
'-e',
|
||||
`
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
const { root } = path.parse(process.cwd());
|
||||
const abs = (p) => path.join(root, p);
|
||||
assert.ok(!process.permission.has('fs.read', abs('slow')));
|
||||
assert.ok(!process.permission.has('fs.read', abs('slows')));
|
||||
assert.ok(process.permission.has('fs.read', abs('slown')));
|
||||
assert.ok(process.permission.has('fs.read', abs('home\\\\foo')));
|
||||
assert.ok(process.permission.has('fs.read', abs('home\\\\foo\\\\')));
|
||||
assert.ok(!process.permission.has('fs.read', abs('home\\\\fo')));
|
||||
`,
|
||||
]
|
||||
);
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
} else {
|
||||
allowList = [
|
||||
'/tmp/*',
|
||||
'/example/foo*',
|
||||
'/example/bar*',
|
||||
'/folder/*',
|
||||
'/show',
|
||||
'/slower',
|
||||
'/slown',
|
||||
'/home/foo/*',
|
||||
];
|
||||
const { status, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
`--allow-fs-read=${allowList.join(',')}`,
|
||||
'-e',
|
||||
`
|
||||
const assert = require('assert')
|
||||
assert.ok(!process.permission.has('fs.read', '/slow'));
|
||||
assert.ok(!process.permission.has('fs.read', '/slows'));
|
||||
assert.ok(process.permission.has('fs.read', '/slown'));
|
||||
assert.ok(process.permission.has('fs.read', '/home/foo'));
|
||||
assert.ok(process.permission.has('fs.read', '/home/foo/'));
|
||||
assert.ok(!process.permission.has('fs.read', '/home/fo'));
|
||||
`,
|
||||
]
|
||||
);
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
}
|
||||
|
||||
{
|
||||
const { status, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
`--allow-fs-read=${file},${commonPathWildcard},${allowList.join(',')}`,
|
||||
file,
|
||||
],
|
||||
);
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
}
|
||||
@@ -1,66 +1,40 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=* --allow-child-process
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-child-process
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
|
||||
const assert = require('assert');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
if (!common.isWindows) {
|
||||
common.skip('windows test');
|
||||
}
|
||||
|
||||
const protectedFolder = fixtures.path('permission', 'deny', 'protected-folder');
|
||||
|
||||
{
|
||||
assert.ok(process.permission.has('fs.write', protectedFolder));
|
||||
assert.ok(process.permission.deny('fs.write', [protectedFolder]));
|
||||
assert.ok(!process.permission.has('fs.write', protectedFolder));
|
||||
common.skip('windows UNC path test');
|
||||
}
|
||||
|
||||
{
|
||||
assert.throws(() => {
|
||||
fs.openSync(path.join(protectedFolder, 'protected-file.md'), 'w');
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemWrite',
|
||||
resource: path.toNamespacedPath(path.join(protectedFolder, 'protected-file.md')),
|
||||
}));
|
||||
|
||||
assert.rejects(() => {
|
||||
return new Promise((_resolve, reject) => {
|
||||
const stream = fs.createWriteStream(path.join(protectedFolder, 'protected-file.md'));
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_ACCESS_DENIED',
|
||||
permission: 'FileSystemWrite',
|
||||
resource: path.toNamespacedPath(path.join(protectedFolder, 'protected-file.md')),
|
||||
})).then(common.mustCall());
|
||||
}
|
||||
|
||||
{
|
||||
const { stdout } = spawnSync(process.execPath, [
|
||||
const { stdout, status } = spawnSync(process.execPath, [
|
||||
'--experimental-permission', '--allow-fs-write', 'C:\\\\', '-e',
|
||||
'console.log(process.permission.has("fs.write", "C:\\\\"))',
|
||||
]);
|
||||
assert.strictEqual(stdout.toString(), 'true\n');
|
||||
assert.strictEqual(status, 0);
|
||||
}
|
||||
|
||||
{
|
||||
assert.ok(process.permission.has('fs.write', 'C:\\home'));
|
||||
assert.ok(process.permission.deny('fs.write', ['C:\\home']));
|
||||
assert.ok(!process.permission.has('fs.write', 'C:\\home'));
|
||||
const { stdout, status, stderr } = spawnSync(process.execPath, [
|
||||
'--experimental-permission', '--allow-fs-write="\\\\?\\C:\\"', '-e',
|
||||
'console.log(process.permission.has("fs.write", "C:\\\\"))',
|
||||
]);
|
||||
assert.strictEqual(stdout.toString(), 'false\n', stderr.toString());
|
||||
assert.strictEqual(status, 0);
|
||||
}
|
||||
|
||||
{
|
||||
assert.ok(process.permission.has('fs.write', '\\\\?\\C:\\'));
|
||||
assert.ok(process.permission.deny('fs.write', ['\\\\?\\C:\\']));
|
||||
// UNC aren't supported so far
|
||||
assert.ok(process.permission.has('fs.write', 'C:/'));
|
||||
assert.ok(process.permission.has('fs.write', '\\\\?\\C:\\'));
|
||||
const { stdout, status, stderr } = spawnSync(process.execPath, [
|
||||
'--experimental-permission', '--allow-fs-write', 'C:\\', '-e',
|
||||
`const path = require('path');
|
||||
console.log(process.permission.has('fs.write', path.toNamespacedPath('C:\\\\')))`,
|
||||
]);
|
||||
assert.strictEqual(stdout.toString(), 'true\n', stderr.toString());
|
||||
assert.strictEqual(status, 0);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=* --allow-child-process
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfWorker();
|
||||
if (!common.hasCrypto)
|
||||
common.skip('no crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const blockedFolder = fixtures.path('permission', 'deny', 'protected-folder');
|
||||
const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md');
|
||||
const relativeProtectedFile = './test/fixtures/permission/deny/protected-file.md';
|
||||
const relativeProtectedFolder = './test/fixtures/permission/deny/protected-folder';
|
||||
|
||||
const commonPath = path.join(__filename, '../../common');
|
||||
const regularFile = fixtures.path('permission', 'deny', 'regular-file.md');
|
||||
const file = fixtures.path('permission', 'fs-write.js');
|
||||
|
||||
{
|
||||
const { status, stderr } = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
'--experimental-permission',
|
||||
'--allow-fs-read=*',
|
||||
`--allow-fs-write=${regularFile},${commonPath}`,
|
||||
file,
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
BLOCKEDFILE: blockedFile,
|
||||
BLOCKEDFOLDER: blockedFolder,
|
||||
RELATIVEBLOCKEDFOLDER: relativeProtectedFolder,
|
||||
RELATIVEBLOCKEDFILE: relativeProtectedFile,
|
||||
ALLOWEDFILE: regularFile,
|
||||
ALLOWEDFOLDER: commonPath,
|
||||
},
|
||||
}
|
||||
);
|
||||
assert.strictEqual(status, 0, stderr.toString());
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Flags: --experimental-permission --allow-fs-read=*
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
{
|
||||
assert.ok(typeof process.permission.has === 'function');
|
||||
assert.throws(() => {
|
||||
process.permission.has(null, '');
|
||||
}, common.expectsError({
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "scope" argument must be of type string. Received null',
|
||||
}));
|
||||
assert.ok(!process.permission.has('invalid-key'));
|
||||
assert.throws(() => {
|
||||
process.permission.has('fs', {});
|
||||
}, common.expectsError({
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: 'The "reference" argument must be of type string. Received an instance of Object',
|
||||
}));
|
||||
}
|
||||
File renamed without changes.
Reference in new issue
Block a user