Skip to content

Name the private addresses an agent may live at - #213

Merged
davidmckayv merged 1 commit into
mainfrom
feat/name-the-private-agent-hosts-you-allow
Aug 24, 2026
Merged

Name the private addresses an agent may live at#213
davidmckayv merged 1 commit into
mainfrom
feat/name-the-private-agent-hosts-you-allow

Conversation

@davidmckayv

Copy link
Copy Markdown
Contributor

Closes the gap #198 left behind.

Why

#198 was right to refuse AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS in production: .env.example shipped it on, the quick start says copy that file and deploy, and the image bakes NODE_ENV=production — so a by-the-book deployment ran with the private-address floor down.

But it took something real with it. Bring your own agent is a headline capability, a company's own agent legitimately lives at an internal address, and after #198 the only way to reach one in the shipped image was to lift the floor for everything. Telling people to set that flag is precisely the advice that made it dangerous in the first place.

What

AGENT_ENDPOINT_ALLOWED_HOSTS — a comma-separated list of hosts, each optionally with a port:

AGENT_ENDPOINT_ALLOWED_HOSTS=agents.internal,10.0.0.42:9000

agents.internal covers any port on that host. 10.0.0.42:9000 pins that one. A deployment sets this and leaves the floor exactly where #198 put it.

Four ways this could have gone wrong, and what stops each

It could have widened browsing. It does not — agent endpoints only. A page can steer a Bot somewhere it should not go; an operator naming an address they themselves run is a different act, and the two do not deserve the same permission.

A pattern could widen by accident. There are no patterns. Matching is exact on the host as written. A list containing a *, or written as URLs, is refused at startup with the offending entry named, rather than silently never matching and being discovered at the first registration.

Cloud metadata could be named back in. It cannot. Whether a refusal may be overruled is decided by re-running the check with the floor down and seeing whether it passes — so "refused for being private" is separated from "refused for being the metadata address or not a web address" structurally, rather than by pattern-matching the refusal text, which would break the first time somebody rephrased it.

A redirect could be the way around registration. It is not. The list is carried to every hop, so a named address is reachable wherever it appears and an unnamed one is refused wherever it appears. checkAgentEndpoint is the single chokepoint for registration, the Test button and every redirect hop, so there is one place to get this right.

Unset means none, which is what every deployment has today.

Proof

Driven in the browser, in production postureNODE_ENV=production, AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS=false, one host named. Two addresses on the same LAN host, differing only by port:

Endpoint tested Result
http://10.0.0.75:4900/ag-ui (not named) "That address is inside this deployment's own network, so an agent may not live there."
http://10.0.0.75:4899/ag-ui (named) "That address answered, but not with AG-UI events (it sent text/plain)."

The second one was dialled. The only complaint is about what came back, which is the point: the floor let it through because the operator named it, and the stub I pointed it at is not an AG-UI agent.

Tests: 19 new. The allowlist cases, the port-pinning both ways, the config parser including both refusals, and three redirect cases — a hop to a named address is followed, a hop to an unnamed one is refused, and a hop to the metadata address is refused even when someone has explicitly named it.

Full suite 1387 pass, 0 fail. Typecheck, lint and format clean.

Note

AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS is unchanged and still refused in production. This does not reopen it; it removes the reason anybody had to reach for it.

Refusing AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS in production closed a hole
and took something with it. Bring your own agent is a headline capability,
a company's own agent legitimately lives at an internal address, and the
only way to reach one was to lift the floor for everything. Telling people
to set that flag is the advice that made it dangerous.

AGENT_ENDPOINT_ALLOWED_HOSTS names addresses instead. A host, optionally
with a port: agents.internal covers any port on that host, 10.0.0.42:9000
pins that one.

Narrow on purpose, and each of these is a way it could have gone wrong.

Agent endpoints only, so browsing is not widened. A page can steer a Bot
somewhere; an operator naming an address they run is a different act from
a Bot following a link to it.

Exact matching, no wildcards and no suffixes. A pattern that widens by
accident is the usual way a host check fails. A list written as URLs or
with a star is refused at startup with the entry named, rather than
quietly never matching.

The never-allowed addresses stay never-allowed. Whether a refusal may be
overruled is decided by re-running the check with the floor down, not by
reading the refusal text: cloud metadata is refused before the private
rule is reached, so naming it changes nothing.

Every hop, not only the first. A named address is reachable wherever it
appears and an unnamed one is refused wherever it appears, so a redirect
cannot arrive somewhere registration would have declined.

Unset means none, which is what every deployment has today.
@davidmckayv
davidmckayv merged commit 1524f57 into main Aug 24, 2026
8 checks passed
@davidmckayv
davidmckayv deleted the feat/name-the-private-agent-hosts-you-allow branch August 24, 2026 02:45
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