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
- 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
- Start Hermes Gateway
- Try to access any public website:
browser_navigate(url="https://www.baidu.com")
# or web_extract, web_search, browser_vision, etc.
- 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:
- These IPs are non-routable - only meaningful to local TUN interface
- Traffic to
198.18.x.x must go through proxy software
- 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)
- Disable TUN mode: Use HTTP/HTTPS proxy instead (
HTTP_PROXY env)
- Change Fake-IP range: Configure proxy to use non-conflicting range (e.g.,
28.0.0.0/8 in some proxies)
- Disable Fake-IP: Use real-ip mode in TUN (performance penalty)
References
Willingness to Contribute
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
tun.enable: true,dns.enhanced-mode: fake-ipBlocked: URL targets a private or internal addressExpected 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:
Root Cause Analysis
1. Fake-IP Mechanism (TUN/TProxy Mode)
Modern proxy clients use Fake-IP to optimize DNS resolution:
198.18.0.0/15198.18.x.x→ TUN interface captures → Proxy forwards to real destination2. DNS Resolution with Fake-IP
3. Python
ipaddressMisclassificationRFC 2544 defines
198.18.0.0/15as Benchmarking Test Range, not a private network. Python'sipaddressmodule incorrectly classifies it asis_private=True.4. Affected Code
tools/url_safety.pyline 40:Why This Matters
Popularity of Fake-IP:
198.18.0.0/15as default Fake-IP pool in TUN modeSecurity Consideration:
Simply allowing
198.18.0.0/15globally might seem risky, but:198.18.x.xmust go through proxy softwareProposed Solutions
Solution 1: Config Option (Recommended - similar to OpenClaw #51407)
Add
security.ssrfPolicy.allowRfc2544BenchmarkRangeconfig option:Code change in
url_safety.py:Solution 2: Auto-detect Proxy Environment (Option 3 from OpenClaw)
If
HTTP_PROXY/HTTPS_PROXY/ALL_PROXYis set, or if resolved IP is in198.18.0.0/15specifically, 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
Workarounds (Until Fixed)
HTTP_PROXYenv)28.0.0.0/8in some proxies)References
ssrfPolicy.allowRfc2544BenchmarkRangeconfig optionWillingness to Contribute