René's URL Explorer Experiment


Title: A new net permission model: allow scoping in --allow-net · Issue #67115 · nodejs/node · GitHub

Open Graph Title: A new net permission model: allow scoping in --allow-net · Issue #67115 · nodejs/node

X Title: A new net permission model: allow scoping in --allow-net · Issue #67115 · nodejs/node

Description: Summary Today --allow-net is all-or-nothing. This proposal lets it take a list: --allow-net=api.example.com:443,10.0.0.0/8,*.example.com. The list accepts host names, IPv4, bracketed IPv6, CIDR and *. wildcards, each with an optional por...

Open Graph Description: Summary Today --allow-net is all-or-nothing. This proposal lets it take a list: --allow-net=api.example.com:443,10.0.0.0/8,*.example.com. The list accepts host names, IPv4, bracketed IPv6, CIDR and...

X Description: Summary Today --allow-net is all-or-nothing. This proposal lets it take a list: --allow-net=api.example.com:443,10.0.0.0/8,*.example.com. The list accepts host names, IPv4, bracketed IPv6, CIDR and...

Opengraph URL: https://github.com/nodejs/node/issues/67115

X: @github

direct link

Domain: github.com


Hey, it has json ld scripts:
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"A new net permission model: allow scoping in --allow-net","articleBody":"\n## Summary\n\nToday `--allow-net` is all-or-nothing. This proposal lets it take a list: `--allow-net=api.example.com:443,10.0.0.0/8,*.example.com`. The list accepts host names, IPv4, bracketed IPv6, CIDR and `*.` wildcards, each with an optional port.\n\nBare `--allow-net` keeps its current meaning. Checks run in the native layer (TCP, UDP, pipes, DNS, QUIC, DTLS), and a lookup token ties an allowed name to the IPs it resolved to. `process.permission.has('net', 'host:port')` exposes the same check to JS.\n\nThe first iteration has no `--deny-net` and no proxy awareness.\n\n## Motivation and prior art\n\n* Today `net` is all-or-nothing: `--allow-net` grants every host, every port and every socket.\n* Users want least privilege, for example allowing only `api.example.com:443` and their database.\n* Prior art: Deno's `--allow-net=host:port` and `--deny-net`.\n* [nodejs/node#66117](https://github.com/nodejs/node/pull/66117) closed the `Socket(fd)` bypass; only stdio and the IPC channel are exempt.\n\n## Goals\n\n* Limit network access to specific hosts, IPs, CIDR ranges and ports.\n* Bare `--allow-net`, and no `--allow-net` at all, behave exactly as today.\n* Enforce in the native layer for every transport (TCP, UDP, pipes, DNS, QUIC, DTLS), so it can't be bypassed from JS through `_handle`.\n* No cost when the permission model is off; bare `--allow-net` stays a single boolean check.\n* `process.permission.has('net', 'host:port')` gives the same answer as the native check.\n* The config file accepts the list (`\"allow-net\": [\"api.example.com:443\"]`); `true` keeps working.\n* Audit mode reports the denied `host:port`.\n\n## Non-goals (first iteration)\n\n* No `--deny-net`.\n* No proxy awareness: with `HTTP_PROXY` set, the allowed host is the proxy.\n* No change to bare `--allow-net`. Sockets created from an fd ([nodejs/node#66117](https://github.com/nodejs/node/pull/66117)) are allowed only with bare `--allow-net`, since there is no host to match.\n\n## Compatibility and release\n\n* **semver-minor:** an existing flag gains a value form. Bare `--allow-net`, no `--allow-net`, and `\"allow-net\": true` all behave as today.\n* Stability 1.1 would allow semantic changes in a minor, but none are needed.\n* Lands on `main` (v27). v24 and older don't have `--allow-net` (added in v25.0.0).\n* Backport to v26: needs input from the Release WG, since v26 is expected to stay stable.\n\n## Resource model\n\n* `*.example.com` matches names at any depth (RFC 4592), but not `example.com` itself.\n* IPv4-mapped IPv6 addresses are normalized: `::ffff:192.0.2.1` is `192.0.2.1`.\n* A trailing dot is ignored: `example.com.` is `example.com`.\n* Names are case-insensitive (RFC 1035, RFC 4343).\n\n## Surfaces\n\n* **CLI:** `--allow-net=\u003crule>[,\u003crule>...]`. A rule is `host[:port]`, `ip[:port]`, `[ipv6][:port]`, `cidr`, `*.domain[:port]` or `unix:\u003cpath>` (see Q4).\n* **Config file:** `\"permission\": { \"allow-net\": [\"api.example.com:443\"] }`, or `true`.\n* **`process.permission.has('net', 'host:port')`:** same answer as the native check.\n* **`permission.drop('net', rule)`:** only drops a rule exactly as it was granted. `drop('net')` drops everything.\n* **Audit:** the published message names the `host:port`.\n\n## Enforcement\n\n**UDP:**\n\n* Unconnected `send` checks the destination on every packet, since each packet can go to a different host.\n* A connected socket checks once at `connect`; its `send` calls are not checked again.\n\n**DNS:** `dns.resolve*` checks the queried name but creates no token. A connect to an IP is a connect to that IP, not to a host, so it needs an IP entry. Code that also wants a name check can call `process.permission.has('net', 'api.example.com')` itself.\n\n**Custom lookup:** with `--allow-net=api.example.com`, `fetch('https://api.example.com')` works, because Node's own lookup resolves the name. If the code sets its own `lookup` function, for example `new Agent({ connect: { lookup } })` from undici, that function returns the IP instead of Node. Node can't tell whether that IP really belongs to `api.example.com`, so it checks the IP: the request is denied unless the IP is allowed too.\n\n## Security\n\n* **Threat model unchanged:** a seat belt for trusted code, not a sandbox.\n* **What lists add:** trusted code can't accidentally reach a host outside the list.\n* **Known limits:**\n  * An allowed name is trusted wherever its DNS answer points, including internal IPs (DNS rebinding).\n  * An allowed name's IP can serve other sites through TLS SNI or the Host header (domain fronting).\n  * Proxies and fd sockets are covered under Non-goals.\n* **Hardening:** checks are native, so `_handle` can't bypass them, and the token can't be forged.\n\n## Performance\n\n**Principle:** permission mode accepts the cost of checking, and every check is made as cheap as possible. The cost is only paid in list mode; bare `--allow-net` stays a boolean.\n\n**Matchers (first iteration):**\n\n* Exact IPs: a hash set.\n* CIDR: a prefix table.\n* `*.` names: a suffix tree.\n\nThese can change later for performance.\n\n**Generation counter:** each rule add or drop increments a counter per Environment. A token stores the counter value and the rule its host matched. At connect, if the values are equal, only `ip ∈ token` and the port are checked; otherwise a full check runs. The counter is compared before the cached rule is used, and it isn't exposed to JS.\n\n## Rollout and testing\n\n**PR order** (every release stays safe):\n\n1. tcp, udp, pipe and cares checks pass host, port and direction, one PR per transport. The only visible change is more detail in audit messages.\n2. Lookup token, and `connect(..., token)`.\n3. Matchers and the generation counter.\n4. **Switch:** parser, config file, `has()`, `drop`, audit, docs. In list mode, a check with no host denies.\n5. QUIC and DTLS, as separate PRs. Until they land, both deny in list mode.\n\n**Tests:** extend the per-transport files (`test/parallel/test-permission-net-*`). Add bypass tests (`_handle`, custom `lookup`, `::ffff:`, trailing dot, case), `has()` vs native check, a `drop` invalidating tokens, Happy Eyeballs, undici IP + `servername`, config file, audit.\n\n**Benchmark:** `benchmark/net` with permission off, bare, and a list, plus UDP `send`.\n\n## Notes\n\n**Workers:** each worker has its own permission state, built from its own flags. A `drop` only affects the thread that calls it: running workers keep their rules, and new workers start again from the CLI list.\n\n**Child processes:** spawning needs `--allow-child-process`. A Node.js child gets the parent's permission flags through `NODE_OPTIONS`, so the same `--allow-net=\u003clist>` applies, and runtime drops don't carry over. Non-Node children (for example `curl`) aren't restricted at all. The list has to survive `NODE_OPTIONS` quoting, e.g. `unix:` paths that contain spaces.\n\n## Open questions\n\n### Q1: Name vs IP\n\n**Importance:** Critical\n\n**Question:** Hostnames can only be enforced at DNS lookup time (`cares_wrap`); a direct `connect('192.0.2.1')` needs an IP entry. Deno checks names at lookup and links the resolved IPs to connect with a per-socket token; resolution is also used for deny checks. Is that acceptable for Node, or should connect re-resolve and compare?\n\n**Problem:** connect only sees the IP, so a name rule cannot match it. And connect can be reached without a lookup.\n\n**Recommendation:** a native token `{host, ips}`, created by the lookup and checked by connect.\n\n```text\n example.com ▶ cares_wrap ▶ tcp_wrap ──▶ ip in token? ── yes ─▶ check host\n               check host   (ip+token)                └─ no ──▶ check ip\n               make token\n\n 192.0.2.1 ───────────────▶ tcp_wrap ──▶ no token ───────────▶ check ip\n (IP literal, custom lookup, _handle)\n```\n\n* **Binding change:** `tcp_wrap.connect(req, ip, port)` → `connect(req, ip, port[, token])`, for both `connect` and `connect6`. The token is optional, so existing calls don't break, but both TCP paths in `net.js` must pass it: `internalConnect` and Happy Eyeballs (`internalConnectMultiple`). Otherwise name rules never match.\n* **Port:** unlike Deno, not in the token. The lookup never sees a port; connect always does. So the lookup matches the host, and connect checks `host:port`.\n\n**Alternatives considered:**\n\n| Option                         | Result | Why                                               |\n| ------------------------------ | ------ | ------------------------------------------------- |\n| Check the name only at lookup  | ✗      | IP literal, custom `lookup` and `_handle` skip it |\n| Re-resolve at connect          | ✗      | second answer can differ; extra latency           |\n| IP set per Environment         | ✗      | an allowed name opens its IPs to every socket     |\n| JS-only check                  | ✗      | bypassed through `_handle`                        |\n| Native lookup+connect          | ✗      | breaks `lookup` and Happy Eyeballs                |\n| **Native token `{host, ips}`** | ✓      | per socket, cannot be forged, only in list mode   |\n\n### Q2: Connect vs listen\n\n**Importance:** Critical\n\n**Question:** should a host list also control `listen()`?\n\n**Problem:** a listen resource is a local address, not a remote host.\n\n**Options:**\n\n| Option                  | Example                                                | Pros                                                | Cons                                                       |\n| ----------------------- | ------------------------------------------------------ | --------------------------------------------------- | ---------------------------------------------------------- |\n| A. One list             | `--allow-net=api.example.com,0.0.0.0:3000`             | one grammar, no new flag, listen can be restricted  | no connect-only rule; servers must list their bind address |\n| B. Outbound only        | `--allow-net=api.example.com`                          | servers keep working, no listen syntax              | listen cannot be restricted                                |\n| C. Separate listen list | `--allow-net=api.example.com --allow-net-listen=:3000` | explicit direction, each side restricted on its own | new flag; two lists to keep in sync                        |\n\nPrior art: Deno uses A.\n\nA and C both need `Bind` to pass `addr:port`; today it passes `\"\"`.\n\n### Q3: Port ranges\n\n**Importance:** Critical\n\n**Question:** should `example.com:8000-8100` be supported? Leaning yes.\n\n### Q4: Unix sockets and named pipes\n\n**Importance:** Critical\n\n**Question:** which permission controls a unix socket or named pipe: net, fs, or both?\n\n**Problem:** a unix socket is both a file and a network connection. Today only `kNet` is checked, and bare `--allow-net` opens every socket path.\n\n**Options:**\n\n| Option                    | Example                               | Pros                                    | Cons                        |\n| ------------------------- | ------------------------------------- | --------------------------------------- | --------------------------- |\n| A. net only               | `--allow-net=unix:/run/app.sock`      | one flag                                | ignores fs rules            |\n| B. fs only                | `--allow-fs-read/write=/run/app.sock` | no new syntax                           | network uncontrolled        |\n| C1. both, path in each    | fs + `--allow-net=unix:/run/app.sock` | broad fs rules don't open other sockets | path written twice          |\n| C2. both, path only in fs | fs + `--allow-net=unix`               | shorter                                 | broad fs opens every socket |\n\n**Recommendation:** C1.\n\nAbstract sockets have no file, so only the net rule applies.\n\n### Q5: IPv6 zone\n\n**Importance:** High\n\n**Question:** does `[fe80::1]` match `fe80::1%eth0`?\n\n**Problem:** a link-local address is only unique per interface: `fe80::1%eth0` and `fe80::1%wlan0` can be two different machines. A rule without a zone either matches both (too broad) or neither (useless).\n\n**Options:**\n\n* A. A rule without a zone matches every interface.\n* B. The rule must name the zone: `[fe80::1%eth0]`.\n\n### Q6: CIDR with a port\n\n**Importance:** Medium\n\n**Question:** should `10.0.0.0/8:443` be allowed?\n\n**Problem:** \"any host in my network, only on port 443\" is a real need, but CIDR notation (RFC 4632) has no port form, so this syntax would be our own.\n\n**Options:**\n\n* A. Allow `cidr:port`, for example `10.0.0.0/8:443` and `[2001:db8::/32]:443`.\n* B. A CIDR rule always means all ports.\n\n### Q7: Windows pipe prefix\n\n**Importance:** Medium\n\n**Question:** which prefix names a Windows named pipe in a net rule?\n\n**Problem:** a Windows pipe (`\\\\.\\pipe\\app`) isn't a unix socket, but Node handles both through the same `PipeWrap` (libuv `uv_pipe_t`).\n\n**Options:**\n\n* A. `unix:\\\\.\\pipe\\app`, as in Deno: one prefix, but a misleading name.\n* B. `pipe:\\\\.\\pipe\\app`: a clear name, but two prefixes.\n\n**Recommendation:** A.\n\n### Q8: IDN\n\n**Importance:** Low\n\n**Question:** should `bücher.example` work as a rule?\n\n**Problem:** DNS carries only ASCII, so the name travels as `xn--bcher-kva.example`. If the rule and the lookup are spelled differently, the rule never matches. Converting the name also lets look-alike names (homographs) through.\n\n**Options:**\n\n* A. Convert: `bücher.example` becomes `xn--bcher-kva.example` before matching. Friendly, but look-alike names get through.\n* B. Reject: a non-ASCII rule is an error, and users write the `xn--` form. Simple and safe.\n\n### Q9: Counter width\n\n**Importance:** Low\n\n**Question:** should the generation counter be `uint32` or `uint64`?\n\n**Problem:** if the counter wraps, old tokens become valid again. At a million drops per second, `uint32` wraps in 71.6 minutes; `uint64` takes 585,000 years.\n\n## Future work\n\n* `--deny-net`, with deny checks after resolution (catches a name that resolves to a denied IP).\n* Proxy awareness: check the target host behind `HTTP_PROXY`, not just the proxy.\n* Faster matchers, if benchmarks show a need.\n","author":{"url":"https://github.com/GroophyLifefor","@type":"Person","name":"GroophyLifefor"},"datePublished":"2026-10-29T16:55:55Z","url":"https://github.com/nodejs/node/issues/67115"}

react-profiling0
react-import-mapreact,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@d5750093facf
route-pattern/_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format)
route-controllervoltron_issues_fragments
route-actionissue_layout
fetch-noncev2:599428b7-9694-63c4-d82f-ca80b7ebd896
current-catalog-service-hash81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114
request-id92F2:4CC9:397E61:4E0891:6AED1D87
html-safe-nonceec5626fed6910f5e55926e6fb5c2cbda4aaa2d54f497ec526c0f6d25ec9a864f
visitor-payloadeyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5MkYyOjRDQzk6Mzk3RTYxOjRFMDg5MTo2QUVEMUQ4NyIsInZpc2l0b3JfaWQiOiIyMTkwMTQyMzY0NDg5NDI0MjYzIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0=
visitor-hmac39fdaf9afca215318866e2f6f9f9f826883219022dcdf11b495ba9b5494af68b
hovercard-subject-tagissue:6104535560
github-keyboard-shortcutsrepository,issues,copilot
google-site-verificationApib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I
octolytics-urlhttps://collector.github.com/github/collect
analytics-location///voltron/issues_fragments/issue_layout
fb:app_id1401488693436528
apple-itunes-appapp-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/nodejs/node/67115/issue_layout
twitter:imagehttps://opengraph.githubassets.com/e355b10deccbced84ae6c20789dc326295b96ec7cc8aff2a22e3e7dfa709d204/nodejs/node/issues/67115
twitter:cardsummary_large_image
og:imagehttps://opengraph.githubassets.com/e355b10deccbced84ae6c20789dc326295b96ec7cc8aff2a22e3e7dfa709d204/nodejs/node/issues/67115
og:image:altSummary Today --allow-net is all-or-nothing. This proposal lets it take a list: --allow-net=api.example.com:443,10.0.0.0/8,*.example.com. The list accepts host names, IPv4, bracketed IPv6, CIDR and...
og:image:width1200
og:image:height600
og:site_nameGitHub
og:typeobject
og:author:usernameGroophyLifefor
hostnamegithub.com
expected-hostnamegithub.com
Nonee3e0f7f63e50c6fe879198a93a0a023a648ecc0a5e9bc3b5e033ac57172237e6
turbo-cache-controlno-preview
go-importgithub.com/nodejs/node git https://github.com/nodejs/node.git
octolytics-dimension-user_id9950313
octolytics-dimension-user_loginnodejs
octolytics-dimension-repository_id27193779
octolytics-dimension-repository_nwonodejs/node
octolytics-dimension-repository_publictrue
octolytics-dimension-repository_is_forkfalse
octolytics-dimension-repository_network_root_id27193779
octolytics-dimension-repository_network_root_nwonodejs/node
turbo-body-classeslogged-out env-production page-responsive
disable-turbofalse
browser-stats-urlhttps://api.github.com/_private/browser/stats
browser-errors-urlhttps://api.github.com/_private/browser/errors
release707a24b346ac8a82878b9c084efaa38a997660cb
ui-targetfull
theme-color#1e2327
color-schemelight dark
react-app-nameissues

Links:

Skip to contenthttps://github.com/nodejs/node/issues/67115#start-of-content
https://github.com/
Sign inhttps://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fnodejs%2Fnode%2Fissues%2F67115
GitHubRepository hosting, code review, and collaborationhttps://github.com/platform
CopilotAI coding agent for tasks, projects, and automated workhttps://github.com/features/copilot
ActionsCI/CD and automationhttps://github.com/features/actions
Code QualityQuality analysis, fixes, and enforcementhttps://github.com/features/code-quality
CodespacesCloud development environmentshttps://github.com/features/codespaces
Code SecurityVulnerability detection and remediationhttps://github.com/security/advanced-security/code-security
Secret ProtectionSecret scanning and leak preventionhttps://github.com/security/advanced-security/secret-protection
All Featureshttps://github.com/features
Documentationhttps://docs.github.com/en
Traininghttps://learn.github.com/skills
Certificationshttps://learn.github.com/credentials
Build with Copilothttps://github.com/features/ai
Customize Copilothttps://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/customize-copilot-overview
Integrationshttps://github.com/marketplace
Trending reposhttps://github.com/trending
GitHub Sponsorshttps://github.com/open-source/sponsors
Developer articleshttps://github.com/resources/articles
ChangelogYour source for the latest features, improvements, and fixes across GitHub products, all in one place.https://github.blog/changelog/
Enterpriseshttps://github.com/enterprise
Teamhttps://github.com/team
Startupshttps://github.com/enterprise/startups
Nonprofitshttps://github.com/solutions/industry/nonprofits
Healthcarehttps://github.com/solutions/industry/healthcare
Financial serviceshttps://github.com/solutions/industry/financial-services
Manufacturinghttps://github.com/solutions/industry/manufacturing
Governmenthttps://github.com/solutions/industry/government
See all industrieshttps://github.com/solutions/industry
Why GitHubhttps://github.com/why-github
Customer Storieshttps://github.com/customer-stories
Business insightshttps://github.com/solutions/executive-insights
Trust Centerhttps://github.com/trust-center
GitHub Advanced SecurityEnterprise-grade code securityhttps://github.com/security/advanced-security
Copilot for businessEnterprise-grade AI featureshttps://github.com/features/copilot/copilot-business
Premium SupportEnterprise-grade 24/7 supporthttps://github.com/enterprise/premium-support
Documentationhttps://docs.github.com/en
Resource centerhttps://github.com/resources
Bloghttps://github.blog/
Events & webinarshttps://github.com/resources/events
Community Forumhttps://github.com/orgs/community/discussions
Customer Storieshttps://github.com/customer-stories
Integrationshttps://github.com/marketplace
Customer supporthttps://help.github.com/
Traininghttps://learn.github.com/skills
GitHub Statushttps://www.githubstatus.com/
Abouthttps://github.com/about
Partnershttps://github.com/partners
Changeloghttps://github.blog/changelog/
GitHub UniverseThe event for developers, by developers — talks, workshops, and what’s next for GitHub.https://githubuniverse.com/
Pricinghttps://github.com/pricing
Sign inhttps://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fnodejs%2Fnode%2Fissues%2F67115
Sign uphttps://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fvoltron%2Fissues_fragments%2Fissue_layout&source=header-repo&source_repo=nodejs%2Fnode
Reloadhttps://github.com/nodejs/node/issues/67115
Reloadhttps://github.com/nodejs/node/issues/67115
Reloadhttps://github.com/nodejs/node/issues/67115
nodejshttps://github.com/nodejs
nodehttps://github.com/nodejs/node
Codehttps://github.com/nodejs/node
Issues655 (655)https://github.com/nodejs/node/issues
Pull requests666 (666)https://github.com/nodejs/node/pulls
Actionshttps://github.com/nodejs/node/actions
Projectshttps://github.com/nodejs/node/projects
Security and qualityhttps://github.com/nodejs/node/security
Insightshttps://github.com/nodejs/node/pulse
https://github.com/nodejs/node
New issuehttps://github.com/login?return_to=https://github.com/nodejs/node/issues/67115
New issuehttps://github.com/login?return_to=https://github.com/nodejs/node/issues/67115
A new net permission model: allow scoping in --allow-nethttps://github.com/nodejs/node/issues/67115#top
https://github.com/GroophyLifefor
GroophyLifeforhttps://github.com/GroophyLifefor
on Oct 29, 2026https://github.com/nodejs/node/issues/67115#issue-6104535560
nodejs/node#66117https://github.com/nodejs/node/pull/66117
nodejs/node#66117https://github.com/nodejs/node/pull/66117
https://github.com/UlisesGascon
UlisesGasconhttps://github.com/UlisesGascon
on Oct 30, 2026https://github.com/nodejs/node/issues/67115#issuecomment-6518444495
Sign up for freehttps://github.com/signup?return_to=https://github.com/nodejs/node/issues/67115
Sign in to commenthttps://github.com/login?return_to=https://github.com/nodejs/node/issues/67115
https://github.com
Termshttps://docs.github.com/site-policy/github-terms/github-terms-of-service
Privacyhttps://docs.github.com/site-policy/privacy-policies/github-privacy-statement
Securityhttps://github.com/security
Statushttps://www.githubstatus.com/
Communityhttps://github.community/
Docshttps://docs.github.com/
Contacthttps://support.github.com?tags=dotcom-footer

Viewport: width=device-width


URLs of crawlers that visited me.