Skip to content

fix(web-fetch): prevent SSRF via DNS hostnames and redirects#27739

Open
g0w6y wants to merge 2 commits into
google-gemini:mainfrom
g0w6y:fix/web-fetch-ssrf-dns-redirect
Open

fix(web-fetch): prevent SSRF via DNS hostnames and redirects#27739
g0w6y wants to merge 2 commits into
google-gemini:mainfrom
g0w6y:fix/web-fetch-ssrf-dns-redirect

Conversation

@g0w6y

@g0w6y g0w6y commented Jun 8, 2026

Copy link
Copy Markdown

What this fixes

The web_fetch tool guards outbound requests with isBlockedHost
(packages/core/src/tools/web-fetch.ts), which inspects only the hostname
string of the entry url and delegates to the synchronous isPrivateIp. Two
gaps let a request reach a private or internal target:

  1. Hostname bypass. isPrivateIp returns false for anything that is not an IP
    literal, so a DNS hostname that resolves to a private or link local address
    passes the guard. Examples are metadata.google.internal, an attacker owned
    domain whose A record points to 169.254.169.254 or 127.0.0.1, and
    wildcard resolvers such as 169.254.169.254.nip.io.
  2. Redirect bypass. The guard runs only on the entry url. The fetch then follows
    HTTP redirects with the default behaviour and no revalidation, so an allowed
    host can redirect to an internal address.

In both cases the internal response body is returned to the caller, so the
attacker reads internal or cloud metadata content. On a Google Cloud host this
includes the service account token at
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token.

The repository already contains isPrivateIpAsync, which resolves the hostname
and checks the address, but web_fetch never used it.

The change

Add assertUrlIsPublic and safeFetchFollowingRedirects to
packages/core/src/utils/fetch.ts.

  1. assertUrlIsPublic rejects a url whose literal hostname is private or
    loopback, and whose resolved address is private or link local, reusing the
    existing isPrivateIp and isPrivateIpAsync.
  2. safeFetchFollowingRedirects follows redirects manually with
    redirect: 'manual' and calls assertUrlIsPublic on the initial url and on
    every redirect hop before connecting, so a hostname that resolves inward and
    a redirect that points inward are both refused. It caps the redirect chain.

web_fetch now uses safeFetchFollowingRedirects for both fallback fetch
paths. The existing synchronous isBlockedHost pre check is kept as a fast
first filter.

Test cases

Added packages/core/src/utils/fetch.ssrf.test.ts:

  1. assertUrlIsPublic rejects literal private and loopback addresses:
    127.0.0.1, 169.254.169.254, 10.0.0.5, 192.168.1.1, localhost.
  2. assertUrlIsPublic rejects a hostname that resolves to a private address
    (dns lookup mocked), including metadata.google.internal resolving to
    169.254.169.254.
  3. assertUrlIsPublic allows a hostname that resolves to a public address.
  4. safeFetchFollowingRedirects refuses a private target, so the internal body
    is never read.

Manual end to end check that the redirect hop is revalidated. With a local
internal server holding a secret and a redirector that points at it, a fetch
that passes the entry check is still refused when the redirect target resolves
to a private address, and the secret is never returned.

Suggested follow up

For full protection against DNS rebinding between the check and the connection,
the validated address can be pinned at connect time with a custom dispatcher.
This change closes the reported bypasses without that larger refactor.

Security

This addresses a Server Side Request Forgery issue (CWE 918). This was reported
to the Google Bug Hunters program.

Checklist

  1. Fix scoped to the web_fetch fetch paths.
  2. Unit tests added.
  3. Reuses existing isPrivateIpAsync rather than introducing a parallel check.

The web_fetch tool guarded outbound requests with isBlockedHost, which
inspects only the entry url hostname string. A non IP hostname that
resolves to a private or link local address, for example
metadata.google.internal, passed the guard, and redirects were followed
with no revalidation, so an allowed host could redirect to an internal
address. In both cases the internal response body was returned to the
caller.

Add safeFetchFollowingRedirects, which validates the initial url and
every redirect hop against the literal address and the resolved address
using the existing isPrivateIpAsync, and follows redirects manually.
web_fetch now uses it for both fallback fetch paths.

Add tests for assertUrlIsPublic and the guarded fetch.

This was reported to the Google Bug Hunters program.
@g0w6y g0w6y requested review from a team as code owners June 8, 2026 06:55
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a Server Side Request Forgery (SSRF) vulnerability in the web_fetch tool. The previous implementation only performed a superficial check on the initial URL hostname, leaving it susceptible to bypasses via DNS hostnames that resolve to internal addresses or through malicious HTTP redirects. The changes introduce robust validation logic that checks both the initial request and every subsequent redirect hop, ensuring that no internal or private network resources can be accessed.

Highlights

  • SSRF Prevention: Introduced assertUrlIsPublic to validate both literal hostnames and resolved IP addresses against private, loopback, and link-local ranges.
  • Redirect Security: Implemented safeFetchFollowingRedirects to manually handle HTTP redirects, ensuring every hop is revalidated against the SSRF guard.
  • Tool Integration: Updated web_fetch to utilize the new secure fetch utility, replacing the previous vulnerable implementation.
  • Testing: Added comprehensive unit tests in fetch.ssrf.test.ts covering various SSRF bypass scenarios, including DNS resolution to private IPs.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added the size/m A medium sized PR label Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

📊 PR Size: size/L

  • Lines changed: 327
  • Additions: +318
  • Deletions: -9
  • Files changed: 3

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

🛑 Action Required: Evaluation Approval

Steering changes have been detected in this PR. To prevent regressions, a maintainer must approve the evaluation run before this PR can be merged.

Maintainers:

  1. Go to the Workflow Run Summary.
  2. Click the yellow 'Review deployments' button.
  3. Select the 'eval-gate' environment and click 'Approve'.

Once approved, the evaluation results will be posted here automatically.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces SSRF protection for web fetching by replacing fetchWithTimeout with a new safeFetchFollowingRedirects utility, which manually follows redirects and validates that each hop targets a public IP address. It also adds corresponding unit tests. The review feedback highlights critical security and correctness issues in the manual redirect implementation, specifically regarding cross-origin header leakage (CWE-200), potential protocol smuggling/SSRF bypass due to lack of protocol validation, and HTTP spec compliance for redirect methods (e.g., handling 303 redirects by changing the method to GET). A comprehensive code suggestion is provided to address these issues.

Comment thread packages/core/src/utils/fetch.ts
Address review feedback on the SSRF fix:
- reject redirect targets whose protocol is not http or https, preventing
  protocol smuggling to file, gopher, or ftp
- strip credential carrying headers (authorization, cookie, cookie2,
  proxy-authorization) when a redirect crosses to a different origin
- downgrade the method to GET and drop the body and content headers for 303
  responses and for non GET or HEAD 301 and 302 responses, per RFC 9110

Redirect logic is extracted into applyRedirect and covered by unit tests.
@github-actions github-actions Bot added the size/l A large sized PR label Jun 8, 2026
@g0w6y

g0w6y commented Jun 8, 2026

Copy link
Copy Markdown
Author

Thanks for the review. Addressed all three points in the follow up commit:

  1. Protocol smuggling: redirect targets are now rejected unless the protocol is http or https.
  2. Cross origin header leakage: authorization, cookie, cookie2, and proxy-authorization are stripped when a redirect crosses to a different origin.
  3. RFC 9110: 303, and non GET or HEAD 301 and 302, are downgraded to GET with the body and content headers removed.

The redirect logic is extracted into applyRedirect with unit tests covering protocol rejection, header stripping on cross vs same origin, and the method downgrade cases.

@gemini-cli gemini-cli Bot added the status/need-issue Pull requests that need to have an associated issue. label Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l A large sized PR size/m A medium sized PR status/need-issue Pull requests that need to have an associated issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant