Files
hermes-agent/hermes_cli
AYin-Z 0f870069ea fix(url_safety): dial a declared local-proxy fake-ip block instead of blocking it
A TUN proxy in fake-ip mode (Mihomo/Clash `fake-ip`, Surge enhanced) answers DNS with an
address from its own block — `198.18.0.0/15` by default — for every name outside its filter.
The SSRF guard resolves names with the local resolver and reads that sentinel as a private
destination, so on such a host every fetch fails before the request leaves the machine:
`web_extract`, gateway media downloads (`is_safe_url` has ~20 call sites, including the
Feishu/WeCom/Telegram/Slack/Discord attachment paths) and the browser relay all report
"URL targets a private or internal network address".

The existing hostname allowlist (`_TRUSTED_PRIVATE_IP_HOSTS`, the QQ multimedia case) does not
generalise to this: the sentinel is a property of the host's resolver, not of one name.

Fix: `security.fake_ip_ranges` declares the CIDR blocks the local proxy owns. Answers inside a
declared block are dialable even with private-IP blocking on. Empty by default, so no existing
host changes behaviour. Loopback, RFC 1918, link-local, CGNAT and the cloud-metadata floor are
untouched. Pre-flight and connect-time checks both go through `_resolved_ip_block_reason`, so one
exemption covers the class instead of the ~20 call sites.

Tests: `TestDeclaredFakeIpSentinelRanges` in tests/tools/test_url_safety.py — red on the unfixed
module (declared sentinel rejected, "Blocked request to private/internal address during connect:
example.com -> 198.18.0.55"), green after. The pre-existing benchmark/QQ hostname tests are
unchanged and still pass (66 passed).

Docs: website/docs/user-guide/security.md + zh-Hans translation. The key is registered in
hermes_cli/config_defaults.py so `hermes config set security.fake_ip_ranges` is not flagged as
unknown.
2026-09-16 17:12:11 -07:00
..
…
…
…
…
…
…
…
…