Skip to content

fix: Port URL validation, client timeout and credential scoping fixes to v1 - #6802

Open
GWeale wants to merge 6 commits into
v1from
backport-v1-pr10
Open

fix: Port URL validation, client timeout and credential scoping fixes to v1#6802
GWeale wants to merge 6 commits into
v1from
backport-v1-pr10

Conversation

@GWeale

@GWeale GWeale commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Ports six commits from main to the v1 branch.

  1. Block IPv6 literals that embed a non-global IPv4 (6f182571, the load_web_page part only)

    • load_web_page refuses IPv4-mapped, 6to4, NAT64 and IPv4-compatible forms whose embedded IPv4 is not globally routable.
  2. Honour request timeouts on Apigee (2547db61)

    • http_options.timeout applies to the streaming and non-streaming completions calls.
  3. Bound the completions client (f57a67d6)

    • 30s connect budget, 600s request budget, follow_redirects=False.
    • Breaking: a proxy answering /chat/completions with a 3xx raises httpx.HTTPStatusError. Point the deployment at the host the redirect names.
  4. Reject base_url in generate_content_config (472e4635 as amended by 4c6f22e8, landed as one commit)

    • Breaking at construction: LlmAgent raises ValueError for generate_content_config.http_options.base_url. Set the endpoint on the model or its genai.Client. Headers, timeout, retries and extra_body are unaffected.
  5. Validate URLs before computer use navigate (b0fff3f0)

    • navigate applies load_web_page's URL checks and returns an error dict when one fails.
    • ComputerUseToolset(allow_private_network_access=True) re-enables localhost and internal hosts.
  6. Scope API registry credentials (cc275f0c)

    • ADC tokens go only to https endpoints on googleapis.com; every other MCP server takes its headers from header_provider.

GWeale added 6 commits August 18, 2026 20:24
load_web_page decided whether a resolved address was safe from a bare
is_global check on the address itself. That reads the outer IPv6 address
and not the IPv4 target it may encode, so http://[64:ff9b::169.254.169.254]/
was treated as public and fetched, and on a network with NAT64 it reaches
the internal 169.254.169.254 metadata endpoint. The IPv4-compatible form
::169.254.169.254 passed the same way.

The check now unwraps the IPv4 address embedded in an IPv6 address and
refuses it when that IPv4 is not globally routable. It covers the
IPv4-mapped, 6to4, NAT64 and IPv4-compatible encodings. Current CPython
patch releases already refuse the first two inside is_global, but the
declared minimum interpreter of 3.10 does not, so all four are unwrapped
here. An encoding of a public address, such as 64:ff9b::8.8.8.8, is still
fetched.

Behaviour change: a fetch of an internal address written in one of those
IPv6 encodings now fails where it previously succeeded.

This is a prerequisite for the computer use navigate guard later in this
branch, which reuses the same address check.
The Apigee model accepted a configured http_options.timeout but silently
ignored it, so a stalled request could wait forever.

The timeout is now passed through to Apigee's OpenAI-compatible HTTP calls
for both streaming and non-streaming requests, converting the documented
Google GenAI millisecond value to HTTPX seconds. When no timeout is set the
existing unlimited default is kept.

Behaviour change: an application that set http_options.timeout while it was
being ignored now gets the shorter budget it asked for.
The httpx client behind the Apigee OpenAI-compatible completions path was
built with timeout=None and follow_redirects=True. A stalled proxy could
hold the connection and the streaming loop open forever, and a 3xx from the
proxy moved the request, its payload and its Authorization header to
whatever host the proxy named.

The client now gets a finite budget of 30 seconds to connect and 600
seconds for the request, and no longer follows redirects. A caller-supplied
http_options.timeout still sets the request budget, but the connect budget
stays short so an unreachable proxy fails fast rather than consuming the
whole budget.

Behaviour changes: an Apigee proxy that answers /chat/completions with a
3xx now surfaces an HTTP status error instead of being followed, and a
non-streaming request that runs past 600 seconds now fails. A stream whose
individual chunks arrive inside the budget is unaffected, because httpx
spends the read budget per read rather than per request.
An agent-level generate_content_config.http_options.base_url is copied into
every LlmRequest and overrides the client transport, so the configured API
key and the full prompt and response traffic go to that host. Nothing
rejected it, so a supplied agent config, including a YAML one, could
redirect a credentialed model call to an arbitrary endpoint.

The field validator on LlmAgent.generate_content_config now rejects
http_options.base_url. Request-time http options such as headers, timeout,
retry options and extra_body are unaffected; base_url is a transport
setting and belongs on the model or its client.

Behaviour change: an agent that sets base_url this way now raises
ValueError at construction rather than at request time.

This lands as one commit, because the upstream fix also rejected extra_body
and a follow-up took that rejection back out again, so v1 never carries the
intermediate state.
ComputerUseToolset passed the url the model supplied straight to the
browser driver, without checking it. A model-invented url could point the
browser at a cloud metadata endpoint, a file:// path, or a private-network
host.

navigate now runs the same url checks load_web_page does before the url
reaches the driver. A url that fails returns an error to the model,
reporting the page the browser is still on, rather than opening it. Name
resolution runs off the event loop, so every navigation to a public host
now costs one DNS lookup.

Behaviour change: an agent driving a browser at localhost or another
internal host gets a refusal until it is constructed with the new
ComputerUseToolset(allow_private_network_access=True), which defaults to
False.

This reuses v1's existing load_web_page helpers, and the earlier commit in
this branch that unwraps embedded IPv4 addresses is a prerequisite for it.
With that commit in place the guard refuses the same addresses main's does,
including the NAT64 and IPv4-compatible encodings of an internal address.
…ndpoints (v1)

ApiRegistry.get_toolset took the MCP server URL verbatim from the registry
response and attached the caller's own ADC bearer token to it, whatever
host the entry named. A registry entry pointing at an arbitrary host
received that token.

The credentials are now attached only when the resolved URL is an https
endpoint whose host is googleapis.com or a subdomain of it. Every other
server gets headers from header_provider, as before.

Behaviour change: a non-Google MCP server that relied on receiving those
credentials will start getting 401 and needs header_provider instead. A
googleapis.com entry registered with an explicit http:// scheme also loses
them, which is the intent.

The upstream import
hunk did not apply because v1's import block predates the mTLS support and
the deprecation, so the urlparse import was added by hand.
@GWeale GWeale changed the title fix: Port outbound request and credential scoping fixes to v1 fix: Port URL validation, client timeout and credential scoping fixes to v1 Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants