Access control / Authorization bypass

HIGH
nodejs/node
Commit: 59c86b1d9377
Affected: < 25.9.0
2026-04-05 10:15 UTC

Description

Summary of the fix: - The commit adds a permission check for Net scope in PipeWrap::Bind and PipeWrap::Listen. Specifically, it invokes THROW_IF_INSUFFICIENT_PERMISSIONS with permission scope Net (and, for Bind, uses the socket/path name as context) before binding or listening on sockets. This enforces access control for net-related operations (including UNIX domain sockets) and prevents unprivileged code from binding/listening to sockets without the Net permission. What was changed: - In src/pipe_wrap.cc, Bind now creates the socket name using the Environment and checks NET permission before calling uv_pipe_bind2. - Listen now checks NET permission before calling uv_listen. - Tests were added to ensure that unprivileged attempts to bind/listen to UNIX domain sockets throw ERR_ACCESS_DENIED with permission Net. Reason this constitutes a real vulnerability fix: - Prior to this patch, non-privileged code could potentially bind/listen on UNIX domain sockets or network listeners without a permission gate, enabling an authorization bypass around socket creation and binding. By introducing a Net permission check, Node.js now enforces explicit permission to perform net-related operations, aligning with a more robust authorization model and preventing unauthorized service binding or unauthorized listening on sockets as described in the CVE. Affected behavior prior to patch (assumed): - Unprivileged code could call net.createServer().listen('/path/to/socket') or tls.createServer().listen('/path/to/socket') and bind to a UNIX socket without Net permission checks. Security impact: - Type: Access control / Authorization bypass for net-related operations (UNIX domain sockets and network listeners). - Severity: HIGH (because it gates the ability to bind/listen to sockets by permissions, preventing potential privilege escalation or resource binding by unprivileged actors). - Affected versions: versions prior to 25.9.0 (i.e., < 25.9.0). Notes: - The commit references a CVE-2026-21711 and includes tests asserting ERR_ACCESS_DENIED with permission Net for unprivileged attempts. - The changes are not just a dependency bump or a minor refactor; they introduce a functional security gate and corresponding tests.

Proof of Concept

Proof of Concept (PoC) This PoC demonstrates the vulnerability path before the fix (attempting to bind/listen on a UNIX socket as an unprivileged user). On a vulnerable Node build, this would succeed; after the fix, it should fail with ERR_ACCESS_DENIED due to missing Net permission. PoC 1: net Unix socket listen (unprivileged environment) // poc-net.js const net = require('net'); const fs = require('fs'); const socketPath = '/tmp/poc-server.sock'; try { if (fs.existsSync(socketPath)) fs.unlinkSync(socketPath); } catch (e) { // ignore } try { const s = net.createServer(() => {}); s.listen(socketPath, () => { console.log('LISTENING on', socketPath); }); } catch (err) { console.error('Error:', err.code, 'permission:', err.permission); process.exit(1); } # How to run (pre-fix vs post-fix) # 1) Run as a non-root user in an environment where Net permission gating is not yet enforced (pre-fix): # node poc-net.js # Expected on vulnerable builds: "LISTENING on /tmp/poc-server.sock" and ability to accept connections. # 2) Run in a fixed Node.js build (post-fix): # node poc-net.js # Expected: throws an error (ERR_ACCESS_DENIED) indicating missing Net permission. PoC 2: TLS UNIX socket listen (unprivileged environment) // poc-tls-net.js const tls = require('tls'); try { const s = tls.createServer({ /* key/cert omitted for brevity in PoC */ }).listen('/tmp/poc-tls.sock'); console.log('LISTENING on /tmp/poc-tls.sock'); } catch (err) { console.error('Error:', err.code, 'permission:', err.permission); process.exit(1); } # Run: # node poc-tls-net.js # Expect similar ERR_ACCESS_DENIED behavior on patched builds.

Commit Details

Author: RafaelGSS

Date: 2026-02-18 16:37 UTC

Message:

permission: include permission check to pipe_wrap.cc PR-URL: https://github.com/nodejs-private/node-private/pull/820 Refs: https://hackerone.com/reports/3559715 Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com> CVE-ID: CVE-2026-21711

Triage Assessment

Vulnerability Type: Access control / Authorization bypass

Confidence: HIGH

Reasoning:

The patch adds a permission check (Net scope) before binding and listening on sockets in PipeWrap, and accompanying tests ensure that unprivileged attempts are blocked with ERR_ACCESS_DENIED. This directly enforces access control for net-related operations, preventing unauthorized use of UNIX domain sockets or network listeners. The commit references a CVE ID, indicating a tracked security vulnerability.

Verification Assessment

Vulnerability Type: Access control / Authorization bypass

Confidence: HIGH

Affected Versions: < 25.9.0

Code Diff

diff --git a/src/pipe_wrap.cc b/src/pipe_wrap.cc index 770f0847aec59f..5100b0fed17455 100644 --- a/src/pipe_wrap.cc +++ b/src/pipe_wrap.cc @@ -162,7 +162,10 @@ PipeWrap::PipeWrap(Environment* env, void PipeWrap::Bind(const FunctionCallbackInfo<Value>& args) { PipeWrap* wrap; ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); - node::Utf8Value name(args.GetIsolate(), args[0]); + Environment* env = wrap->env(); + node::Utf8Value name(env->isolate(), args[0]); + THROW_IF_INSUFFICIENT_PERMISSIONS( + env, permission::PermissionScope::kNet, name.ToStringView()); int err = uv_pipe_bind2(&wrap->handle_, *name, name.length(), UV_PIPE_NO_TRUNCATE); args.GetReturnValue().Set(err); @@ -193,6 +196,7 @@ void PipeWrap::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); args.GetReturnValue().Set(err); diff --git a/test/parallel/test-permission-net-uds.js b/test/parallel/test-permission-net-uds.js index 7024c9ff6d3b16..436757e9d8b3cb 100644 --- a/test/parallel/test-permission-net-uds.js +++ b/test/parallel/test-permission-net-uds.js @@ -29,3 +29,21 @@ const tls = require('tls'); client.on('connect', common.mustNotCall('TCP connection should be blocked')); } + +{ + assert.throws(() => { + net.createServer().listen('/tmp/perm-server.sock'); + }, { + code: 'ERR_ACCESS_DENIED', + permission: 'Net', + }); +} + +{ + assert.throws(() => { + tls.createServer().listen('/tmp/perm-tls-server.sock'); + }, { + code: 'ERR_ACCESS_DENIED', + permission: 'Net', + }); +}
← Back to Alerts View on GitHub →