Skip to content

fix(core): prevent SSRF in MCP OAuth metadata discovery and authentication - #29081

Open
josebalius wants to merge 2 commits into
mainfrom
fix/mcp-oauth-ssrf-validation
Open

fix(core): prevent SSRF in MCP OAuth metadata discovery and authentication#29081
josebalius wants to merge 2 commits into
mainfrom
fix/mcp-oauth-ssrf-validation

Conversation

@josebalius

Copy link
Copy Markdown
Contributor

Enforce RFC 9728 Section 7.7 and RFC 8414 security constraints during MCP OAuth discovery, dynamic client registration, and token exchange/refresh.

  • Enforce HTTPS for remote OAuth endpoints (allow HTTP only for loopback when connecting to local MCP servers)
  • Validate origin matching for resource_metadata in WWW-Authenticate challenges
  • Block private IPv4/IPv6 address ranges, loopback endpoints from remote servers, link-local / IMDS (169.254.169.254), benchmark ranges (198.18.0.0/15), and multicast/broadcast
  • Perform asynchronous DNS resolution to prevent DNS rebinding attacks against internal IP ranges
  • Validate dynamic client registration endpoints and token exchange/refresh URLs prior to issuing requests
  • Add comprehensive test coverage in oauth-utils.test.ts, oauth-provider.test.ts, and oauth-flow.test.ts

Summary

Prevents Server-Side Request Forgery (SSRF) during Model Context Protocol (MCP) OAuth 2.0 metadata discovery, dynamic client registration, and token exchange/refresh flows by implementing strict URL validation, origin matching, and network boundary enforcement per RFC 9728 Section 7.7 and RFC 8414.

Details

Remote MCP servers returning unvalidated WWW-Authenticate: Bearer resource_metadata="..." challenge headers or authorization_servers URLs could previously trigger out-of-band HTTP requests to internal IP addresses, local services (localhost/127.0.0.1), or cloud instance metadata services (IMDS at 169.254.169.254).

Core Protections Implemented:

  1. Scheme & Loopback Enforcement:
    • Requires https: for all remote endpoints.
    • Allows http: only for loopback addresses (localhost, 127.0.0.1, [::1]) when explicitly connecting to a local MCP server (allowLoopback: true).
    • Disallows loopback redirection/discovery when connecting to a remote MCP server (allowLoopback: false).
  2. RFC 9728 §7.7 Origin Validation:
    • Verifies that resource_metadata in WWW-Authenticate strictly matches the MCP server's origin.
    • Properly resolves relative metadata URLs against the MCP server base URI.
  3. Private IP & Cloud IMDS Blocking:
    • Rejects private IPv4 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10), link-local / IMDS (169.254.169.254), benchmark testing (198.18.0.0/15), IPv6 private/link-local ranges, and multicast/broadcast addresses.
  4. DNS Rebinding Prevention:
    • Asynchronously resolves hostnames via node:dns/promises and validates all resolved IP addresses against private network boundaries before issuing HTTP requests.
  5. Flow Endpoint Validation:
    • Validates dynamic client registration endpoints in MCPOAuthProvider.registerClient() before issuing registration POST requests.
    • Validates tokenUrl in exchangeCodeForToken() and refreshAccessToken() before issuing token requests.
    • Propagates OAuthSecurityError without swallowing security rejections.

How to Validate

1. Automated Tests

Run the targeted OAuth unit tests covering SSRF attack scenarios, cloud metadata blocking, DNS rebinding, and origin matching:

npm test -w @google/gemini-cli-core -- src/mcp/oauth-utils.test.ts src/mcp/oauth-provider.test.ts src/utils/oauth-flow.test.ts

Run full typecheck and linting:

npm run typecheck
npm run lint

2. Edge Cases Verified:

  • Remote MCP server returning authorization_servers: ["http://127.0.0.1:18080"] -> Throws OAuthSecurityError.
  • Remote MCP server returning resource_metadata="http://169.254.169.254/computeMetadata/v1" -> Throws OAuthSecurityError.
  • Domain name resolving to 127.0.0.1 or 169.254.169.254 (DNS rebinding) -> Throws OAuthSecurityError.
  • WWW-Authenticate header with unquoted resource_metadata=https://example.com/oauth/metadata vs quoted strings -> Both parsed and validated.
  • Local MCP server on http://localhost:3000 -> Loopback permitted, non-loopback private IPs still blocked.

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run

…ation

Enforce RFC 9728 Section 7.7 and RFC 8414 security constraints during MCP
OAuth discovery, dynamic client registration, and token exchange/refresh.

- Enforce HTTPS for remote OAuth endpoints (allow HTTP only for loopback when connecting to local MCP servers)
- Validate origin matching for resource_metadata in WWW-Authenticate challenges
- Block private IPv4/IPv6 address ranges, loopback endpoints from remote servers, link-local / IMDS (169.254.169.254), benchmark ranges (198.18.0.0/15), and multicast/broadcast
- Perform asynchronous DNS resolution to prevent DNS rebinding attacks against internal IP ranges
- Validate dynamic client registration endpoints and token exchange/refresh URLs prior to issuing requests
- Add comprehensive test coverage in oauth-utils.test.ts, oauth-provider.test.ts, and oauth-flow.test.ts
@josebalius josebalius self-assigned this Aug 25, 2026
@josebalius
josebalius requested a review from a team as a code owner August 25, 2026 15:32
@github-actions github-actions Bot added the size/l A large sized PR label Aug 25, 2026
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

📊 PR Size: size/L

  • Lines changed: 673
  • Additions: +643
  • Deletions: -30
  • Files changed: 7

@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 significantly hardens the security of the Model Context Protocol (MCP) OAuth implementation by introducing robust defenses against Server-Side Request Forgery (SSRF). By enforcing strict validation on all OAuth-related endpoints and implementing DNS rebinding protections, the changes ensure that remote MCP servers cannot force the client to interact with sensitive internal network resources or cloud metadata services.

Highlights

  • SSRF Prevention: Implemented strict URL validation and network boundary enforcement to block private IP ranges, loopback addresses, and cloud metadata services (IMDS) during OAuth flows.
  • DNS Rebinding Protection: Added asynchronous DNS resolution to validate hostnames against internal network boundaries before issuing HTTP requests.
  • RFC Compliance: Enforced security constraints for MCP OAuth discovery, dynamic client registration, and token exchange in accordance with RFC 9728 Section 7.7 and RFC 8414.
  • Comprehensive Testing: Added extensive unit tests to verify SSRF attack scenarios, origin matching, and loopback handling across OAuth utilities and providers.
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 commented Aug 25, 2026

Copy link
Copy Markdown

Size Change: +6.56 kB (+0.02%)

Total Size: 35.3 MB

Filename Size Change
./bundle/chunk-46KUKGG2.js 0 B -13 kB (removed) 🏆
./bundle/chunk-5H6K2ISS.js 0 B -19.5 kB (removed) 🏆
./bundle/chunk-6HA43V75.js 0 B -3.65 MB (removed) 🏆
./bundle/chunk-7JZM6SAX.js 0 B -3.77 kB (removed) 🏆
./bundle/chunk-7WDFL6OC.js 0 B -49.2 kB (removed) 🏆
./bundle/chunk-AWITP7WW.js 0 B -3.43 kB (removed) 🏆
./bundle/chunk-MFFSY3GL.js 0 B -661 kB (removed) 🏆
./bundle/chunk-MJR2XUNM.js 0 B -16.6 MB (removed) 🏆
./bundle/core-KHZRC3JS.js 0 B -50.9 kB (removed) 🏆
./bundle/devtoolsService-Z2HAIZ5S.js 0 B -147 kB (removed) 🏆
./bundle/gemini-SLJYMTU2.js 0 B -624 kB (removed) 🏆
./bundle/interactiveCli-JPIHACVR.js 0 B -1.31 MB (removed) 🏆
./bundle/liteRtServerManager-QP7SOWQO.js 0 B -2.08 kB (removed) 🏆
./bundle/oauth2-provider-OYFMEZPJ.js 0 B -9.12 kB (removed) 🏆
./bundle/chunk-3EGGNGDL.js 3.43 kB +3.43 kB (new file) 🆕
./bundle/chunk-6KYOG5NT.js 3.77 kB +3.77 kB (new file) 🆕
./bundle/chunk-74XIFNXZ.js 13 kB +13 kB (new file) 🆕
./bundle/chunk-JSRNPSOR.js 3.65 MB +3.65 MB (new file) 🆕
./bundle/chunk-MRN4BPPH.js 16.6 MB +16.6 MB (new file) 🆕
./bundle/chunk-OT2GIJYU.js 661 kB +661 kB (new file) 🆕
./bundle/chunk-PBDGZG3B.js 49.2 kB +49.2 kB (new file) 🆕
./bundle/chunk-PBVHWFII.js 19.5 kB +19.5 kB (new file) 🆕
./bundle/core-2W6U5PGM.js 51 kB +51 kB (new file) 🆕
./bundle/devtoolsService-UQQEYCDC.js 147 kB +147 kB (new file) 🆕
./bundle/gemini-EDXVFIEW.js 624 kB +624 kB (new file) 🆕
./bundle/interactiveCli-QINZ33HQ.js 1.31 MB +1.31 MB (new file) 🆕
./bundle/liteRtServerManager-3UZRRNSZ.js 2.08 kB +2.08 kB (new file) 🆕
./bundle/oauth2-provider-FCFVXPXY.js 9.12 kB +9.12 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size Change
./bundle/bundled/third_party/index.js 8 MB 0 B
./bundle/chunk-34MYV7JD.js 2.45 kB 0 B
./bundle/chunk-5AUYMPVF.js 858 B 0 B
./bundle/chunk-5PS3AYFU.js 1.18 kB 0 B
./bundle/chunk-664ZODQF.js 124 kB 0 B
./bundle/chunk-DAHVX5MI.js 206 kB 0 B
./bundle/chunk-IUUIT4SU.js 56.5 kB 0 B
./bundle/chunk-L5V3KIDT.js 1.62 kB 0 B
./bundle/chunk-TUDYL3X4.js 40.3 kB 0 B
./bundle/cleanup-I6TOQVJQ.js 0 B -902 B (removed) 🏆
./bundle/devtools-TYCPOPV3.js 683 kB 0 B
./bundle/events-XB7DADIJ.js 418 B 0 B
./bundle/examples/hooks/scripts/on-start.js 188 B 0 B
./bundle/examples/mcp-server/example.js 1.43 kB 0 B
./bundle/gemini.js 5.38 kB 0 B
./bundle/getMachineId-bsd-TXG52NKR.js 1.55 kB 0 B
./bundle/getMachineId-darwin-7OE4DDZ6.js 1.55 kB 0 B
./bundle/getMachineId-linux-SHIFKOOX.js 1.34 kB 0 B
./bundle/getMachineId-unsupported-5U5DOEYY.js 1.06 kB 0 B
./bundle/getMachineId-win-6KLLGOI4.js 1.72 kB 0 B
./bundle/https-proxy-agent-AVGR4LHR.js 490 B 0 B
./bundle/multipart-parser-E7RMVJWU.js 11.7 kB 0 B
./bundle/multipart-parser-KPBZEGQU.js 11.7 kB 0 B
./bundle/sandbox-macos-permissive-open.sb 7.17 kB 0 B
./bundle/sandbox-macos-permissive-proxied.sb 7.46 kB 0 B
./bundle/sandbox-macos-restrictive-open.sb 5.17 kB 0 B
./bundle/sandbox-macos-restrictive-proxied.sb 5.38 kB 0 B
./bundle/sandbox-macos-strict-open.sb 6.63 kB 0 B
./bundle/sandbox-macos-strict-proxied.sb 6.84 kB 0 B
./bundle/src-65GKNWUJ.js 45.4 kB 0 B
./bundle/src-U45KTUYT.js 45.7 kB 0 B
./bundle/src-XZYPU6PJ.js 352 kB 0 B
./bundle/start-MHTQDLPB.js 0 B -622 B (removed) 🏆
./bundle/tree-sitter-7U6MW5PS.js 274 kB 0 B
./bundle/tree-sitter-bash-34ZGLXVX.js 1.84 MB 0 B
./bundle/worker/worker-entry.js 363 kB 0 B
./bundle/cleanup-3O3UVEGZ.js 902 B +902 B (new file) 🆕
./bundle/start-LWBKMGII.js 622 B +622 B (new file) 🆕

compressed-size-action

@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 protections and URL security validations for OAuth endpoints per RFC 9728 Section 7.7, including DNS resolution checks to block private IP addresses. Feedback focuses on critical security issues where remote MCP servers could bypass loopback restrictions due to incorrect allowLoopback evaluation logic during client registration, metadata discovery, and token exchange. Additionally, the reviewer noted that asynchronous DNS lookups alone do not fully prevent DNS rebinding attacks and suggested pinning resolved IPs or using a custom agent.

Note: Security Review did not run due to the size of the PR.

Comment thread packages/core/src/mcp/oauth-provider.ts Outdated
Comment thread packages/core/src/mcp/oauth-provider.ts Outdated
Comment thread packages/core/src/mcp/oauth-provider.ts Outdated
Comment thread packages/core/src/mcp/oauth-utils.ts
@gemini-cli gemini-cli Bot added the status/need-issue Pull requests that need to have an associated issue. label Aug 25, 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 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