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:
Rafael Gonzaga 2025-06-17 09:35:23 -03:00 committed by GitHub
parent 07220230d9
commit 462c74181d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 681 additions and 19 deletions

View file

@ -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.
### `--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`
<!-- YAML
@ -1943,6 +1975,7 @@ following permissions are restricted:
* File System - manageable through
[`--allow-fs-read`][], [`--allow-fs-write`][] flags
* Network - manageable through [`--allow-net`][] flag
* Child Process - manageable through [`--allow-child-process`][] flag
* Worker Threads - manageable through [`--allow-worker`][] flag
* WASI - manageable through [`--allow-wasi`][] flag
@ -3297,6 +3330,7 @@ one is included in the list below.
* `--allow-child-process`
* `--allow-fs-read`
* `--allow-fs-write`
* `--allow-net`
* `--allow-wasi`
* `--allow-worker`
* `--conditions`, `-C`
@ -3899,6 +3933,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`--allow-child-process`]: #--allow-child-process
[`--allow-fs-read`]: #--allow-fs-read
[`--allow-fs-write`]: #--allow-fs-write
[`--allow-net`]: #--allow-net
[`--allow-wasi`]: #--allow-wasi
[`--allow-worker`]: #--allow-worker
[`--build-snapshot`]: #--build-snapshot

View file

@ -51,9 +51,9 @@ The available permissions are documented by the [`--permission`][]
flag.
When starting Node.js with `--permission`,
the ability to access the file system through the `fs` module, spawn processes,
use `node:worker_threads`, use native addons, use WASI, and enable the runtime inspector
will be restricted.
the ability to access the file system through the `fs` module, access the network,
spawn processes, use `node:worker_threads`, use native addons, use WASI, and
enable the runtime inspector will be restricted.
```console
$ 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
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.
#### 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.
* When using the Permission Model the following features will be restricted:
* Native modules
* Network
* Child process
* Worker Threads
* 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-fs-read`]: cli.md#--allow-fs-read
[`--allow-fs-write`]: cli.md#--allow-fs-write
[`--allow-net`]: cli.md#--allow-net
[`--allow-wasi`]: cli.md#--allow-wasi
[`--allow-worker`]: cli.md#--allow-worker
[`--permission`]: cli.md#--permission

View file

@ -45,6 +45,9 @@
}
]
},
"allow-net": {
"type": "boolean"
},
"allow-wasi": {
"type": "boolean"
},

View file

@ -85,6 +85,9 @@ Allow using native addons when using the permission model.
.It Fl -allow-child-process
Allow spawning process when using the permission model.
.
.It Fl -allow-net
Allow network access when using the permission model.
.
.It Fl -allow-wasi
Allow execution of WASI when using the permission model.
.

View file

@ -113,6 +113,10 @@ function setInternalPrepareStackTrace(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
* accessed, if the error is created in a shadow realm, the shadow realm's
@ -762,17 +766,23 @@ class ExceptionWithHostPort extends Error {
// This can be replaced with [ code ] = errmap.get(err) when this method
// is no longer exposed to user land.
util ??= require('util');
const code = util.getSystemErrorName(err);
let code;
let details = '';
if (port && port > 0) {
details = ` ${address}:${port}`;
} else if (address) {
details = ` ${address}`;
// True when permission model is enabled
if (isPermissionModelError(err)) {
code = err.code;
details = ` ${err.message}`;
} else {
code = util.getSystemErrorName(err);
if (port && port > 0) {
details = ` ${address}:${port}`;
} else if (address) {
details = ` ${address}`;
}
if (additional) {
details += ` - Local (${additional})`;
}
}
if (additional) {
details += ` - Local (${additional})`;
}
super(`${syscall} ${code}${details}`);
this.errno = err;
@ -798,7 +808,7 @@ class DNSException extends Error {
constructor(code, syscall, hostname) {
let errno;
// 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
// make them available in a property that's not error.errno (since they
// can be in conflict with libuv error codes). Also make sure
@ -813,6 +823,9 @@ class DNSException extends Error {
} else {
code = lazyInternalUtil().getSystemErrorName(code);
}
} else if (isPermissionModelError(code)) {
// Expects a ERR_ACCESS_DENIED object
code = code.code;
}
super(`${syscall} ${code}${hostname ? ` ${hostname}` : ''}`);
this.errno = errno;

View file

@ -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', {
__proto__: null,
enumerable: true,
@ -614,6 +625,7 @@ function initializePermission() {
'--allow-fs-write',
'--allow-addons',
'--allow-child-process',
'--allow-net',
'--allow-wasi',
'--allow-worker',
];

View file

@ -163,6 +163,7 @@
'src/permission/permission.cc',
'src/permission/wasi_permission.cc',
'src/permission/worker_permission.cc',
'src/permission/net_permission.cc',
'src/pipe_wrap.cc',
'src/process_wrap.cc',
'src/signal_wrap.cc',
@ -292,6 +293,7 @@
'src/permission/permission.h',
'src/permission/wasi_permission.h',
'src/permission/worker_permission.h',
'src/permission/net_permission.h',
'src/pipe_wrap.h',
'src/req_wrap.h',
'src/req_wrap-inl.h',

View file

@ -1613,6 +1613,16 @@ Maybe<int> SoaTraits::Parse(QuerySoaWrap* wrap,
}
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;
char address_buffer[sizeof(struct in6_addr)];
@ -1851,6 +1861,10 @@ void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
CHECK(args[4]->IsUint32());
Local<Object> req_wrap_obj = args[0].As<Object>();
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());
int32_t flags = 0;
@ -1925,10 +1939,18 @@ void GetNameInfo(const FunctionCallbackInfo<Value>& args) {
TRACING_CATEGORY_NODE2(dns, native), "lookupService", req_wrap.get(),
"ip", TRACE_STR_COPY(*ip), "port", port);
int err = req_wrap->Dispatch(uv_getnameinfo,
AfterGetNameInfo,
reinterpret_cast<struct sockaddr*>(&addr),
NI_NAMEREQD);
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,
reinterpret_cast<struct sockaddr*>(&addr),
NI_NAMEREQD);
}
if (err == 0)
// Release ownership of the pointer allowing the ownership to be transferred
USE(req_wrap.release());

View file

@ -11,6 +11,7 @@
#include "memory_tracker.h"
#include "node.h"
#include "node_internals.h"
#include "permission/permission.h"
#include "util.h"
#include "ares.h"
@ -214,6 +215,8 @@ class GetNameInfoReqWrap final : public ReqWrap<uv_getnameinfo_t> {
public:
GetNameInfoReqWrap(Environment* env, v8::Local<v8::Object> req_wrap_obj);
SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(permission::PermissionScope::kNet)
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(GetNameInfoReqWrap)
SET_SELF_SIZE(GetNameInfoReqWrap)
@ -249,6 +252,15 @@ class QueryWrap final : public AsyncWrap {
void AresQuery(const char* name,
ares_dns_class_t dnsclass,
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();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this,
@ -262,6 +274,8 @@ class QueryWrap final : public AsyncWrap {
nullptr);
}
SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(permission::PermissionScope::kNet)
void ParseError(int status) {
CHECK_NE(status, ARES_SUCCESS);
v8::HandleScope handle_scope(env()->isolate());
@ -356,6 +370,20 @@ class QueryWrap final : public AsyncWrap {
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) {
BaseObjectPtr<QueryWrap<Traits>> strong_ref{this};
env()->SetImmediate([this, strong_ref](Environment*) {

View file

@ -958,6 +958,10 @@ Environment::Environment(IsolateData* isolate_data,
options_->allow_fs_write,
permission::PermissionScope::kFileSystemWrite);
}
if (options_->allow_net) {
permission()->Apply(this, {"*"}, permission::PermissionScope::kNet);
}
}
}

View file

@ -565,6 +565,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"allow use of child process when any permissions are set",
&EnvironmentOptions::allow_child_process,
kAllowedInEnvvar);
AddOption("--allow-net",
"allow use of network when any permissions are set",
&EnvironmentOptions::allow_net,
kAllowedInEnvvar);
AddOption("--allow-wasi",
"allow wasi when any permissions are set",
&EnvironmentOptions::allow_wasi,

View file

@ -142,6 +142,7 @@ class EnvironmentOptions : public Options {
std::vector<std::string> allow_fs_write;
bool allow_addons = false;
bool allow_child_process = false;
bool allow_net = false;
bool allow_wasi = false;
bool allow_worker_threads = false;
bool experimental_repl_await = true;

View 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

View 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_

View file

@ -84,6 +84,7 @@ Permission::Permission() : enabled_(false) {
std::shared_ptr<PermissionBase> inspector =
std::make_shared<InspectorPermission>();
std::shared_ptr<PermissionBase> wasi = std::make_shared<WASIPermission>();
std::shared_ptr<PermissionBase> net = std::make_shared<NetPermission>();
#define V(Name, _, __, ___) \
nodes_.insert(std::make_pair(PermissionScope::k##Name, fs));
FILESYSTEM_PERMISSIONS(V)
@ -104,6 +105,10 @@ Permission::Permission() : enabled_(false) {
nodes_.insert(std::make_pair(PermissionScope::k##Name, wasi));
WASI_PERMISSIONS(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) {

View file

@ -8,6 +8,7 @@
#include "permission/child_process_permission.h"
#include "permission/fs_permission.h"
#include "permission/inspector_permission.h"
#include "permission/net_permission.h"
#include "permission/permission_base.h"
#include "permission/wasi_permission.h"
#include "permission/worker_permission.h"
@ -45,6 +46,32 @@ namespace permission {
} \
} 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 {
public:
Permission();
@ -91,6 +118,10 @@ class Permission {
bool enabled_;
};
v8::MaybeLocal<v8::Value> CreateAccessDeniedError(Environment* env,
PermissionScope perm,
const std::string_view& res);
} // namespace permission
} // namespace node

View file

@ -29,12 +29,15 @@ namespace permission {
#define INSPECTOR_PERMISSIONS(V) V(Inspector, "inspector", PermissionsRoot, "")
#define NET_PERMISSIONS(V) V(Net, "net", PermissionsRoot, "--allow-net")
#define PERMISSIONS(V) \
FILESYSTEM_PERMISSIONS(V) \
CHILD_PROCESS_PERMISSIONS(V) \
WASI_PERMISSIONS(V) \
WORKER_THREADS_PERMISSIONS(V) \
INSPECTOR_PERMISSIONS(V)
INSPECTOR_PERMISSIONS(V) \
NET_PERMISSIONS(V)
#define V(name, _, __, ___) k##name,
enum class PermissionScope {

View file

@ -28,6 +28,7 @@
#include "node_buffer.h"
#include "node_external_reference.h"
#include "node_internals.h"
#include "permission/permission.h"
#include "stream_base-inl.h"
#include "stream_wrap.h"
#include "util-inl.h"
@ -245,6 +246,9 @@ void TCPWrap::Bind(
ASSIGN_OR_RETURN_UNWRAP(
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
Environment* env = wrap->env();
THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kNet, "");
node::Utf8Value ip_address(env->isolate(), args[0]);
int port;
unsigned int flags = 0;
@ -285,6 +289,9 @@ void TCPWrap::Listen(const FunctionCallbackInfo<Value>& args) {
Environment* env = wrap->env();
int backlog;
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_),
backlog,
OnConnection);
@ -329,6 +336,9 @@ void TCPWrap::Connect(const FunctionCallbackInfo<Value>& args,
Local<Object> req_wrap_obj = args[0].As<Object>();
node::Utf8Value ip_address(env->isolate(), args[1]);
ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kNet, ip_address.ToStringView(), args);
T addr;
int err = uv_ip_addr(*ip_address, &addr);

View file

@ -26,6 +26,7 @@
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_sockaddr-inl.h"
#include "permission/permission.h"
#include "req_wrap-inl.h"
#include "util-inl.h"
@ -307,6 +308,13 @@ void UDPWrap::DoBind(const FunctionCallbackInfo<Value>& args, int family) {
CHECK_EQ(args.Length(), 3);
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();
uint32_t port, flags;
if (!args[1]->Uint32Value(ctx).To(&port) ||
@ -332,6 +340,13 @@ void UDPWrap::DoConnect(const FunctionCallbackInfo<Value>& args, int family) {
ASSIGN_OR_RETURN_UNWRAP(
&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);
node::Utf8Value address(args.GetIsolate(), args[0]);
@ -523,6 +538,12 @@ void UDPWrap::DoSend(const FunctionCallbackInfo<Value>& args, int family) {
ASSIGN_OR_RETURN_UNWRAP(
&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[0]->IsObject());
CHECK(args[1]->IsArray());

9
test/fixtures/permission/net-fetch.js vendored Normal file
View 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
View 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
View 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
View 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'));
}

View file

@ -25,3 +25,14 @@ const assert = require('assert');
{
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
}

View 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'));
}

View 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
}

View 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());
}));

View 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());
}));

View 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());
}));

View 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());
}));

View 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());
}

View 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.');

View 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());