Skip to content

fix(security): prevent SSRF via DNS resolution bypass in web-fetch - #28725

Closed
alifakbxr wants to merge 2 commits into
google-gemini:mainfrom
alifakbxr:fix/28555-ssrf-dns-bypass
Closed

alifakbxr wants to merge 2 commits into
google-gemini:mainfrom
alifakbxr:fix/28555-ssrf-dns-bypass

Conversation

@alifakbxr

Copy link
Copy Markdown

Description

Fixes #28555

This PR addresses a critical Server-Side Request Forgery (SSRF) vulnerability (CVSS 8.6) in the web-fetch tool where malicious actors could bypass DNS protections by using a custom domain pointing to a private or loopback IP address (e.g., 169.254.169.254).

The previous implementation relied on synchronous IP checks (isPrivateIp) which only caught explicit private IP addresses but did not resolve hostnames to their underlying IPs.

Changes

  • Updated isBlockedHost in web-fetch.ts to utilize the asynchronous isPrivateIpAsync from fetch.ts, ensuring that all domains are properly resolved and checked for private/loopback IP resolution before being fetched.
  • Refactored filterAndValidateUrls to an async function to properly await the isBlockedHost checks.
  • Refactored caller execution flows in WebFetchTool (execute, executeExperimental, executeFallbackForUrl) to await the filtered URLs.
  • Updated web-fetch.test.ts to mock the new asynchronous implementation and ensure accurate coverage of the IP resolution logic.

Security Impact

This fix prevents attackers from accessing internal services, metadata endpoints, or bypassing network restrictions through the Gemini CLI's web-fetch capability.

Checklist

  • I have read the CONTRIBUTING.md guide.
  • My code follows the code style of this project.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have updated the documentation accordingly (if applicable).
  • I have added a valid Apache-2.0 copyright header to all new files (if applicable).

Refs: google-gemini#28555

- Updated isBlockedHost to use async DNS resolution via isPrivateIpAsync
- Refactored WebFetchTool callers to await block checks
- Ensures domains pointing to loopback/private IPs are correctly blocked
@alifakbxr
alifakbxr requested review from a team as code owners August 7, 2026 08:12
@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 critical Server-Side Request Forgery (SSRF) vulnerability in the web-fetch tool. By shifting from synchronous IP checks to an asynchronous resolution process, the tool now properly validates hostnames against private and loopback IP ranges, preventing attackers from accessing internal services or metadata endpoints through DNS-based obfuscation.

Highlights

  • SSRF Mitigation: Updated the web-fetch tool to perform asynchronous DNS resolution for host validation, preventing bypasses where malicious domains resolve to private or loopback IP addresses.
  • Async Refactoring: Refactored isBlockedHost and filterAndValidateUrls to be asynchronous, ensuring all URL validation logic correctly awaits IP resolution.
  • Loopback Handling: Improved detection of loopback addresses by explicitly checking for ::1 and ensuring all loopback hosts are correctly identified as blocked.
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 Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

📊 PR Size: size/M

  • Lines changed: 63
  • Additions: +37
  • Deletions: -26
  • Files changed: 3

@github-actions

github-actions Bot commented Aug 7, 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 refactors the private IP check in WebFetchTool to use the asynchronous isPrivateIpAsync helper, adds IPv6 loopback (::1) to the blocked hosts list, and fixes a bug where loopback hosts were incorrectly returned as non-private. The review feedback highlights critical Server-Side Request Forgery (SSRF) vulnerabilities, specifically pointing out that pre-flight checks are susceptible to DNS Rebinding (TOCTOU) and 3xx redirect bypasses. It is highly recommended to implement socket-level validation using a custom connection manager (such as an undici connector) to secure the fetch operations.


if (isLoopbackHost(hostname)) {
return false;
return true;

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.

critical

DNS Rebinding (TOCTOU) SSRF Vulnerability

Performing an asynchronous pre-flight DNS check (isPrivateIpAsync) before executing fetch is vulnerable to a Time-of-Check to Time-of-Use (TOCTOU) DNS Rebinding attack.

An attacker can configure a malicious DNS server with a very low TTL (Time-To-Live) that resolves to a public IP during the isPrivateIpAsync check (passing the validation), but resolves to a private/loopback IP (e.g., 127.0.0.1 or 169.254.169.254) when fetch performs its own DNS resolution to establish the connection.

To completely prevent DNS Rebinding, you should intercept DNS resolution at the socket connection level using a custom undici connector. This ensures that the IP address being connected to is validated at the exact moment the connection is established.

Here is a secure implementation using buildConnector from undici:

import { Agent, buildConnector } from 'undici';
import dns from 'node:dns';

const safeConnector = buildConnector({
  lookup: (hostname, options, callback) => {
    dns.lookup(hostname, options, (err, address, family) => {
      if (err) {
        callback(err);
        return;
      }
      
      const addresses = Array.isArray(address) 
        ? address 
        : [{ address, family }];
        
      const hasPrivate = addresses.some(addr => isAddressPrivate(addr.address));
      if (hasPrivate) {
        callback(new PrivateIpError('Access to private network is blocked'));
      } else {
        callback(null, address, family);
      }
    });
  }
});

// Configure the global dispatcher with the safe connector
setGlobalDispatcher(
  new Agent({
    connect: safeConnector,
    headersTimeout: defaultHeadersTimeout,
    bodyTimeout: defaultBodyTimeout,
  }),
);

This centralized socket-level validation is a robust, "secure by default" defense-in-depth approach that renders pre-flight checks obsolete and fully secures the web-fetch tool.

Comment on lines +270 to +281
private async isBlockedHost(urlStr: string): Promise<boolean> {
try {
const url = new URL(urlStr);
const hostname = url.hostname.toLowerCase();
if (hostname === 'localhost' || hostname === '127.0.0.1') {
if (
hostname === 'localhost' ||
hostname === '127.0.0.1' ||
hostname === '::1'
) {
return true;
}
return isPrivateIp(urlStr);
return await isPrivateIpAsync(urlStr);

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.

security-high high

This code is vulnerable to Server-Side Request Forgery (SSRF) bypasses due to both DNS Rebinding (Time-of-Check to Time-of-Use) and automatic 3xx redirect handling.

  1. DNS Rebinding: The isBlockedHost check is performed on the hostname, but the actual fetch request performs its own DNS resolution. A malicious DNS server could return a public IP during the initial isBlockedHost check and then a private IP (e.g., 127.0.0.1 or 169.254.169.254) when fetch establishes the connection, bypassing the protection.
  2. 3xx Redirect Bypass: The isBlockedHost check is only applied to the initial URL. fetch (via undici) automatically follows HTTP redirects (3xx status codes). If a public URL redirects to a private/loopback IP, the redirect will be followed without re-running isBlockedHost, completely bypassing the SSRF protection.

To mitigate these issues, the IP address validation must be performed at the connection level. This can be done by configuring a custom DNS lookup or connect function in the global undici dispatcher to reject private IPs. Additionally, consider setting redirect: 'manual' in fetch options and manually validating each redirect URL in the chain, or ensure the connection-level validation covers redirected IPs.

@gemini-cli gemini-cli Bot added priority/p2 Important but can be addressed in a future release. area/core Issues related to User Interface, OS Support, Core Functionality labels Aug 7, 2026
@gemini-cli

gemini-cli Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Hi there! Thank you for your interest in contributing to Gemini CLI.

To ensure we maintain high code quality and focus on our prioritized roadmap, we only guarantee review and consideration of pull requests for issues that are explicitly labeled as 'help wanted'.

This PR will be closed in 7 days if it remains without that designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

@gemini-cli

gemini-cli Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

This pull request is being closed as it has been open for 14 days without a 'help wanted' designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

@gemini-cli gemini-cli Bot closed this Aug 22, 2026

This branch had an error being deployed

1 failed deployment
eval-gate — 026cd9f2 Deployed Aug 7, 2026 by alifakbxr via Evaluate Steering & Regressions #1750
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Issues related to User Interface, OS Support, Core Functionality priority/p2 Important but can be addressed in a future release. size/m A medium sized PR status/pr-nudge-sent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant