Skip to content

[Bug]: RFC 2544 benchmark range (198.18.0.0/15) misclassified as private, breaking TUN proxy users #3777

@imbytecat

Description

@imbytecat

Bug Summary

SSRF protection incorrectly blocks public websites when DNS resolves to Fake-IP addresses in the RFC 2544 Benchmark range (198.18.0.0/15), commonly used by modern proxy software (Clash, Mihomo, Sing-box, Surge) in TUN/TProxy mode.

Similar issues in other projects:

Reproduction Steps

  1. Configure proxy software with TUN mode + Fake-IP enabled:
    • Clash/Mihomo: tun.enable: true, dns.enhanced-mode: fake-ip
    • Clash Verge/Sing-box/Surge: similar TUN/Fake-IP settings
  2. Start Hermes Gateway
  3. Try to access any public website:
    browser_navigate(url="https://www.baidu.com")
    # or web_extract, web_search, browser_vision, etc.
  4. Error: Blocked: URL targets a private or internal address

Expected Behavior

Public websites should be accessible even when DNS returns Fake-IP addresses (198.18.x.x). These virtual IPs are only local routing constructs - the actual HTTP traffic is forwarded through the proxy tunnel to real public destinations.

Actual Behavior

All URL-accessing tools fail:

Blocked: URL targets a private or internal address

Root Cause Analysis

1. Fake-IP Mechanism (TUN/TProxy Mode)

Modern proxy clients use Fake-IP to optimize DNS resolution:

  • DNS query → Proxy intercepts → Returns virtual IP from 198.18.0.0/15
  • HTTP request to 198.18.x.x → TUN interface captures → Proxy forwards to real destination
  • This avoids real DNS round-trips until actual connection is needed

2. DNS Resolution with Fake-IP

Domain Fake-IP Assigned Real Destination
www.baidu.com 198.18.4.114 Public (via proxy)
google.com 198.18.2.84 Public (via proxy)
github.com 198.18.0.131 Public (via proxy)

3. Python ipaddress Misclassification

import ipaddress
ip = ipaddress.ip_address("198.18.4.114")
print(ip.is_private)   # True  ❌ Incorrect
print(ip.is_reserved)  # False
print(ip.is_global)   # False

RFC 2544 defines 198.18.0.0/15 as Benchmarking Test Range, not a private network. Python's ipaddress module incorrectly classifies it as is_private=True.

4. Affected Code

tools/url_safety.py line 40:

if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
    return True  # Blocks Fake-IP addresses

Why This Matters

Popularity of Fake-IP:

  • Clash/Mihomo (millions of users, especially in China)
  • Sing-box (growing adoption globally)
  • Clash Verge Rev / Clash Nyanpasu (popular GUI clients)
  • Surge (macOS/iOS premium users)
  • All use 198.18.0.0/15 as default Fake-IP pool in TUN mode

Security Consideration:
Simply allowing 198.18.0.0/15 globally might seem risky, but:

  1. These IPs are non-routable - only meaningful to local TUN interface
  2. Traffic to 198.18.x.x must go through proxy software
  3. Without this fix, Hermes is completely unusable for all TUN mode users

Proposed Solutions

Solution 1: Config Option (Recommended - similar to OpenClaw #51407)

Add security.ssrfPolicy.allowRfc2544BenchmarkRange config option:

security:
  ssrfPolicy:
    allowRfc2544BenchmarkRange: true  # Allow 198.18.0.0/15 Fake-IP range

Code change in url_safety.py:

# RFC 2544 Benchmarking Test Range (used by Fake-IP implementations)
_RFC2544_BENCHMARK = ipaddress.ip_network("198.18.0.0/15")

def _is_blocked_ip(ip, allow_rfc2544: bool = False) -> bool:
    """Return True if the IP should be blocked for SSRF protection."""
    # If config allows RFC 2544, skip blocking for this range
    if allow_rfc2544 and ip in _RFC2544_BENCHMARK:
        return False
    
    if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
        return True
    if ip.is_multicast or ip.is_unspecified:
        return True
    if ip in _CGNAT_NETWORK:
        return True
    return False

Solution 2: Auto-detect Proxy Environment (Option 3 from OpenClaw)

If HTTP_PROXY/HTTPS_PROXY/ALL_PROXY is set, or if resolved IP is in 198.18.0.0/15 specifically, skip the block or pass through to the proxy. This is safest as it only activates when proxy is present.

Solution 3: Connection-Level Verification (Future Enhancement)

Use connection-level validation (e.g., Champion library or egress proxy) to verify actual connection target after proxy routing, as noted in current code TODO comments about TOCTOU issues.

Environment

  • Hermes Version: v0.3.0 (v2026.3.17)
  • Python Version: 3.11.15
  • OS: macOS / Linux / Windows
  • Network: Any proxy software with TUN/TProxy + Fake-IP mode:
    • Clash / Mihomo / Clash.Meta
    • Sing-box
    • Clash Verge Rev / Clash Nyanpasu
    • Surge

Workarounds (Until Fixed)

  1. Disable TUN mode: Use HTTP/HTTPS proxy instead (HTTP_PROXY env)
  2. Change Fake-IP range: Configure proxy to use non-conflicting range (e.g., 28.0.0.0/8 in some proxies)
  3. Disable Fake-IP: Use real-ip mode in TUN (performance penalty)

References

Willingness to Contribute

  • I can provide more debugging information and packet captures
  • I can provide a fix (based on OpenClaw's solution)
  • I can test the fix in various proxy configurations (Clash, Sing-box, etc.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsarea/configConfig system, migrations, profilescomp/gatewayGateway runner, session dispatch, deliverytype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions