Skip to content

feat(fetch): add SSRF protection and comprehensive security test suite#3180

Open
Tomo1912 wants to merge 2 commits intomodelcontextprotocol:mainfrom
Tomo1912:feat/security-hardening
Open

feat(fetch): add SSRF protection and comprehensive security test suite#3180
Tomo1912 wants to merge 2 commits intomodelcontextprotocol:mainfrom
Tomo1912:feat/security-hardening

Conversation

@Tomo1912
Copy link

@Tomo1912 Tomo1912 commented Jan 5, 2026

Summary

This PR adds Server-Side Request Forgery (SSRF) protection and a comprehensive security test suite to the fetch MCP server.

Security Features Added

SSRF Protection

  • URL scheme validation (only http/https allowed)
  • Private IP range blocking (10.x, 172.16-31.x, 192.168.x, 127.x, etc.)
  • IPv6 private address blocking (::1, fe80::, fc00::, etc.)
  • Dangerous hostname blocking (localhost, metadata services, etc.)
  • DNS resolution validation to prevent DNS rebinding
  • Configurable via MCP_FETCH_ALLOW_PRIVATE_IPS env var
  • Whitelist support via MCP_FETCH_ALLOWED_PRIVATE_HOSTS

SSL Configuration

  • Configurable SSL verification via MCP_FETCH_SSL_VERIFY env var
  • Comprehensive SSL error handling with helpful messages

Test Suite (89 tests)

  • SSRF protection tests
  • Private IP blocking tests
  • Input validation tests
  • URL scheme validation tests
  • Integration tests
  • Edge case tests

Configuration

# Disable SSL verification for self-signed certs
export MCP_FETCH_SSL_VERIFY=false

# Allow private IPs (use with caution)
export MCP_FETCH_ALLOW_PRIVATE_IPS=true

# Whitelist specific internal hosts
export MCP_FETCH_ALLOWED_PRIVATE_HOSTS=internal.company.com,api.local

Server Details

  • Server: fetch
  • Changes to: Security (SSRF protection, SSL config), tests

Motivation and Context

The fetch server can be exploited for SSRF attacks, allowing malicious actors to access internal services (cloud metadata endpoints, internal APIs, etc.). This PR adds comprehensive protection while maintaining flexibility for legitimate internal use cases through configuration options.

How Has This Been Tested?

  • 89 security tests pass locally
  • Tested with pyright (0 errors)
  • CI pipeline passes

Breaking Changes

None. All protections are backward compatible. Private IPs can be enabled via env var if needed.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Protocol Documentation
  • My changes follows MCP security best practices
  • I have updated the server's README accordingly
  • I have tested this with an LLM client
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have documented all environment variables and configuration options

Additional context

This PR builds on #3179 which adds SSL verification configuration.

@Tomo1912 Tomo1912 force-pushed the feat/security-hardening branch 3 times, most recently from d0c9333 to 2892ae1 Compare January 8, 2026 20:53
@Tomo1912 Tomo1912 force-pushed the feat/security-hardening branch from 7e6a302 to 345a570 Compare January 19, 2026 22:19
@Tomo1912 Tomo1912 force-pushed the feat/security-hardening branch from 0e0769e to a4ce0fc Compare January 27, 2026 12:29
@Tomo1912 Tomo1912 force-pushed the feat/security-hardening branch 3 times, most recently from 2a2c923 to 7aabbb4 Compare February 4, 2026 18:08
@lucamorettibuilds
Copy link

This is an excellent and comprehensive approach to SSRF protection! The layered defense strategy and extensive test coverage show a deep understanding of the security requirements.

What's done really well:

  1. Defense in depth - URL validation → DNS resolution → IP blocking is the correct order of operations
  2. IPv6 coverage - Many SSRF implementations forget link-local (fe80::) and ULA (fc00::) ranges
  3. Cloud metadata protection - Blocking 169.254.169.254 prevents the classic AWS metadata service attack
  4. Escape hatch design - The whitelist approach (MCP_FETCH_ALLOWED_PRIVATE_HOSTS) is much better than a global override
  5. 89 tests - This gives real confidence the edge cases are covered

Security considerations:

⚠️ DNS rebinding timing - The current approach validates the resolved IP before making the request, but there's a TOCTOU (time-of-check-time-of-use) window. An attacker-controlled DNS server could:

  1. Return a public IP during the validation check
  2. Change to a private IP between validation and the actual fetch

Mitigation options:

  • Use a custom DNS resolver that caches the resolved IP and passes it directly to the HTTP client
  • Or: Re-validate the IP immediately before connection (check if Python's requests library allows IP pinning)
  • Or: Document this as a known limitation with the recommendation to use network-level SSRF protection (firewall rules, egress filtering)

🔒 Redirect handling - Does the fetch server follow HTTP redirects? If yes, an attacker could bypass SSRF protection:

https://evil.com/redirect → http://169.254.169.254/latest/meta-data/

Should validate every URL in the redirect chain, or disable redirects entirely.

📋 Private IP bypass with octal/hex encoding - Does the IP parser handle these edge cases?

  • http://0x7f.0.0.1 (127.0.0.1 in hex)
  • http://2130706433 (127.0.0.1 as decimal integer)
  • http://017700000001 (127.0.0.1 in octal)

Python's ipaddress library should handle these, but worth testing.

Recommendation:

This is production-ready for most use cases. For maximum security in high-risk environments, I'd suggest:

  1. Checking redirect handling behavior
  2. Adding a note in the README about the DNS rebinding timing window
  3. Recommending network-level egress controls as defense-in-depth

Really strong work on this — the 89-test suite and comprehensive IP range coverage are exemplary!

@Tomo1912 Tomo1912 force-pushed the feat/security-hardening branch from c51e6d7 to 4b71477 Compare February 12, 2026 17:12
This PR adds Server-Side Request Forgery (SSRF) protection and a comprehensive
security test suite to the fetch MCP server.

- URL scheme validation (only http/https allowed)
- Private IP range blocking (10.x, 172.16-31.x, 192.168.x, 127.x, etc.)
- IPv6 private address blocking (::1, fe80::, fc00::, etc.)
- Dangerous hostname blocking (localhost, metadata services, etc.)
- DNS resolution validation to prevent DNS rebinding
- Configurable via MCP_FETCH_ALLOW_PRIVATE_IPS env var
- Whitelist support via MCP_FETCH_ALLOWED_PRIVATE_HOSTS

- Configurable SSL verification via MCP_FETCH_SSL_VERIFY env var
- Comprehensive SSL error handling with helpful messages

- SSRF protection tests
- Private IP blocking tests
- Input validation tests
- URL scheme validation tests
- Integration tests
- Edge case tests

```bash
export MCP_FETCH_SSL_VERIFY=false

export MCP_FETCH_ALLOW_PRIVATE_IPS=true

export MCP_FETCH_ALLOWED_PRIVATE_HOSTS=internal.company.com,api.local
```

fix: address security review feedback

- Disable follow_redirects to prevent SSRF bypass via open redirects
- Add explicit IP obfuscation detection (decimal/octal/hex formats)
- Fix SSL parsing to be fail-secure (only 'false' disables verification)
- Clean up test headers (remove enterprise roleplay language)
- Add comprehensive tests for IP obfuscation parsing

fix: add octal integer IP parsing and fix test naming

- Add octal integer format parsing (017700000001 = 127.0.0.1)
- Rename SSL test to reflect fail-secure behavior (stays_enabled, not defaults_to_false)
- Add tests for octal integer IP obfuscation
@Tomo1912 Tomo1912 force-pushed the feat/security-hardening branch 2 times, most recently from 30fb221 to 87391aa Compare February 12, 2026 17:34
Address review feedback on SSRF protection:

- Add SSRFSafeTransport custom async transport that resolves DNS,
  validates the resolved IP, and replaces the hostname with the
  validated IP before connecting. This eliminates the TOCTOU window
  between validate_url_for_ssrf() and the actual HTTP request.
- Integrate SSRFSafeTransport into fetch_url() and
  check_may_autonomously_fetch_url() replacing direct AsyncClient usage.
- Add 6 DNS rebinding tests including full attack scenario simulation.
- Update existing tests to match new transport-based architecture.
@Tomo1912 Tomo1912 force-pushed the feat/security-hardening branch from 87391aa to 217addd Compare February 12, 2026 17:43
@Tomo1912
Copy link
Author

@lucamorettibuilds

Thanks for the thorough review! I've addressed all three points:

  1. Redirect handling: Already covered: both fetch_url() and check_may_autonomously_fetch_url() use follow_redirects=False, so httpx won't follow redirects to internal endpoints like http://169.254.169.254/.

  2. Octal/hex IP encoding: Already covered: _parse_obfuscated_ip() handles decimal (2130706433), octal (017700000001), hex (0x7f000001), and mixed dotted formats (0x7f.0.0.1). All are tested in TestIPObfuscationParsing and TestSSRFProtection::test_blocks_ip_obfuscation.

  3. DNS rebinding TOCTOU: Fixed. Added SSRFSafeTransport, a custom AsyncBaseTransport wrapper that:

Resolves DNS at connection time
Validates the resolved IP against private/reserved ranges
Replaces the hostname in the URL with the validated IP (preserving the Host header)
Eliminates the TOCTOU window between validate_url_for_ssrf() and the actual HTTP connection
Added 6 new tests in TestDNSRebindingProtection, including a full attack scenario simulation where DNS returns a public IP during validation but a private IP at connection time.

All 115 tests passing. Appreciate the feedback, the DNS rebinding point was a great catch.

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