mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
src,permission: add --allow-net permission
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> PR-URL: https://github.com/nodejs/node/pull/58517 Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Darshan Sen <raisinten@gmail.com>
This commit is contained in:
parent
07220230d9
commit
462c74181d
|
|
@ -263,6 +263,38 @@ When passing a single flag with a comma a warning will be displayed.
|
||||||
|
|
||||||
Examples can be found in the [File System Permissions][] documentation.
|
Examples can be found in the [File System Permissions][] documentation.
|
||||||
|
|
||||||
|
### `--allow-net`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1.1 - Active development
|
||||||
|
|
||||||
|
When using the [Permission Model][], the process will not be able to access
|
||||||
|
network by default.
|
||||||
|
Attempts to do so will throw an `ERR_ACCESS_DENIED` unless the
|
||||||
|
user explicitly passes the `--allow-net` flag when starting Node.js.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const http = require('node:http');
|
||||||
|
// Attempt to bypass the permission
|
||||||
|
const req = http.get('http://example.com', () => {});
|
||||||
|
|
||||||
|
req.on('error', (err) => {
|
||||||
|
console.log('err', err);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ node --permission index.js
|
||||||
|
Error: connect ERR_ACCESS_DENIED Access to this API has been restricted. Use --allow-net to manage permissions.
|
||||||
|
code: 'ERR_ACCESS_DENIED',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `--allow-wasi`
|
### `--allow-wasi`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
|
@ -1943,6 +1975,7 @@ following permissions are restricted:
|
||||||
|
|
||||||
* File System - manageable through
|
* File System - manageable through
|
||||||
[`--allow-fs-read`][], [`--allow-fs-write`][] flags
|
[`--allow-fs-read`][], [`--allow-fs-write`][] flags
|
||||||
|
* Network - manageable through [`--allow-net`][] flag
|
||||||
* Child Process - manageable through [`--allow-child-process`][] flag
|
* Child Process - manageable through [`--allow-child-process`][] flag
|
||||||
* Worker Threads - manageable through [`--allow-worker`][] flag
|
* Worker Threads - manageable through [`--allow-worker`][] flag
|
||||||
* WASI - manageable through [`--allow-wasi`][] flag
|
* WASI - manageable through [`--allow-wasi`][] flag
|
||||||
|
|
@ -3297,6 +3330,7 @@ one is included in the list below.
|
||||||
* `--allow-child-process`
|
* `--allow-child-process`
|
||||||
* `--allow-fs-read`
|
* `--allow-fs-read`
|
||||||
* `--allow-fs-write`
|
* `--allow-fs-write`
|
||||||
|
* `--allow-net`
|
||||||
* `--allow-wasi`
|
* `--allow-wasi`
|
||||||
* `--allow-worker`
|
* `--allow-worker`
|
||||||
* `--conditions`, `-C`
|
* `--conditions`, `-C`
|
||||||
|
|
@ -3899,6 +3933,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
|
||||||
[`--allow-child-process`]: #--allow-child-process
|
[`--allow-child-process`]: #--allow-child-process
|
||||||
[`--allow-fs-read`]: #--allow-fs-read
|
[`--allow-fs-read`]: #--allow-fs-read
|
||||||
[`--allow-fs-write`]: #--allow-fs-write
|
[`--allow-fs-write`]: #--allow-fs-write
|
||||||
|
[`--allow-net`]: #--allow-net
|
||||||
[`--allow-wasi`]: #--allow-wasi
|
[`--allow-wasi`]: #--allow-wasi
|
||||||
[`--allow-worker`]: #--allow-worker
|
[`--allow-worker`]: #--allow-worker
|
||||||
[`--build-snapshot`]: #--build-snapshot
|
[`--build-snapshot`]: #--build-snapshot
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,9 @@ The available permissions are documented by the [`--permission`][]
|
||||||
flag.
|
flag.
|
||||||
|
|
||||||
When starting Node.js with `--permission`,
|
When starting Node.js with `--permission`,
|
||||||
the ability to access the file system through the `fs` module, spawn processes,
|
the ability to access the file system through the `fs` module, access the network,
|
||||||
use `node:worker_threads`, use native addons, use WASI, and enable the runtime inspector
|
spawn processes, use `node:worker_threads`, use native addons, use WASI, and
|
||||||
will be restricted.
|
enable the runtime inspector will be restricted.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ node --permission index.js
|
$ node --permission index.js
|
||||||
|
|
@ -69,7 +69,8 @@ Error: Access to this API has been restricted
|
||||||
Allowing access to spawning a process and creating worker threads can be done
|
Allowing access to spawning a process and creating worker threads can be done
|
||||||
using the [`--allow-child-process`][] and [`--allow-worker`][] respectively.
|
using the [`--allow-child-process`][] and [`--allow-worker`][] respectively.
|
||||||
|
|
||||||
To allow native addons when using permission model, use the [`--allow-addons`][]
|
To allow network access, use [`--allow-net`][] and for allowing native addons
|
||||||
|
when using permission model, use the [`--allow-addons`][]
|
||||||
flag. For WASI, use the [`--allow-wasi`][] flag.
|
flag. For WASI, use the [`--allow-wasi`][] flag.
|
||||||
|
|
||||||
#### Runtime API
|
#### Runtime API
|
||||||
|
|
@ -197,6 +198,7 @@ There are constraints you need to know before using this system:
|
||||||
* The model does not inherit to a child node process or a worker thread.
|
* The model does not inherit to a child node process or a worker thread.
|
||||||
* When using the Permission Model the following features will be restricted:
|
* When using the Permission Model the following features will be restricted:
|
||||||
* Native modules
|
* Native modules
|
||||||
|
* Network
|
||||||
* Child process
|
* Child process
|
||||||
* Worker Threads
|
* Worker Threads
|
||||||
* Inspector protocol
|
* Inspector protocol
|
||||||
|
|
@ -227,6 +229,7 @@ There are constraints you need to know before using this system:
|
||||||
[`--allow-child-process`]: cli.md#--allow-child-process
|
[`--allow-child-process`]: cli.md#--allow-child-process
|
||||||
[`--allow-fs-read`]: cli.md#--allow-fs-read
|
[`--allow-fs-read`]: cli.md#--allow-fs-read
|
||||||
[`--allow-fs-write`]: cli.md#--allow-fs-write
|
[`--allow-fs-write`]: cli.md#--allow-fs-write
|
||||||
|
[`--allow-net`]: cli.md#--allow-net
|
||||||
[`--allow-wasi`]: cli.md#--allow-wasi
|
[`--allow-wasi`]: cli.md#--allow-wasi
|
||||||
[`--allow-worker`]: cli.md#--allow-worker
|
[`--allow-worker`]: cli.md#--allow-worker
|
||||||
[`--permission`]: cli.md#--permission
|
[`--permission`]: cli.md#--permission
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,9 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"allow-net": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"allow-wasi": {
|
"allow-wasi": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,9 @@ Allow using native addons when using the permission model.
|
||||||
.It Fl -allow-child-process
|
.It Fl -allow-child-process
|
||||||
Allow spawning process when using the permission model.
|
Allow spawning process when using the permission model.
|
||||||
.
|
.
|
||||||
|
.It Fl -allow-net
|
||||||
|
Allow network access when using the permission model.
|
||||||
|
.
|
||||||
.It Fl -allow-wasi
|
.It Fl -allow-wasi
|
||||||
Allow execution of WASI when using the permission model.
|
Allow execution of WASI when using the permission model.
|
||||||
.
|
.
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,10 @@ function setInternalPrepareStackTrace(callback) {
|
||||||
internalPrepareStackTrace = callback;
|
internalPrepareStackTrace = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPermissionModelError(err) {
|
||||||
|
return typeof err !== 'number' && err.code && err.code === 'ERR_ACCESS_DENIED';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every realm has its own prepareStackTraceCallback. When `error.stack` is
|
* Every realm has its own prepareStackTraceCallback. When `error.stack` is
|
||||||
* accessed, if the error is created in a shadow realm, the shadow realm's
|
* accessed, if the error is created in a shadow realm, the shadow realm's
|
||||||
|
|
@ -762,8 +766,14 @@ class ExceptionWithHostPort extends Error {
|
||||||
// This can be replaced with [ code ] = errmap.get(err) when this method
|
// This can be replaced with [ code ] = errmap.get(err) when this method
|
||||||
// is no longer exposed to user land.
|
// is no longer exposed to user land.
|
||||||
util ??= require('util');
|
util ??= require('util');
|
||||||
const code = util.getSystemErrorName(err);
|
let code;
|
||||||
let details = '';
|
let details = '';
|
||||||
|
// True when permission model is enabled
|
||||||
|
if (isPermissionModelError(err)) {
|
||||||
|
code = err.code;
|
||||||
|
details = ` ${err.message}`;
|
||||||
|
} else {
|
||||||
|
code = util.getSystemErrorName(err);
|
||||||
if (port && port > 0) {
|
if (port && port > 0) {
|
||||||
details = ` ${address}:${port}`;
|
details = ` ${address}:${port}`;
|
||||||
} else if (address) {
|
} else if (address) {
|
||||||
|
|
@ -772,7 +782,7 @@ class ExceptionWithHostPort extends Error {
|
||||||
if (additional) {
|
if (additional) {
|
||||||
details += ` - Local (${additional})`;
|
details += ` - Local (${additional})`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
super(`${syscall} ${code}${details}`);
|
super(`${syscall} ${code}${details}`);
|
||||||
|
|
||||||
this.errno = err;
|
this.errno = err;
|
||||||
|
|
@ -798,7 +808,7 @@ class DNSException extends Error {
|
||||||
constructor(code, syscall, hostname) {
|
constructor(code, syscall, hostname) {
|
||||||
let errno;
|
let errno;
|
||||||
// If `code` is of type number, it is a libuv error number, else it is a
|
// If `code` is of type number, it is a libuv error number, else it is a
|
||||||
// c-ares error code.
|
// c-ares/permission model error code.
|
||||||
// TODO(joyeecheung): translate c-ares error codes into numeric ones and
|
// TODO(joyeecheung): translate c-ares error codes into numeric ones and
|
||||||
// make them available in a property that's not error.errno (since they
|
// make them available in a property that's not error.errno (since they
|
||||||
// can be in conflict with libuv error codes). Also make sure
|
// can be in conflict with libuv error codes). Also make sure
|
||||||
|
|
@ -813,6 +823,9 @@ class DNSException extends Error {
|
||||||
} else {
|
} else {
|
||||||
code = lazyInternalUtil().getSystemErrorName(code);
|
code = lazyInternalUtil().getSystemErrorName(code);
|
||||||
}
|
}
|
||||||
|
} else if (isPermissionModelError(code)) {
|
||||||
|
// Expects a ERR_ACCESS_DENIED object
|
||||||
|
code = code.code;
|
||||||
}
|
}
|
||||||
super(`${syscall} ${code}${hostname ? ` ${hostname}` : ''}`);
|
super(`${syscall} ${code}${hostname ? ` ${hostname}` : ''}`);
|
||||||
this.errno = errno;
|
this.errno = errno;
|
||||||
|
|
|
||||||
|
|
@ -600,6 +600,17 @@ function initializePermission() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const experimentalWarnFlags = [
|
||||||
|
'--allow-net',
|
||||||
|
];
|
||||||
|
for (const flag of experimentalWarnFlags) {
|
||||||
|
if (getOptionValue(flag)) {
|
||||||
|
process.emitWarning(
|
||||||
|
`The flag ${flag} is under experimental phase.`,
|
||||||
|
'ExperimentalWarning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ObjectDefineProperty(process, 'permission', {
|
ObjectDefineProperty(process, 'permission', {
|
||||||
__proto__: null,
|
__proto__: null,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
|
|
@ -614,6 +625,7 @@ function initializePermission() {
|
||||||
'--allow-fs-write',
|
'--allow-fs-write',
|
||||||
'--allow-addons',
|
'--allow-addons',
|
||||||
'--allow-child-process',
|
'--allow-child-process',
|
||||||
|
'--allow-net',
|
||||||
'--allow-wasi',
|
'--allow-wasi',
|
||||||
'--allow-worker',
|
'--allow-worker',
|
||||||
];
|
];
|
||||||
|
|
|
||||||
2
node.gyp
2
node.gyp
|
|
@ -163,6 +163,7 @@
|
||||||
'src/permission/permission.cc',
|
'src/permission/permission.cc',
|
||||||
'src/permission/wasi_permission.cc',
|
'src/permission/wasi_permission.cc',
|
||||||
'src/permission/worker_permission.cc',
|
'src/permission/worker_permission.cc',
|
||||||
|
'src/permission/net_permission.cc',
|
||||||
'src/pipe_wrap.cc',
|
'src/pipe_wrap.cc',
|
||||||
'src/process_wrap.cc',
|
'src/process_wrap.cc',
|
||||||
'src/signal_wrap.cc',
|
'src/signal_wrap.cc',
|
||||||
|
|
@ -292,6 +293,7 @@
|
||||||
'src/permission/permission.h',
|
'src/permission/permission.h',
|
||||||
'src/permission/wasi_permission.h',
|
'src/permission/wasi_permission.h',
|
||||||
'src/permission/worker_permission.h',
|
'src/permission/worker_permission.h',
|
||||||
|
'src/permission/net_permission.h',
|
||||||
'src/pipe_wrap.h',
|
'src/pipe_wrap.h',
|
||||||
'src/req_wrap.h',
|
'src/req_wrap.h',
|
||||||
'src/req_wrap-inl.h',
|
'src/req_wrap-inl.h',
|
||||||
|
|
|
||||||
|
|
@ -1613,6 +1613,16 @@ Maybe<int> SoaTraits::Parse(QuerySoaWrap* wrap,
|
||||||
}
|
}
|
||||||
|
|
||||||
int ReverseTraits::Send(QueryReverseWrap* wrap, const char* name) {
|
int ReverseTraits::Send(QueryReverseWrap* wrap, const char* name) {
|
||||||
|
permission::PermissionScope scope = permission::PermissionScope::kNet;
|
||||||
|
Environment* env_holder = wrap->env();
|
||||||
|
|
||||||
|
if (!env_holder->permission()->is_granted(env_holder, scope, name))
|
||||||
|
[[unlikely]] {
|
||||||
|
wrap->QueuePermissionModelResponseCallback(name);
|
||||||
|
// Error will be returned in the callback
|
||||||
|
return ARES_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
int length, family;
|
int length, family;
|
||||||
char address_buffer[sizeof(struct in6_addr)];
|
char address_buffer[sizeof(struct in6_addr)];
|
||||||
|
|
||||||
|
|
@ -1851,6 +1861,10 @@ void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
|
||||||
CHECK(args[4]->IsUint32());
|
CHECK(args[4]->IsUint32());
|
||||||
Local<Object> req_wrap_obj = args[0].As<Object>();
|
Local<Object> req_wrap_obj = args[0].As<Object>();
|
||||||
node::Utf8Value hostname(env->isolate(), args[1]);
|
node::Utf8Value hostname(env->isolate(), args[1]);
|
||||||
|
|
||||||
|
ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS(
|
||||||
|
env, permission::PermissionScope::kNet, hostname.ToStringView(), args);
|
||||||
|
|
||||||
std::string ascii_hostname = ada::idna::to_ascii(hostname.ToStringView());
|
std::string ascii_hostname = ada::idna::to_ascii(hostname.ToStringView());
|
||||||
|
|
||||||
int32_t flags = 0;
|
int32_t flags = 0;
|
||||||
|
|
@ -1925,10 +1939,18 @@ void GetNameInfo(const FunctionCallbackInfo<Value>& args) {
|
||||||
TRACING_CATEGORY_NODE2(dns, native), "lookupService", req_wrap.get(),
|
TRACING_CATEGORY_NODE2(dns, native), "lookupService", req_wrap.get(),
|
||||||
"ip", TRACE_STR_COPY(*ip), "port", port);
|
"ip", TRACE_STR_COPY(*ip), "port", port);
|
||||||
|
|
||||||
int err = req_wrap->Dispatch(uv_getnameinfo,
|
int err = 0;
|
||||||
|
if (!env->permission()->is_granted(
|
||||||
|
env, permission::PermissionScope::kNet, ip.ToStringView()))
|
||||||
|
[[unlikely]] {
|
||||||
|
req_wrap->InsufficientPermissionError(*ip);
|
||||||
|
} else {
|
||||||
|
err = req_wrap->Dispatch(uv_getnameinfo,
|
||||||
AfterGetNameInfo,
|
AfterGetNameInfo,
|
||||||
reinterpret_cast<struct sockaddr*>(&addr),
|
reinterpret_cast<struct sockaddr*>(&addr),
|
||||||
NI_NAMEREQD);
|
NI_NAMEREQD);
|
||||||
|
}
|
||||||
|
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
// Release ownership of the pointer allowing the ownership to be transferred
|
// Release ownership of the pointer allowing the ownership to be transferred
|
||||||
USE(req_wrap.release());
|
USE(req_wrap.release());
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "memory_tracker.h"
|
#include "memory_tracker.h"
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
#include "node_internals.h"
|
#include "node_internals.h"
|
||||||
|
#include "permission/permission.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#include "ares.h"
|
#include "ares.h"
|
||||||
|
|
@ -214,6 +215,8 @@ class GetNameInfoReqWrap final : public ReqWrap<uv_getnameinfo_t> {
|
||||||
public:
|
public:
|
||||||
GetNameInfoReqWrap(Environment* env, v8::Local<v8::Object> req_wrap_obj);
|
GetNameInfoReqWrap(Environment* env, v8::Local<v8::Object> req_wrap_obj);
|
||||||
|
|
||||||
|
SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(permission::PermissionScope::kNet)
|
||||||
|
|
||||||
SET_NO_MEMORY_INFO()
|
SET_NO_MEMORY_INFO()
|
||||||
SET_MEMORY_INFO_NAME(GetNameInfoReqWrap)
|
SET_MEMORY_INFO_NAME(GetNameInfoReqWrap)
|
||||||
SET_SELF_SIZE(GetNameInfoReqWrap)
|
SET_SELF_SIZE(GetNameInfoReqWrap)
|
||||||
|
|
@ -249,6 +252,15 @@ class QueryWrap final : public AsyncWrap {
|
||||||
void AresQuery(const char* name,
|
void AresQuery(const char* name,
|
||||||
ares_dns_class_t dnsclass,
|
ares_dns_class_t dnsclass,
|
||||||
ares_dns_rec_type_t type) {
|
ares_dns_rec_type_t type) {
|
||||||
|
permission::PermissionScope scope = permission::PermissionScope::kNet;
|
||||||
|
Environment* env_holder = env();
|
||||||
|
|
||||||
|
if (!env_holder->permission()->is_granted(env_holder, scope, name))
|
||||||
|
[[unlikely]] {
|
||||||
|
QueuePermissionModelResponseCallback(name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
channel_->EnsureServers();
|
channel_->EnsureServers();
|
||||||
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
|
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
|
||||||
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this,
|
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this,
|
||||||
|
|
@ -262,6 +274,8 @@ class QueryWrap final : public AsyncWrap {
|
||||||
nullptr);
|
nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(permission::PermissionScope::kNet)
|
||||||
|
|
||||||
void ParseError(int status) {
|
void ParseError(int status) {
|
||||||
CHECK_NE(status, ARES_SUCCESS);
|
CHECK_NE(status, ARES_SUCCESS);
|
||||||
v8::HandleScope handle_scope(env()->isolate());
|
v8::HandleScope handle_scope(env()->isolate());
|
||||||
|
|
@ -356,6 +370,20 @@ class QueryWrap final : public AsyncWrap {
|
||||||
wrap->QueueResponseCallback(status);
|
wrap->QueueResponseCallback(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QueuePermissionModelResponseCallback(const char* resource) {
|
||||||
|
BaseObjectPtr<QueryWrap<Traits>> strong_ref{this};
|
||||||
|
const std::string res{resource};
|
||||||
|
env()->SetImmediate([this, strong_ref, res](Environment*) {
|
||||||
|
InsufficientPermissionError(res);
|
||||||
|
|
||||||
|
// Delete once strong_ref goes out of scope.
|
||||||
|
Detach();
|
||||||
|
});
|
||||||
|
|
||||||
|
channel_->set_query_last_ok(true);
|
||||||
|
channel_->ModifyActivityQueryCount(-1);
|
||||||
|
}
|
||||||
|
|
||||||
void QueueResponseCallback(int status) {
|
void QueueResponseCallback(int status) {
|
||||||
BaseObjectPtr<QueryWrap<Traits>> strong_ref{this};
|
BaseObjectPtr<QueryWrap<Traits>> strong_ref{this};
|
||||||
env()->SetImmediate([this, strong_ref](Environment*) {
|
env()->SetImmediate([this, strong_ref](Environment*) {
|
||||||
|
|
|
||||||
|
|
@ -958,6 +958,10 @@ Environment::Environment(IsolateData* isolate_data,
|
||||||
options_->allow_fs_write,
|
options_->allow_fs_write,
|
||||||
permission::PermissionScope::kFileSystemWrite);
|
permission::PermissionScope::kFileSystemWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options_->allow_net) {
|
||||||
|
permission()->Apply(this, {"*"}, permission::PermissionScope::kNet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -565,6 +565,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
||||||
"allow use of child process when any permissions are set",
|
"allow use of child process when any permissions are set",
|
||||||
&EnvironmentOptions::allow_child_process,
|
&EnvironmentOptions::allow_child_process,
|
||||||
kAllowedInEnvvar);
|
kAllowedInEnvvar);
|
||||||
|
AddOption("--allow-net",
|
||||||
|
"allow use of network when any permissions are set",
|
||||||
|
&EnvironmentOptions::allow_net,
|
||||||
|
kAllowedInEnvvar);
|
||||||
AddOption("--allow-wasi",
|
AddOption("--allow-wasi",
|
||||||
"allow wasi when any permissions are set",
|
"allow wasi when any permissions are set",
|
||||||
&EnvironmentOptions::allow_wasi,
|
&EnvironmentOptions::allow_wasi,
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,7 @@ class EnvironmentOptions : public Options {
|
||||||
std::vector<std::string> allow_fs_write;
|
std::vector<std::string> allow_fs_write;
|
||||||
bool allow_addons = false;
|
bool allow_addons = false;
|
||||||
bool allow_child_process = false;
|
bool allow_child_process = false;
|
||||||
|
bool allow_net = false;
|
||||||
bool allow_wasi = false;
|
bool allow_wasi = false;
|
||||||
bool allow_worker_threads = false;
|
bool allow_worker_threads = false;
|
||||||
bool experimental_repl_await = true;
|
bool experimental_repl_await = true;
|
||||||
|
|
|
||||||
23
src/permission/net_permission.cc
Normal file
23
src/permission/net_permission.cc
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#include "net_permission.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
namespace permission {
|
||||||
|
|
||||||
|
void NetPermission::Apply(Environment* env,
|
||||||
|
const std::vector<std::string>& allow,
|
||||||
|
PermissionScope scope) {
|
||||||
|
allow_net_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetPermission::is_granted(Environment* env,
|
||||||
|
PermissionScope perm,
|
||||||
|
const std::string_view& param) const {
|
||||||
|
return allow_net_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace permission
|
||||||
|
} // namespace node
|
||||||
31
src/permission/net_permission.h
Normal file
31
src/permission/net_permission.h
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef SRC_PERMISSION_NET_PERMISSION_H_
|
||||||
|
#define SRC_PERMISSION_NET_PERMISSION_H_
|
||||||
|
|
||||||
|
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "permission/permission_base.h"
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
namespace permission {
|
||||||
|
|
||||||
|
class NetPermission final : public PermissionBase {
|
||||||
|
public:
|
||||||
|
void Apply(Environment* env,
|
||||||
|
const std::vector<std::string>& allow,
|
||||||
|
PermissionScope scope) override;
|
||||||
|
bool is_granted(Environment* env,
|
||||||
|
PermissionScope perm,
|
||||||
|
const std::string_view& param) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool allow_net_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace permission
|
||||||
|
|
||||||
|
} // namespace node
|
||||||
|
|
||||||
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
#endif // SRC_PERMISSION_NET_PERMISSION_H_
|
||||||
|
|
@ -84,6 +84,7 @@ Permission::Permission() : enabled_(false) {
|
||||||
std::shared_ptr<PermissionBase> inspector =
|
std::shared_ptr<PermissionBase> inspector =
|
||||||
std::make_shared<InspectorPermission>();
|
std::make_shared<InspectorPermission>();
|
||||||
std::shared_ptr<PermissionBase> wasi = std::make_shared<WASIPermission>();
|
std::shared_ptr<PermissionBase> wasi = std::make_shared<WASIPermission>();
|
||||||
|
std::shared_ptr<PermissionBase> net = std::make_shared<NetPermission>();
|
||||||
#define V(Name, _, __, ___) \
|
#define V(Name, _, __, ___) \
|
||||||
nodes_.insert(std::make_pair(PermissionScope::k##Name, fs));
|
nodes_.insert(std::make_pair(PermissionScope::k##Name, fs));
|
||||||
FILESYSTEM_PERMISSIONS(V)
|
FILESYSTEM_PERMISSIONS(V)
|
||||||
|
|
@ -104,6 +105,10 @@ Permission::Permission() : enabled_(false) {
|
||||||
nodes_.insert(std::make_pair(PermissionScope::k##Name, wasi));
|
nodes_.insert(std::make_pair(PermissionScope::k##Name, wasi));
|
||||||
WASI_PERMISSIONS(V)
|
WASI_PERMISSIONS(V)
|
||||||
#undef V
|
#undef V
|
||||||
|
#define V(Name, _, __, ___) \
|
||||||
|
nodes_.insert(std::make_pair(PermissionScope::k##Name, net));
|
||||||
|
NET_PERMISSIONS(V)
|
||||||
|
#undef V
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* GetErrorFlagSuggestion(node::permission::PermissionScope perm) {
|
const char* GetErrorFlagSuggestion(node::permission::PermissionScope perm) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "permission/child_process_permission.h"
|
#include "permission/child_process_permission.h"
|
||||||
#include "permission/fs_permission.h"
|
#include "permission/fs_permission.h"
|
||||||
#include "permission/inspector_permission.h"
|
#include "permission/inspector_permission.h"
|
||||||
|
#include "permission/net_permission.h"
|
||||||
#include "permission/permission_base.h"
|
#include "permission/permission_base.h"
|
||||||
#include "permission/wasi_permission.h"
|
#include "permission/wasi_permission.h"
|
||||||
#include "permission/worker_permission.h"
|
#include "permission/worker_permission.h"
|
||||||
|
|
@ -45,6 +46,32 @@ namespace permission {
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
#define ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS( \
|
||||||
|
env, perm_, resource_, args, ...) \
|
||||||
|
do { \
|
||||||
|
if (!env->permission()->is_granted(env, perm_, resource_)) [[unlikely]] { \
|
||||||
|
Local<Value> err_access; \
|
||||||
|
if (permission::CreateAccessDeniedError(env, perm_, resource_) \
|
||||||
|
.ToLocal(&err_access)) { \
|
||||||
|
args.GetReturnValue().Set(err_access); \
|
||||||
|
} else { \
|
||||||
|
args.GetReturnValue().Set(UV_EACCES); \
|
||||||
|
} \
|
||||||
|
return __VA_ARGS__; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(scope) \
|
||||||
|
void InsufficientPermissionError(const std::string resource) { \
|
||||||
|
v8::HandleScope handle_scope(env()->isolate()); \
|
||||||
|
v8::Context::Scope context_scope(env()->context()); \
|
||||||
|
v8::Local<v8::Value> arg; \
|
||||||
|
if (!permission::CreateAccessDeniedError(env(), scope, resource) \
|
||||||
|
.ToLocal(&arg)) { \
|
||||||
|
} \
|
||||||
|
MakeCallback(env()->oncomplete_string(), 1, &arg); \
|
||||||
|
}
|
||||||
|
|
||||||
class Permission {
|
class Permission {
|
||||||
public:
|
public:
|
||||||
Permission();
|
Permission();
|
||||||
|
|
@ -91,6 +118,10 @@ class Permission {
|
||||||
bool enabled_;
|
bool enabled_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
v8::MaybeLocal<v8::Value> CreateAccessDeniedError(Environment* env,
|
||||||
|
PermissionScope perm,
|
||||||
|
const std::string_view& res);
|
||||||
|
|
||||||
} // namespace permission
|
} // namespace permission
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,15 @@ namespace permission {
|
||||||
|
|
||||||
#define INSPECTOR_PERMISSIONS(V) V(Inspector, "inspector", PermissionsRoot, "")
|
#define INSPECTOR_PERMISSIONS(V) V(Inspector, "inspector", PermissionsRoot, "")
|
||||||
|
|
||||||
|
#define NET_PERMISSIONS(V) V(Net, "net", PermissionsRoot, "--allow-net")
|
||||||
|
|
||||||
#define PERMISSIONS(V) \
|
#define PERMISSIONS(V) \
|
||||||
FILESYSTEM_PERMISSIONS(V) \
|
FILESYSTEM_PERMISSIONS(V) \
|
||||||
CHILD_PROCESS_PERMISSIONS(V) \
|
CHILD_PROCESS_PERMISSIONS(V) \
|
||||||
WASI_PERMISSIONS(V) \
|
WASI_PERMISSIONS(V) \
|
||||||
WORKER_THREADS_PERMISSIONS(V) \
|
WORKER_THREADS_PERMISSIONS(V) \
|
||||||
INSPECTOR_PERMISSIONS(V)
|
INSPECTOR_PERMISSIONS(V) \
|
||||||
|
NET_PERMISSIONS(V)
|
||||||
|
|
||||||
#define V(name, _, __, ___) k##name,
|
#define V(name, _, __, ___) k##name,
|
||||||
enum class PermissionScope {
|
enum class PermissionScope {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
#include "node_buffer.h"
|
#include "node_buffer.h"
|
||||||
#include "node_external_reference.h"
|
#include "node_external_reference.h"
|
||||||
#include "node_internals.h"
|
#include "node_internals.h"
|
||||||
|
#include "permission/permission.h"
|
||||||
#include "stream_base-inl.h"
|
#include "stream_base-inl.h"
|
||||||
#include "stream_wrap.h"
|
#include "stream_wrap.h"
|
||||||
#include "util-inl.h"
|
#include "util-inl.h"
|
||||||
|
|
@ -245,6 +246,9 @@ void TCPWrap::Bind(
|
||||||
ASSIGN_OR_RETURN_UNWRAP(
|
ASSIGN_OR_RETURN_UNWRAP(
|
||||||
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
|
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
|
||||||
Environment* env = wrap->env();
|
Environment* env = wrap->env();
|
||||||
|
|
||||||
|
THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kNet, "");
|
||||||
|
|
||||||
node::Utf8Value ip_address(env->isolate(), args[0]);
|
node::Utf8Value ip_address(env->isolate(), args[0]);
|
||||||
int port;
|
int port;
|
||||||
unsigned int flags = 0;
|
unsigned int flags = 0;
|
||||||
|
|
@ -285,6 +289,9 @@ void TCPWrap::Listen(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = wrap->env();
|
Environment* env = wrap->env();
|
||||||
int backlog;
|
int backlog;
|
||||||
if (!args[0]->Int32Value(env->context()).To(&backlog)) return;
|
if (!args[0]->Int32Value(env->context()).To(&backlog)) return;
|
||||||
|
|
||||||
|
THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kNet, "");
|
||||||
|
|
||||||
int err = uv_listen(reinterpret_cast<uv_stream_t*>(&wrap->handle_),
|
int err = uv_listen(reinterpret_cast<uv_stream_t*>(&wrap->handle_),
|
||||||
backlog,
|
backlog,
|
||||||
OnConnection);
|
OnConnection);
|
||||||
|
|
@ -329,6 +336,9 @@ void TCPWrap::Connect(const FunctionCallbackInfo<Value>& args,
|
||||||
Local<Object> req_wrap_obj = args[0].As<Object>();
|
Local<Object> req_wrap_obj = args[0].As<Object>();
|
||||||
node::Utf8Value ip_address(env->isolate(), args[1]);
|
node::Utf8Value ip_address(env->isolate(), args[1]);
|
||||||
|
|
||||||
|
ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS(
|
||||||
|
env, permission::PermissionScope::kNet, ip_address.ToStringView(), args);
|
||||||
|
|
||||||
T addr;
|
T addr;
|
||||||
int err = uv_ip_addr(*ip_address, &addr);
|
int err = uv_ip_addr(*ip_address, &addr);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#include "node_errors.h"
|
#include "node_errors.h"
|
||||||
#include "node_external_reference.h"
|
#include "node_external_reference.h"
|
||||||
#include "node_sockaddr-inl.h"
|
#include "node_sockaddr-inl.h"
|
||||||
|
#include "permission/permission.h"
|
||||||
#include "req_wrap-inl.h"
|
#include "req_wrap-inl.h"
|
||||||
#include "util-inl.h"
|
#include "util-inl.h"
|
||||||
|
|
||||||
|
|
@ -307,6 +308,13 @@ void UDPWrap::DoBind(const FunctionCallbackInfo<Value>& args, int family) {
|
||||||
CHECK_EQ(args.Length(), 3);
|
CHECK_EQ(args.Length(), 3);
|
||||||
|
|
||||||
node::Utf8Value address(args.GetIsolate(), args[0]);
|
node::Utf8Value address(args.GetIsolate(), args[0]);
|
||||||
|
|
||||||
|
// Check for network permission
|
||||||
|
Environment* env = wrap->env();
|
||||||
|
|
||||||
|
ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS(
|
||||||
|
env, permission::PermissionScope::kNet, address.ToStringView(), args);
|
||||||
|
|
||||||
Local<Context> ctx = args.GetIsolate()->GetCurrentContext();
|
Local<Context> ctx = args.GetIsolate()->GetCurrentContext();
|
||||||
uint32_t port, flags;
|
uint32_t port, flags;
|
||||||
if (!args[1]->Uint32Value(ctx).To(&port) ||
|
if (!args[1]->Uint32Value(ctx).To(&port) ||
|
||||||
|
|
@ -332,6 +340,13 @@ void UDPWrap::DoConnect(const FunctionCallbackInfo<Value>& args, int family) {
|
||||||
ASSIGN_OR_RETURN_UNWRAP(
|
ASSIGN_OR_RETURN_UNWRAP(
|
||||||
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
|
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
|
||||||
|
|
||||||
|
// Check for network permission
|
||||||
|
Environment* env = wrap->env();
|
||||||
|
THROW_IF_INSUFFICIENT_PERMISSIONS(env,
|
||||||
|
permission::PermissionScope::kNet,
|
||||||
|
"",
|
||||||
|
args.GetReturnValue().Set(UV_EACCES));
|
||||||
|
|
||||||
CHECK_EQ(args.Length(), 2);
|
CHECK_EQ(args.Length(), 2);
|
||||||
|
|
||||||
node::Utf8Value address(args.GetIsolate(), args[0]);
|
node::Utf8Value address(args.GetIsolate(), args[0]);
|
||||||
|
|
@ -523,6 +538,12 @@ void UDPWrap::DoSend(const FunctionCallbackInfo<Value>& args, int family) {
|
||||||
ASSIGN_OR_RETURN_UNWRAP(
|
ASSIGN_OR_RETURN_UNWRAP(
|
||||||
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
|
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
|
||||||
|
|
||||||
|
// Check for network permission
|
||||||
|
THROW_IF_INSUFFICIENT_PERMISSIONS(env,
|
||||||
|
permission::PermissionScope::kNet,
|
||||||
|
"",
|
||||||
|
args.GetReturnValue().Set(UV_EACCES));
|
||||||
|
|
||||||
CHECK(args.Length() == 4 || args.Length() == 6);
|
CHECK(args.Length() == 4 || args.Length() == 6);
|
||||||
CHECK(args[0]->IsObject());
|
CHECK(args[0]->IsObject());
|
||||||
CHECK(args[1]->IsArray());
|
CHECK(args[1]->IsArray());
|
||||||
|
|
|
||||||
9
test/fixtures/permission/net-fetch.js
vendored
Normal file
9
test/fixtures/permission/net-fetch.js
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const url = process.env.URL;
|
||||||
|
|
||||||
|
fetch(url).catch(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.cause.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
15
test/fixtures/permission/net-http.js
vendored
Normal file
15
test/fixtures/permission/net-http.js
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
const url = process.env.URL;
|
||||||
|
|
||||||
|
{
|
||||||
|
const req = http.get(url, common.mustNotCall('HTTP request should be blocked'));
|
||||||
|
|
||||||
|
req.on('error', common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
}
|
||||||
19
test/fixtures/permission/net-https.js
vendored
Normal file
19
test/fixtures/permission/net-https.js
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
const url = process.env.URL;
|
||||||
|
|
||||||
|
{
|
||||||
|
const options = {
|
||||||
|
rejectUnauthorized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = https.get(url, options, common.mustNotCall('HTTPS request should be blocked'));
|
||||||
|
|
||||||
|
req.on('error', common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
}
|
||||||
35
test/fixtures/permission/net-tcp.js
vendored
Normal file
35
test/fixtures/permission/net-tcp.js
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const net = require('net');
|
||||||
|
|
||||||
|
const host = process.env.HOST || 'localhost';
|
||||||
|
const port = parseInt(process.env.PORT, 10);
|
||||||
|
|
||||||
|
{
|
||||||
|
const client = net.connect({
|
||||||
|
port,
|
||||||
|
host,
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('error', common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
|
||||||
|
client.on('connect', common.mustNotCall('TCP connection should be blocked'));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const client = net.connect({
|
||||||
|
port,
|
||||||
|
host: '127.0.0.1',
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('error', common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
assert.strictEqual(err.syscall, 'connect');
|
||||||
|
}));
|
||||||
|
|
||||||
|
client.on('connect', common.mustNotCall('TCP connection should be blocked'));
|
||||||
|
}
|
||||||
|
|
@ -25,3 +25,14 @@ const assert = require('assert');
|
||||||
{
|
{
|
||||||
assert.ok(!process.permission.has('FileSystemWrite', Buffer.from('reference')));
|
assert.ok(!process.permission.has('FileSystemWrite', Buffer.from('reference')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
assert.ok(!process.permission.has('fs'));
|
||||||
|
assert.ok(process.permission.has('fs.read'));
|
||||||
|
assert.ok(!process.permission.has('fs.write'));
|
||||||
|
assert.ok(!process.permission.has('wasi'));
|
||||||
|
assert.ok(!process.permission.has('worker'));
|
||||||
|
assert.ok(!process.permission.has('inspector'));
|
||||||
|
assert.ok(!process.permission.has('net'));
|
||||||
|
// TODO(rafaelgss): add addon
|
||||||
|
}
|
||||||
|
|
|
||||||
9
test/parallel/test-permission-net-allowed.js
Normal file
9
test/parallel/test-permission-net-allowed.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Flags: --permission --allow-net --allow-fs-read=*
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('../common');
|
||||||
|
const assert = require('node:assert');
|
||||||
|
|
||||||
|
{
|
||||||
|
assert.ok(process.permission.has('net'));
|
||||||
|
}
|
||||||
94
test/parallel/test-permission-net-dns.js
Normal file
94
test/parallel/test-permission-net-dns.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
// Flags: --permission --allow-fs-read=*
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const dns = require('dns');
|
||||||
|
const { Resolver } = dns.promises;
|
||||||
|
|
||||||
|
{
|
||||||
|
dns.lookup('localhost', common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
dns.promises.lookup('localhost').catch(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
dns.resolve('localhost', common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
dns.resolve('come.on.fhqwhgads.test', (err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
});
|
||||||
|
dns.promises.resolve('localhost').catch(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const resolver = new Resolver();
|
||||||
|
resolver.resolve('localhost').catch(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const ip = '8.8.8.8';
|
||||||
|
dns.reverse(ip, common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
|
||||||
|
dns.promises.reverse(ip).catch(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
dns.lookup('127.0.0.1', common.mustSucceed((address, family) => {
|
||||||
|
assert.strictEqual(typeof address, 'string');
|
||||||
|
assert.ok(family === 4 || family === 6);
|
||||||
|
}));
|
||||||
|
|
||||||
|
dns.lookup('127.0.0.1', { family: 4 }, common.mustSucceed((address, family) => {
|
||||||
|
assert.strictEqual(typeof address, 'string');
|
||||||
|
assert.strictEqual(family, 4);
|
||||||
|
}));
|
||||||
|
|
||||||
|
dns.lookup('127.0.0.1', { all: true }, common.mustSucceed((results) => {
|
||||||
|
assert(Array.isArray(results));
|
||||||
|
assert(results.length > 0);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
dns.resolve4('localhost', common.mustCall((err, addresses) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
|
||||||
|
const resolver = new Resolver();
|
||||||
|
resolver.setServers(['127.0.0.1']); // Do not throw
|
||||||
|
|
||||||
|
resolver.resolve('localhost').catch(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
dns.lookupService('127.0.0.1', 80, common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
|
||||||
|
dns.lookupService('8.8.8.8', 80, common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
dns.promises.lookupService('127.0.0.1', 80).catch(common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const resolver = new Resolver();
|
||||||
|
resolver.getServers(); // Do not throw
|
||||||
|
}
|
||||||
42
test/parallel/test-permission-net-fetch.js
Normal file
42
test/parallel/test-permission-net-fetch.js
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const { isMainThread } = require('worker_threads');
|
||||||
|
|
||||||
|
if (!isMainThread) {
|
||||||
|
common.skip('This test only works on a main thread');
|
||||||
|
}
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const http = require('http');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
|
||||||
|
const file = fixtures.path('permission', 'net-fetch.js');
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
res.writeHead(200);
|
||||||
|
res.end('Hello');
|
||||||
|
}).listen(0, common.mustCall(() => {
|
||||||
|
const port = server.address().port;
|
||||||
|
const url = `http://localhost:${port}`;
|
||||||
|
|
||||||
|
const { status, stderr } = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
'--permission',
|
||||||
|
'--allow-fs-read=*',
|
||||||
|
file,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
URL: url,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
|
||||||
|
assert.strictEqual(status, 0, stderr.toString());
|
||||||
|
}));
|
||||||
41
test/parallel/test-permission-net-http.js
Normal file
41
test/parallel/test-permission-net-http.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const { isMainThread } = require('worker_threads');
|
||||||
|
|
||||||
|
if (!isMainThread) {
|
||||||
|
common.skip('This test only works on a main thread');
|
||||||
|
}
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const http = require('http');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
|
||||||
|
const file = fixtures.path('permission', 'net-http.js');
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
res.writeHead(200);
|
||||||
|
res.end('Hello');
|
||||||
|
}).listen(0, common.mustCall(() => {
|
||||||
|
const port = server.address().port;
|
||||||
|
const url = `http://localhost:${port}`;
|
||||||
|
|
||||||
|
const { status, stderr } = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
'--permission',
|
||||||
|
'--allow-fs-read=*',
|
||||||
|
file,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
URL: url,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
assert.strictEqual(status, 0, stderr.toString());
|
||||||
|
}));
|
||||||
48
test/parallel/test-permission-net-https.js
Normal file
48
test/parallel/test-permission-net-https.js
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto) { common.skip('missing crypto'); };
|
||||||
|
const { isMainThread } = require('worker_threads');
|
||||||
|
|
||||||
|
if (!isMainThread) {
|
||||||
|
common.skip('This test only works on a main thread');
|
||||||
|
}
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const https = require('https');
|
||||||
|
const fs = require('fs');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
|
||||||
|
const file = fixtures.path('permission', 'net-https.js');
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
key: fs.readFileSync(fixtures.path('keys', 'agent1-key.pem')),
|
||||||
|
cert: fs.readFileSync(fixtures.path('keys', 'agent1-cert.pem'))
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = https.createServer(options, (req, res) => {
|
||||||
|
res.writeHead(200);
|
||||||
|
res.end('Hello');
|
||||||
|
}).listen(0, common.mustCall(() => {
|
||||||
|
const port = server.address().port;
|
||||||
|
const url = `https://localhost:${port}`;
|
||||||
|
|
||||||
|
const { status, stderr } = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
'--permission',
|
||||||
|
'--allow-fs-read=*',
|
||||||
|
file,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
URL: url,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
assert.strictEqual(status, 0, stderr.toString());
|
||||||
|
}));
|
||||||
38
test/parallel/test-permission-net-tcp.js
Normal file
38
test/parallel/test-permission-net-tcp.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const { isMainThread } = require('worker_threads');
|
||||||
|
|
||||||
|
if (!isMainThread) {
|
||||||
|
common.skip('This test only works on a main thread');
|
||||||
|
}
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const net = require('net');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
|
||||||
|
const file = fixtures.path('permission', 'net-tcp.js');
|
||||||
|
|
||||||
|
const server = net.createServer().listen(0, common.mustCall(() => {
|
||||||
|
const port = server.address().port;
|
||||||
|
|
||||||
|
const { status, stderr } = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
'--permission',
|
||||||
|
'--allow-fs-read=*',
|
||||||
|
file,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PORT: `${port}`,
|
||||||
|
HOST: 'localhost',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
assert.strictEqual(status, 0, stderr.toString());
|
||||||
|
}));
|
||||||
22
test/parallel/test-permission-net-udp.js
Normal file
22
test/parallel/test-permission-net-udp.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Flags: --permission --allow-fs-read=*
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const { isMainThread } = require('worker_threads');
|
||||||
|
|
||||||
|
if (!isMainThread) {
|
||||||
|
common.skip('This test only works on a main thread');
|
||||||
|
}
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const dgram = require('dgram');
|
||||||
|
|
||||||
|
{
|
||||||
|
const socket = dgram.createSocket('udp4');
|
||||||
|
|
||||||
|
socket.on('error', common.mustCall((err) => {
|
||||||
|
assert.strictEqual(err.code, 'ERR_ACCESS_DENIED');
|
||||||
|
}));
|
||||||
|
|
||||||
|
socket.bind(0, common.mustNotCall());
|
||||||
|
}
|
||||||
7
test/parallel/test-permission-net-warning.js
Normal file
7
test/parallel/test-permission-net-warning.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Flags: --permission --allow-net --allow-fs-read=*
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
common.expectWarning('ExperimentalWarning',
|
||||||
|
'The flag --allow-net is under experimental phase.');
|
||||||
18
test/parallel/test-permission-net-websocket.js
Normal file
18
test/parallel/test-permission-net-websocket.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Flags: --permission --allow-fs-read=*
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto) {
|
||||||
|
common.skip('no crypto');
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsUrl = 'ws://nodejs.org';
|
||||||
|
|
||||||
|
const ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
ws.addEventListener('open', common.mustNotCall('WebSocket connection should be blocked'));
|
||||||
|
// WebSocket implementation doesn't expose the Node.js specific errors
|
||||||
|
// so ERR_ACCESS_DENIED won't be "caught" explicitly.
|
||||||
|
// For now, let's just assert the error
|
||||||
|
// TODO(rafaelgss): make this test more comprehensive
|
||||||
|
ws.addEventListener('error', common.mustCall());
|
||||||
Loading…
Reference in New Issue
Block a user