How do you build an egress allowlist for AI agents?
Write the hosts that the agent may contact into a policy that the agent can't edit, compare each host that a tool call would reach against that list before the call runs, and deny the call when a host isn't on it. That list is an egress allowlist: egress is outbound traffic, and an allowlist permits only what it names.
Network egress controls such as firewalls and proxies do a similar job for a whole machine or network. An allowlist on the tool call is narrower and has more context: it knows which agent made the call, which tool it used, and which session it belongs to. Most teams need both, because an agent on a developer laptop runs on a network that also serves the developer's browser.
Arcjet enforces the allowlist on the tool call, before the tool runs. On coding agents such as Claude Code, GitHub Copilot, Cursor, and OpenAI Codex, one policy works from the hooks that the agents already fire, with no code change. In custom agents, Arcjet runs the same check inside the tool before it contacts anything. The same policy can also score each host with Arcjet threat intelligence.
Why does an agent need an egress allowlist?
Agents move data by design. They read files, summarize them, and send results onward through fetch tools, API clients, shell commands, and Model Context Protocol (MCP) servers. When an agent can't reach the destination that it intended, it can pick another one on its own. For example, an agent that can't authenticate to a company file store might upload the file to a public, unauthenticated file-sharing site instead, so that it can finish the task. Nothing in that sequence looks like an attack, and the destination might have a clean reputation, so threat intelligence doesn't catch it and an allowlist does. For more information about exfiltration through agents in general, see how to prevent data exfiltration through AI agents.
How is an allowlist different from threat intelligence?
The two controls answer different questions, and you usually want both:
| Control | Question it answers | What it misses |
|---|---|---|
| Domain allowlist | Is this host on the reviewed list? | A reviewed host that later becomes risky |
| Destination threat scoring | Does threat intelligence mark this host high or critical risk? | A host with no threat record, such as a new domain or a public upload site |
Threat scoring works without knowing in advance which hosts an agent needs. An allowlist needs that knowledge, but it catches hosts that no threat feed has a record of. Arcjet runs both in one policy, so each covers what the other misses. For more information about scoring, see how to stop an AI agent contacting malicious URLs and domains.
Where does the allowlist belong?
Write the list into the policy. Don't accept it as an input that the agent supplies. If the agent could send the list, it could also add hosts to it, and an agent that follows injected instructions could be told to add one.
In an application, the equivalent rule is that the list comes from your configuration, not from the model. If a tool call's arguments include the allowed hosts, the model chose them.
How do you limit a coding agent's web fetches to approved hosts?
On a coding agent, Arcjet uses coding agent hooks. A hook is a request that the agent sends to an external service before each tool call, and the answer decides whether the tool runs. For more information about that model, see coding agent security. Arcjet fills an input named domains with the hosts of a tool call's URL arguments. The following policy is the coding-agent.egress-allowlist starter in the Arcjet Console, and it denies a web tool call to any host outside the set:
package arcjet.guard
import rego.v1
deny contains "unlisted-domain" if { input.values.tool_kind == "web" some host in input.values.domains not host in {"docs.arcjet.com", "github.com", "raw.githubusercontent.com"}}Replace the set with the hosts your organization has reviewed before the rule goes live.
The rule is scoped to tool_kind == "web", so a curl in a shell tool call goes around it. Arcjet covers the shell path with destination threat analysis, which reads a separate input, destinations, that includes the hosts of absolute URLs inside a shell command. For more information about blocking risky downloads in a shell, see how to stop a coding agent downloading and running malware.
How do you combine an allowlist with threat scoring?
A common requirement is that a reviewed host bypasses the threat check. For example, you trust a host that you run, and you don't want a threat feed's opinion of its IP address to block your developers. Arcjet policies are written in Rego, so you express that exception directly in one policy.
It needs one policy because Arcjet runs every live policy whose Execute on setting is Tool call and applies the most restrictive decision. A live rule enforces, as opposed to a dry-run rule, which records what it would deny without blocking. If you publish coding-agent.egress-allowlist and coding-agent.destination-threat side by side, each has its own deny rule and neither short-circuits the other. A host on the allowlist that scores high or critical is still denied by the threat policy.
Write both checks in the same policy instead. Deny unreviewed hosts on web tools, and deny high or critical risk only for hosts that aren't on the allowlist:
allowed := {"docs.arcjet.com", "github.com", "raw.githubusercontent.com"}
deny contains "unlisted-domain" if { input.values.tool_kind == "web" some host in input.values.domains not host in allowed}
deny contains "malicious-destination" if { some a in input.signals.ip_threat.dest.assessments a.risk_level in {"high", "critical"} not a.host in allowed}The threat rule reads assessments, the per-host list of scored destinations, so it can skip a host on the allowlist and still deny another host in the same call. Declare an Arcjet threat intelligence detector with the ID dest over destinations, plus the inputs tool_kind and domains, and set Execute on to Tool call. The following table shows what the policy decides:
| Situation | Result |
|---|---|
| Web fetch to an unlisted host | Denied (unlisted-domain) |
| Host on the allowlist, high or critical risk | Allowed (threat deny skipped) |
| Unlisted host, high or critical risk | Denied (malicious-destination) |
Shell curl to a high-risk host that isn't on the allowlist | Denied (malicious-destination) |
Publish the two starters separately only when you want both constraints with no exception: every web host must be on the allowlist, and no host can score high or critical risk.
How do you build an egress allowlist into a custom agent?
For custom agents you build with frameworks such as LangChain, the OpenAI Agents SDK, or the Vercel AI SDK, Arcjet runs the check inside the tool before it contacts anything. The following policy from the Arcjet policy examples is for a web fetch tool. The application passes the URL and a trusted allowed_hosts list from its own configuration in the guard() call:
authority := split(trim_prefix(lower(input.values.url), "https://"), "/")[0]
deny contains "insecure-scheme" if { not startswith(lower(input.values.url), "https://")}
deny contains "untrusted-host" if { not authority in input.values.allowed_hosts}The authority is everything between the scheme and the first /, so it includes any port and any user information. That handles a URL such as https://api.example.com@evil.example/data, where the real host is evil.example and a simple prefix check would pass it. List each entry in allowed_hosts exactly as it appears in the URL, including a non-default port.
To add threat scoring, map the hosts that the tool is about to contact into a SERVER string list, and declare an Arcjet threat intelligence detector over it. Keep the bypass in the same policy as the threat rule. Hardcode the allow set in Rego, or pass a trusted list, such as allowed_hosts, from application configuration. Don't take it from the model.
Which controls pair with an egress allowlist?
Arcjet enforces the allowlist on every tool call that it sees. Design the list with the following points in mind, and pair it with the controls that each one names:
- Matching is exact. A host comparison in these policies is exact, so allowing
example.orgdoesn't allowmail.example.org. That is deliberate, because a suffix match lets a lookalike subdomain through. List each subdomain that you need. - Redirects can leave the list. A fetch tool that follows redirects can reach a host outside the allowlist after the policy allowed the call. Disable redirects in the tool, or guard each hop.
- Scope each rule to the path it checks. A web-only rule doesn't cover a shell command, so pair it with destination threat analysis, as the combined policy does. Network connections that a process opens outside a tool call need network egress controls.
- Codex hosted tools skip the hook. OpenAI Codex runs hosted tools such as
WebSearchoutside the local hook path, so no tool-call policy sees them.
Where does Arcjet fit in agent egress control?
With Arcjet, an agent's web tool calls reach only the hosts that you reviewed, and a known-malicious host is denied even when you haven't listed it. The following capabilities apply:
- One policy, decided before the tool runs. The allowlist and destination threat analysis run together, with a bypass for hosts that you trust, written in Rego.
- Every coding agent, no code change. On Claude Code, Copilot, Cursor, and Codex, the same policy runs from each agent's hooks, installed through managed settings or MDM so developers can't remove it without administrator access.
- The same engine in custom agents. The same Rego runs inside your tool, with the allow set taken from your own configuration.
- A record of every decision. Arcjet records each decision in the Arcjet Console and exports decisions to Datadog, Splunk, SentinelOne, Panther, and Amazon S3 (Enterprise plan).
To start, publish the policy in dry run. Arcjet starts every new rule in dry run, so you can read which calls it would deny in the Activity view of the Arcjet Console before you set it live, and changes take effect in real time. For MCP servers, the same pattern applies to server names. For more information, see what a rogue MCP server is and how to detect one.
Learn more: Destination threat detection · Policy examples · Coding agent policies
Frequently asked questions
What is an egress allowlist for an AI agent?
An egress allowlist is a list of hosts that an AI agent is allowed to contact, enforced on each tool call before the call runs. The list is written into a policy that the agent can't edit, and a call to a host outside it is denied.
Should I use an allowlist or threat intelligence for agent egress?
Use both. An allowlist asks whether a host is on the list that you reviewed, and it catches unknown hosts such as public upload sites. Threat intelligence asks whether a host is known to be high risk, and it works without knowing in advance which hosts the agent needs. Arcjet runs both in one policy, decided before the tool call runs.
Why can't I publish separate allowlist and threat policies to let trusted hosts bypass the threat check?
Arcjet runs every live policy on a tool call and applies the most restrictive decision, so the threat policy still denies a host on the allowlist that scores high or critical. Put both rules in one policy, and skip the threat deny for hosts on the allowlist.
Can the agent supply its own allowlist?
No. If the agent could send the list, it could also add hosts to it. Hardcode the list in the policy. In an application, pass it from trusted configuration, never from the model.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Start from the egress allowlist starter in dry run and review which hosts your coding agents reach before you enforce it.