Aller au contenu principal

Managed Agents Domain Restrictions

Avancé
What you'll learn
  • Understand which threats a per-tool allowed/blocked domain list actually stops in Managed Agents — and which it does not
  • Pin web_search and web_fetch to specific hosts using the agent_toolset_20260401 configs array
  • Read the ten domain-format rules Anthropic validates at creation time so a 400 doesn't ship your bug to production
  • Reason about multiagent semantics — why allowlists intersect and blocklists add together across coordinator and roster
  • Handle the url_not_allowed tool_result and session.error events at runtime, and know when re-validation happens
  • Tell these settings apart from the Messages API server-tools domain filter and from the sandbox networking policy

An autonomous Managed Agents session that carries web_search and web_fetch is a request forger with a search engine. Any string it can be nudged into believing is a "helpful reference URL" — a prompt-injection payload in a retrieved page, a link in a memory store, a raw model hallucination — becomes an outbound request Anthropic's crawler will make on your behalf. Until this beta, the only way to prevent that was to switch the tools off and reintroduce them as custom tools you validated yourself.

The August 26 beta adds first-class per-tool allowed_domains and blocked_domains lists — plus max_content_tokens on fetches and user_location on searches — enforced on Anthropic's servers before the outbound request is made. Two things about that phrasing matter:

  • Enforcement is on Anthropic's servers, not inside your sandbox. So the sandbox networking policy (which controls what code inside the sandbox can reach) is unrelated. If you leave the toolset without domain lists but lock down the sandbox network, the agent's web_fetch still reaches wherever it wants — the fetch runs outside the sandbox.
  • Organization-level web filters in the Claude Console apply only to the Messages API. They don't attach to Managed Agents sessions. If your org has a console-level "block ads.example.com" rule and you never mirror it into the toolset, an agent session will fetch it.

Where the setting lives

Every built-in tool sits inside the agent_toolset_20260401 toolset object in the agent's tools array, and each tool is configured via an entry in that toolset's configs array. Entries are identified by name (web_search, web_fetch, bash, read, write, edit, glob, grep), typed by an optional type field with the same value, and — for the two web tools — accept allowed_domains, blocked_domains, max_content_tokens, and user_location alongside the usual enabled and permission_policy.

The minimal shape:

Pin web_search and web_fetch to two hosts, cap fetched content

{
"type": "agent_toolset_20260401",
"configs": [
  {
    "name": "web_search",
    "allowed_domains": ["docs.example.com", "arxiv.org"],
    "user_location": {
      "type": "approximate",
      "country": "US",
      "timezone": "America/Los_Angeles"
    }
  },
  {
    "name": "web_fetch",
    "blocked_domains": ["ads.example.com"],
    "max_content_tokens": 50000
  }
]
}

Each tool carries its own list — a search allowlist does not constrain fetches and vice versa. If you only want the two lists to move together, mirror them yourself.

A full agent create

POST /v1/agents — the whole request

curl -fsSL https://api.anthropic.com/v1/agents \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d '{
  "name": "Research Agent",
  "model": "claude-opus-5",
  "tools": [
    {
      "type": "agent_toolset_20260401",
      "configs": [
        {
          "name": "web_search",
          "allowed_domains": ["docs.example.com", "arxiv.org"],
          "user_location": {
            "type": "approximate",
            "country": "US",
            "timezone": "America/Los_Angeles"
          }
        },
        {
          "name": "web_fetch",
          "blocked_domains": ["ads.example.com"],
          "max_content_tokens": 50000
        }
      ]
    }
  ]
}'

In the Python, TypeScript, Go, Java, C#, Ruby, and PHP SDKs each configs entry is typed per-tool as a discriminated union (BetaManagedAgentsWebSearchToolConfigParams, BetaManagedAgentsWebFetchToolConfigParams, and so on). type is optional at construction because the server infers it from name, but it always comes back on responses. Requests that only set name, enabled, and permission_policy stay valid with or without type — old code doesn't need to be rewritten to hold this beta.

The ten domain-format rules

Anthropic validates every listed domain at agent create/update time and at session create/update time, and returns a 400 invalid_request_error with a message that names the list and the zero-based position of the offending entry (allowed_domains.0: IP addresses are not supported…). The rules are stricter than what the Messages API accepts, and each one is a foot-gun in the wild:

Guided walkthrough1 of 10
  1. Set either allowed_domains or blocked_domains on an entry — never both. An entry with both is rejected: "Only one of allowed_domains or blocked_domains may be set."

Two extra checks depend on the underlying providers and are also enforced at create/update time: a domain that Anthropic's crawler cannot access is refused for allowed_domains, an unsupported user_location.country returns a message ending in user_location.country: not a country the search provider supports, and a user_location.timezone must be a valid IANA name.

Multiagent sessions — how the lists combine

A multiagent session can layer three sets of lists on a single tool call: the coordinator's current lists, the lists on any agent that called this one, and the roster agent's own lists. Every list that applies to the thread is enforced at the same time.

  • Allowlists intersect. The effective set is the domains that all applicable allowlists cover. A roster agent can narrow what a tool reaches but never widen it — set an allowlist on a roster agent that isn't inside the coordinator's allowlist and every call comes back with a url_not_allowed error whose message states no domain is permitted. The tool description tells the model so up front.
  • Blocklists add. Every blocklist that applies is enforced together, so any host on any level's blocklist is unreachable to that thread.
  • max_content_tokens and user_location do not combine. A thread reads its own tool config first, then the calling agent's, then the coordinator's current config — first non-null wins.
  • {"type": "self"} roster entries have no web settings of their own and follow the coordinator's current config.
  • The grader in outcome-driven sessions runs without web tools at all — no allow or block list matters, because the grader has no web_search or web_fetch to run.

The consequence: if the coordinator's allowlist is [docs.example.com, arxiv.org] and a roster agent's allowlist is [github.com], the roster agent gets zero reachable hosts. Design roster agents' allowlists as subsets of the coordinator's, or don't set them at all.

Mid-session updates and the second validation

You can update an idle session's tools to change the domain lists — the new lists apply from that point on. In a multiagent session, each thread picks up the new lists on its next turn, but a roster agent's own lists are frozen at session-create time (they live on the agent definition, not the session).

A second validation happens when the session first initializes the tool. A domain that passed the sync check at create time can still fail later (an allowlisted host might have lost crawler access in the interval). If the runtime check fails, the session emits a session.error event, returns to idle, and does not retry. The fix is to update the session's tools, update the agent as well so new sessions start clean, then send a fresh user.message.

The runtime error path

At session time, the two tools behave differently when they encounter a URL their lists forbid.

  • web_fetch returns an error result to the agent: is_error: true on the agent.tool_result event, with a content block that names the error code url_not_allowed. The model sees it and can adapt (choose a different source, ask the user, or stop) — this is a normal tool failure, not a session failure.
  • web_search silently omits any result whose host the lists don't permit. The model doesn't see them at all. That's the right default for search — the alternative would leak the URLs of filtered results into the transcript — but it means a search that returns "no results" for what should be a good query is a signal to widen the allowlist, not just retry.

Both signals belong in your session event handler. session.error for the initialization-time miss, agent.tool_result with is_error: true and url_not_allowed for the per-call miss, and — as a rate signal — any search turn whose result count drops to zero when the allowlist is small.

Differences from the Messages API server-tools domain filter

Managed Agents deliberately runs a stricter regime than the Messages API's server_tools web_search and web_fetch filters. The vocabulary is the same, but four things are tighter and one thing is missing outright:

  • List cap is 64 domains, versus the Messages API's larger cap. Big allowlists don't port over — split them into multiple agents.
  • web_fetch domains cannot include a path. The Messages API accepts paths on both tools. Move any Messages-API-style example.com/blog entries to plain hostnames when porting.
  • ASCII only — Punycode required for internationalized names. The Messages API allows Unicode entries (while advising against them).
  • max_uses, citations, and cache_control are not available on the toolset. These are Messages-API-only knobs that the toolset chose not to surface. Design around per-session pricing and the response_inclusion parameter instead.

If you're porting an existing Messages-API agent to Managed Agents, most existing filters transfer cleanly. Bare example.com entries, subdomain-inclusive matching, and the "one list per entry" rule are all identical.

Common gotchas — five that trip real teams

  1. www.example.com is not example.com. Listing only www.example.com won't allow the bare-apex example.com, and listing only example.com will still cover www.example.com (because www. is a subdomain like any other). List the bare domain to get both.
  2. The sandbox networking policy does not affect these tools. web_search and web_fetch run on Anthropic's servers, so an environment that denies all egress from the sandbox still cheerfully fetches whatever the toolset allows. If you want defense in depth, mirror the two policies.
  3. Console-level org filters do not attach. The org-wide web filters in the Console are Messages API only. A "block ads.example.com" rule set in the Console never touches Managed Agents.
  4. Adding a session budget doesn't imply a domain restriction. Session budgets cap dollars, not destinations. A budgeted session can burn its cap fetching one hostile page. Use both.
  5. A web_search path suffix is a URL pattern, not a host rule. Prefer plain hostnames — path filters on search are advisory-strength and the provider may match them looser than you expect.

What this actually mitigates

Be specific about the threat model — over-claiming here bites teams that assume this is a full SSRF fix. It is not. The three concrete risks it does address:

  • Prompt-injection navigation. A poisoned page or memory-store entry that instructs the agent to "fetch this URL to continue" fails with url_not_allowed if the host isn't allowed, and the model sees the error and (usually) stops.
  • Ad and telemetry destinations. A blocklist keeps the agent's fetches out of tracking domains that a target page tries to hop the agent to.
  • Data-exfil via fetch. An agent tricked into building a web_fetch URL with sensitive query-string params (?leaked=<memory>) can't reach a hostile receiver whose host isn't allowed.

What it does not stop: outbound requests from custom tools you define, from MCP-server tools, from code your sandbox runs, or from any tool that isn't web_search/web_fetch. Each of those has its own knob (sandbox networking, MCP server allowlists, custom-tool code). This one setting is one layer in a many-layer story.

Key takeaways
  • allowed_domains and blocked_domains live per-tool inside the agent_toolset_20260401 configs array; each tool carries its own list and they don't move together automatically
  • Ten format rules — no scheme, no port, no wildcard, no path on web_fetch, no bare TLDs, no localhost or .local, Punycode for IDNs, 1-64 domains, no duplicates, subdomain match is downward only
  • Multiagent semantics: allowlists intersect and blocklists add. A roster agent can narrow but never widen the coordinator's reach
  • Runtime: web_fetch fails a forbidden URL with is_error and url_not_allowed; web_search silently omits forbidden results
  • This is Managed-Agents-only. Console org filters don't attach, and sandbox networking policy doesn't affect these tools — mirror rules if you want defense in depth
  • Not the same as the Messages API server-tools filter: stricter (64-cap, no paths on web_fetch, ASCII only) and missing max_uses, citations, cache_control

Check yourself

Check yourself

0/5
  1. You set allowed_domains: ["example.com"] on the web_fetch entry. Which of these URLs will the agent be allowed to fetch?
  2. A coordinator's web_fetch allowlist is ["docs.example.com", "arxiv.org"]. A roster agent sets its own web_fetch allowlist to ["github.com"]. What happens when the roster agent tries to fetch https://github.com/anthropic-ai/sdk?
  3. Your team has a policy of blocking ads.example.com at the Claude Console org level. You spin up a Managed Agents session with web_fetch enabled and no domain lists. The agent fetches https://ads.example.com. What happens?
  4. You submit an agent with allowed_domains: ["https://docs.example.com", "127.0.0.1", "co.uk"]. What does the API return?
  5. A web_fetch call for a URL its list doesn't permit does what?

Sources & further reading

Next