Skip to content

Security: ComputerUseToolset.navigate validates only the initial URL; redirects/meta refresh bypass allow_private_network_access (SSRF) #7968

Open

Description

@Sanu1999

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)

  1. Public initial page (validator passes; resolves to GitHub Pages IPs):
    https://htmlpreview.github.io/?https://github.com/Sanu1999/adk-ssrf-poc/blob/main/index.html

    The page contains:

    <meta http-equiv="refresh" content="0;url=http://127.0.0.1:18888/secret">
  2. _url_validator on 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
    
  3. 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.url against _url_validator and 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

  1. sumedh-aerram commented on Nov 3, 2026

    @sumedh-aerram

    I'd like to take this on. I'll investigate the navigation validation bypass and work on a fix with regression tests.

  2. self-assigned this
    on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions