Repository navigation
Description
Summary
ComputerUseToolset.navigate validates only the model-supplied initial URL. The browser then follows HTTP redirects, HTML <meta http-equiv="refresh">, and JavaScript navigation to private/link-local addresses, so allow_private_network_access=False can be bypassed. This is an SSRF from the computer-use browser to localhost, private networks, and cloud metadata endpoints.
Affected code
src/google/adk/tools/computer_use/computer_use_toolset.py, _wrap_navigate_with_url_validation (around line 513):
target = _parse_request_target(url)
if not self._allow_private_network_access:
if _is_blocked_hostname(target.hostname):
raise ValueError('hostname is blocked')
await asyncio.to_thread(_resolve_direct_addresses, target.hostname)
...
return await navigate_method(url, *args, **kwargs)The check covers only the URL the model passed. navigate_method sends Page.navigate to the browser (sandbox_client.navigate -> _CDP_COMMAND_PAGE_NAVIGATE), and Chrome follows redirects and renders the target page. There is no post-navigation check of ComputerState.url or the redirect chain.
load_web_page already uses allow_redirects=False (load_web_page.py:180,200); the computer-use path needs an equivalent.
Reproduction (confirmed)
-
Public initial page (validator passes; resolves to GitHub Pages IPs):
https://htmlpreview.github.io/?https://github.com/Sanu1999/adk-ssrf-poc/blob/main/index.htmlThe page contains:
<meta http-equiv="refresh" content="0;url=http://127.0.0.1:18888/secret">
-
_url_validatoron the initial URL:initial host: htmlpreview.github.io blocked= False initial resolves: (185.199.111.153, 185.199.108.153, ...) localhost: blocked -> Blocked host: 127.0.0.1 -
A browser navigation starting at the public URL ends at the private service:
URL http://127.0.0.1:18888/secret TEXT SECRET-DATA-FROM-PRIVATE-SERVICE
The private service is a local HTTP server serving SECRET-DATA-FROM-PRIVATE-SERVICE; the same bypass works with an HTTP 302 from an attacker-controlled server and with window.location JavaScript. The model can read the resulting page/screenshot and exfiltrate it.
Impact
An attacker who can get the computer-use agent to visit their page (prompt injection, a link in an email/document, a model-generated URL) can make the browser issue requests to private addresses despite allow_private_network_access=False. In the VMaaS sandbox this includes cloud metadata (169.254.169.254), local admin services, and other internal hosts reachable from the browser; the response is rendered into the model's view, enabling credential/data exfiltration. This directly bypasses the guard added in #6802 ("Validate URLs before computer use navigate").
Suggested fix
- After navigation, validate
ComputerState.urlagainst_url_validatorand refuse if the final URL (or any redirect hop) is private/link-local; close/reset the page. - Better, intercept the browser's requests/redirects (CDP
Fetch/Network) and validate every URL before continuing, so JS/meta/302 redirects cannot escape. - Consider a browser egress proxy with a policy that enforces the same rule.
- Add regression tests with an HTTP 302 and a meta-refresh page pointing at
127.0.0.1/169.254.169.254.
Versions
google-adk 2.13.0, master 766275863d1e80489344f0fbe5b4bfa3b933a9a9 (2026-11-02), Python 3.13, Windows 11. The navigate URL check has the same shape on the v1 branch (#6802).
I could not use a private channel (no SECURITY.md and GitHub private vulnerability reporting is disabled); filing publicly. I can provide the local server/page script or move this to a private report if preferred.
Activity
sumedh-aerram commentedon Nov 3, 2026
I'd like to take this on. I'll investigate the navigation validation bypass and work on a fix with regression tests.
test(tools): cover IPv4-mapped and short-form loopback landings