hermes-net-as-cap

star 26

Replace Hermes' SSRF blocklist (resolve-then-connect with documented DNS-rebinding TOCTOU) with a Goblins network capability — a host-allowlist forwarder that mediates every connection at the socket layer. Connection-time validation by construction, not best-effort pre-flight.

plurigrid By plurigrid schedule Updated 6/10/2026

name: hermes-net-as-cap description: Replace Hermes' SSRF blocklist (resolve-then-connect with documented DNS-rebinding TOCTOU) with a Goblins network capability — a host-allowlist forwarder that mediates every connection at the socket layer. Connection-time validation by construction, not best-effort pre-flight. type: bridge parent: hermes-goblins-bridge row: 17 proto: R polarity: -1 status: stub

hermes-net-as-cap

Phase 1. Closes the SSRF surface that Hermes' own docs flag as not-fixable-at-the-pre-flight-level (DNS rebinding, redirect bypass).

Hermes signature

/Users/bob/i/hermes-agent/tools/url_safety.py

def is_safe_url(url: str) -> bool:
    # urlparse → resolve hostname → reject if IP is private/loopback/CGNAT/multicast
    # Documented limits: DNS rebinding TOCTOU, redirect chains
def _is_blocked_ip(ip): ...    # private/loopback/link-local/reserved/multicast/CGNAT

/Users/bob/i/hermes-agent/tools/website_policy.py — host allow/deny lists per skill.

Used by: vision_tools, gateway adapters (telegram/whatsapp/qq), media cache, web tools (Firecrawl/Tavily). Each call site re-validates on each redirect via httpx event hooks. Authority pattern: resolve-then-connect — the resolve and the connect are separate syscalls; an attacker's DNS server can return different answers.

Goblins signature

/Users/bob/i/goblins-adapter/ — a ^net-cap actor parameterised by a host-allowlist (or single-host attenuation), exposing get, post, stream. Validation happens inside the vat at socket-open time, not at URL-parse time.

(define (^net-cap bcom allowlist)
  (methods
    ((get  url . headers)  (mediated-fetch 'GET  url headers))
    ((post url body . hd)  (mediated-fetch 'POST url body hd))
    ((subhost host)
     (if (member host allowlist)
         (spawn ^net-cap (list host))    ; attenuated single-host cap
         (raise 'cap-error)))))

mediated-fetch resolves and connects in one syscall (or via an egress proxy holding the only public-internet socket), so DNS rebinding cannot split them.

Translation table

Hermes call Goblins message Notes
httpx.get(url) (<- net-cap 'get url) cap mediates the connect
is_safe_url(url) → connect (<- net-cap 'get url) check folded into call — no TOCTOU
redirect via httpx event hook (<- net-cap 'follow-redirect new-url) cap re-validates each hop, refuses if outside allowlist
skill-scoped allowlist (<- net-cap 'subhost "api.openai.com") single-host attenuated forwarder

Failure modes (closed by this bridge)

  • DNS rebinding TOCTOU — Hermes resolves, then connects; cap collapses the two into one mediated op (or routes through a single egress proxy that holds the only public socket).
  • Redirect chain to internal host — every hop is a fresh <- call; cap re-checks containment at each hop, not just at request entry.
  • Forgotten is_safe_url callsite — there is no raw httpx.get; if no cap, no call.
  • Skill exfiltrating data via attacker-controlled hostname — cap's allowlist is parameterised at construction; the skill cannot widen it from inside.

Failure modes (introduced; must mitigate)

  • Cap leakage to LLM context — same Mandy nonce-registry remedy as fs-cap (Pattern B in parent SKILL): /object/<base32-id> URL handle, raw cap stays vat-side.
  • Allowlist ossification — when a skill legitimately needs a new host, route through user-approval cap (hermes-approval-as-revocable, row 15) rather than re-deploying the agent.

Test vector

# Must reject:
net.get("http://169.254.169.254/latest/meta-data/")   # AWS metadata → CapError
net.get("http://localhost:8080/admin")                # loopback → CapError
net.get("http://attacker.com")  # where DNS later resolves to 127.0.0.1
                                # → CapError at connect time, not just at parse time
# Redirect-chain attack:
net.get("https://allowed.example/redirect-to-localhost")
# → CapError on the redirect hop, not silently followed

# Must accept:
sub = net.subhost("api.openai.com")
sub.post("https://api.openai.com/v1/chat/completions", body)   # OK
sub.get("https://elsewhere.example/")                          # CapError (sub is attenuated)

Capability diff

Property Hermes (status quo) Goblins (this bridge)
Authority source ambient socket() explicit cap reference
Validation time URL-parse (TOCTOU) socket-open / per-hop
Revocation restart process drop forwarder
Attenuation new allowlist policy + audit subhost returns scoped cap
Redirect handling best-effort hooks mediated by construction
Failure mode forgotten check or DNS race = silent egress no cap = no socket

Test-harness location

~/i/goblins-adapter/tests/net-cap-bisim.scm (todo) — bisim against Python entrypoint shim. Crucial probe: simulated DNS-rebinding adversary should pass-then-fail Hermes but always-fail the cap.

Status: stub

Phase 1 priority. Pairs naturally with hermes-fs-as-cap (row 16) — together they close the two largest ambient-authority surfaces. Egress-proxy variant (Smokescreen-shaped) optional but recommended for defense-in-depth.

Install via CLI
npx skills add https://github.com/plurigrid/asi --skill hermes-net-as-cap
Repository Details
star Stars 26
call_split Forks 8
navigation Branch main
article Path SKILL.md
More from Creator