Skip to content

fix(ext/node): check permissions when binding node:net unix sockets - #35564

Merged
bartlomieju merged 2 commits into
mainfrom
fix/node-pipe-wrap-permissions
Jun 29, 2026
Merged

bartlomieju merged 2 commits into
mainfrom
fix/node-pipe-wrap-permissions

Conversation

@bartlomieju

Copy link
Copy Markdown
Member

The node-compat PipeWrap handle is the native backing for node:net
unix-domain sockets, and is also reachable directly from user code via
process.binding("pipe_wrap").Pipe. Several of its operations mutated the
filesystem without going through Deno's permission model.

Binding a unix-domain socket creates a socket inode on disk via
socket(2) + bind(2), yet bind() performed no permission check. Worse,
uv_pipe_bind recorded the bound path on the handle before attempting the
bind and did not clear it on failure, so a failed bind against an existing
file still armed the two operations that act on the recorded path: fchmod()
(which calls chmod(2)) and the unlink-on-close in close_pipe (which calls
remove_file). The net effect was that code granted no --allow-* flags could
create a socket inode at an arbitrary writable path, change the mode of an
existing owned file, or delete an arbitrary unlinkable file.

This moves the filesystem permission check to bind(), where the inode is
actually created, mirroring connect() and Deno.listen({ transport: "unix" }).
A denied bind throws NotCapable and never records the path, so the fchmod
and unlink-on-close paths can no longer reach an arbitrary file. As defense in
depth (and to match libuv, which stores the path on the handle only on success),
uv_pipe_bind now records the bound path only after bind(2) succeeds.

The existing permission check in listen() is left in place. A new spec test
under tests/specs/permission/node_pipe_wrap_no_perms runs with no permissions
and asserts that bind() is denied and that no path is left armed for fchmod.
Legitimate unix-socket servers created with --allow-read --allow-write
continue to work, and the existing pipe_open_fd and http_address_override
unix-socket suites pass unchanged.

The node-compat PipeWrap handle (the native backing for node:net
unix-domain sockets, also reachable via process.binding("pipe_wrap"))
bound, chmod'd, and unlinked filesystem paths without consulting Deno's
permission model. Binding a unix socket creates a socket inode on disk,
so bind() now requires read+write permission for the path, matching
connect() and Deno.listen({ transport: "unix" }). The permission check
lives at bind() rather than only at listen() because bind() is where the
filesystem mutation happens; a denied bind never records the path, so the
fchmod and unlink-on-close paths that operate on the bound path can no
longer target an arbitrary file.

Also record the bound path only after a successful bind(2), matching
libuv, so a failed bind can no longer arm fchmod/unlink against an
existing file.
@bartlomieju bartlomieju changed the title fix(node): check permissions when binding node:net unix sockets fix(ext/node): check permissions when binding node:net unix sockets Jun 29, 2026
@bartlomieju
bartlomieju merged commit 4216f2b into main Jun 29, 2026
136 checks passed
@bartlomieju
bartlomieju deleted the fix/node-pipe-wrap-permissions branch June 29, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant