Test a local app
Why localhost does not work from a cloud browser, and how to test an app on your machine or network with a tunnel or your own proxy.
The browser that webagent runs lives in the cloud. It cannot open http://localhost:3000 on your laptop, because that address means "this machine", and "this machine" is our server.
This page explains what happens if you try, and the two ways that work:
- A tunnel to your dev server. Best for one developer on a laptop.
- Your own proxy plus an allow list. For an app on a company network.
If your app is already on a public preview or staging URL, skip all of this and use that URL. See Protected sites if it is behind a gate.
Why localhost fails
Every navigation passes an SSRF guard. The guard refuses localhost, *.localhost, *.internal, loopback addresses (127.0.0.1), link-local and cloud metadata addresses, and private addresses such as 192.168.x.x or 10.x.x.x. No option you can send turns it off. It protects our network and yours.
If you pass such a URL as startUrl, session creation fails with HTTP 403:
{
"error": {
"code": "navigation_blocked",
"message": "startUrl http://localhost:3000 was blocked (private, link-local and metadata addresses are not reachable): navigation blocked: internal host",
"details": { "url": "http://localhost:3000", "reason": "internal host", "fields": ["startUrl"] }
}
}The reason changes with the address:
| Address | reason |
|---|---|
localhost, *.localhost, *.internal | internal host |
127.0.0.1 and other loopback or link-local addresses | blocked address 127.0.0.1 |
192.168.1.20, 10.0.0.5 | private address 192.168.1.20 |
If the agent calls navigate inside a running session, the same refusal comes back as navigation blocked: internal host. A click or redirect onto a refused host comes back as navigation to <host> was blocked: <reason>. A blocked test is a test that could not run. It is not a bug in your app.
Option 1: a tunnel
A tunnel gives your local port a public HTTPS address. The cloud browser opens that address, and the tunnel forwards the traffic to your machine. The tunnel host resolves to a public IP, so the guard lets it through.
A tunnel URL is public. Anyone who has the URL can open your dev server, and the agent can click anything on it. Use test data. Do not tunnel an app that holds real customer data. Stop the tunnel when the test is done.
Start your app
Run your dev server as usual and note the port, for example 3000.
Start a tunnel in a second terminal
Pick one of the two tools.
Install cloudflared (see the Cloudflare quick tunnel page), then run:
cloudflared tunnel --url http://localhost:3000It prints a temporary https://<random-words>.trycloudflare.com URL. You need no Cloudflare account. The URL changes every time and stops working when you stop cloudflared. A quick tunnel does not support server-sent events (SSE) and handles up to 200 requests at once.
Check the URL yourself
Open the tunnel URL in your own browser. You should see your app. If your dev server answers with a "host not allowed" error, add the tunnel host to its allow list. In Vite that is server.allowedHosts.
Give the URL to the agent
In Claude Code or Codex, ask for the test with the tunnel URL, not localhost:
Smoke test https://<random-words>.trycloudflare.com. Log in with the test user and
check that the dashboard shows "Welcome". Give me the verdict and the failed steps.Or call smoke_test yourself with url set to the tunnel URL, or run_task with startUrl. See MCP tools.
Stop the tunnel
Press Ctrl+C in the tunnel terminal. The URL stops working at once.
We ran a webagent session through an ngrok free tunnel to a static page. Without the ngrok-skip-browser-warning header the browser saw ngrok's warning page ("You are about to visit: ..."). With the header, in the session headers option, it saw the page. We have not run the cloudflared steps.
ngrok warning page
The ngrok free plan shows an interstitial page in front of all HTML browser traffic. The agent would see that page instead of your app. ngrok skips it for requests that carry a header named ngrok-skip-browser-warning (any value). ngrok's free plan limits page describes it.
Send that header with the session headers option. It goes only to the host you name. run_task and smoke_test take it directly, so one call is enough. The steps are in Protected sites.
Option 2: your own proxy and an allow list
If the app lives on a private network, for example a staging server at 10.0.0.5, there is one supported route. You run a proxy that can reach the app. The session sends its traffic through your proxy, and an allow list tells the guard which private hosts to accept.
{
"proxyUrl": "http://user:[email protected]:3128",
"allowHosts": ["10.0.0.5"],
"startUrl": "http://10.0.0.5:8080"
}How it works:
allowHoststakes hosts, IPs or CIDRs. It is only valid together withproxyUrl, or with a saved proxy (proxyId). Without one of them the request fails withinvalid_input.- The proxy URL is
http://user:pass@host:portorsocks5://host:port. - Private hosts are reached through your proxy, never from our network. Our own network stays blocked even if you list it.
- Loopback, link-local and metadata addresses stay blocked, even in
allowHosts. This option cannot openlocalhost. - Our servers must be able to reach your proxy over the internet. A proxy behind your laptop's NAT does not work. A proxy on a company network with a public address and a password does.
run_task takes proxyUrl and allowHosts itself, so one call is enough. smoke_test and a skill run take them inside their session object:
{
"url": "http://10.0.0.5:8080",
"expectText": "Welcome",
"session": { "proxyUrl": "http://user:[email protected]:3128", "allowHosts": ["10.0.0.5"] }
}run_task refuses these options together with sessionId: put them on create_session instead. See Sessions and Skills.
We have not run this route against a real corporate proxy for this page.
Which one to pick
| Your app is | Use |
|---|---|
| On a public preview or staging URL | That URL. No tunnel. |
| On your laptop | A tunnel. |
| On a private network, and you can run a public proxy | proxyUrl and allowHosts. |
| Public but behind a WAF, Vercel protection or Cloudflare Access | Protected sites. |
Playground
Try a goal on a real cloud browser from the dashboard, stop or steer it, read the report, and copy the run as a prompt or cURL.
Protected sites
Test a staging site that sits behind a WAF, Vercel deployment protection, Cloudflare Access or an ngrok warning page, by sending your own bypass header.