Modal
A Modal function is a container that exists for seconds and then is gone — which is exactly why it's a bad citizen: its egress IP is whatever Modal's pool hands it, shared, unpinnable, unexplainable.
Whisper gives that ephemeral container a real, routable, per-agent IPv6 identity for the seconds it's alive — and because a Modal function is a full Linux container with an unrestricted outbound stack, not a request-scoped edge isolate, its outbound traffic can leave from that address too, by pointing requests at the Whisper egress proxy.
The problem: ephemeral compute, no address to give anyone
Serverless containers are built to be forgotten. That's the point for compute, and the opposite of what you want for identity: a data provider rate-limiting by IP sees a shared, rotating Modal pool address and either blocks it outright or can't tell your job apart from a neighbor's; a compliance audit asking "which of our workloads touched this vendor" has nothing to grep; and an agent making an HTTP call from inside a Modal function has no way to prove to the far end who it is, because "I'm a Modal container" isn't an identity — it's an infrastructure fact. The fix isn't a bigger allowlist of Modal's CIDR blocks (they change, and everyone else's workload shares them). It's giving the function its own address, one that DNS, RDAP, and DANE all agree on, for exactly as long as it needs it.
Two things fall out of that, split into two files along the honest trust boundary — verify.py needs nothing, egress.py needs your key:
verify.py— no key, no account. Ask "is this a real Whisper agent?" about anything the function is about to call or receive a call from.egress.py— your agent's egress bearer, via a Modal Secret. The container routes its own outbound calls through the Whisper egress proxy so they leave from the agent's/128— Tier 1.5, source-bound to that address.
Source: github.com/whisper-sec/whisper-examples/modal.
Keyless: verify.py
Nothing here needs a Modal Secret, a Whisper account, or even the whisper CLI installed — it's one HTTPS GET to a public endpoint, so it works from inside any sandboxed container that can reach the internet.
With stock tools
If you don't want a dependency at all, curl from inside the container does the whole job — the endpoint is plain JSON over HTTPS:
import subprocess, json
def verify_stock(addr: str) -> bool:
out = subprocess.run(
["curl", "-s", f"https://rdap.whisper.online/verify-identity?ip={addr}"],
capture_output=True, text=True, timeout=10,
).stdout
return json.loads(out or "{}").get("is_whisper_agent", False)
verify_stock("2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4") # -> True
Or from your own shell against the same demo agent, no Modal involved at all:
curl -s https://rdap.whisper.online/verify-identity?ip=2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 | jq
# {"is_whisper_agent":true,"fqdn":"acef2002a323d40d4.<tenant>.agents.whisper.online",
# "dane_ok":true,"jws_ok":true,"evidence":{...}}
With Whisper
pip install whisper-id gives you verify() / rdap() as plain functions — no CLI required for this call, because the keyless surface is one HTTPS GET under the hood:
import json
import modal
image = modal.Image.debian_slim().pip_install("whisper-id")
app = modal.App("whisper-verify", image=image)
@app.function()
def check_identity(addr: str) -> dict:
import whisper_id
ok = whisper_id.verify(addr) # True/False, never raises on a negative verdict
return {"address": addr,
"is_whisper_agent": ok,
"rdap": whisper_id.rdap(addr) if ok else None}
@app.local_entrypoint()
def main(addr: str = "2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"):
print(json.dumps(check_identity.remote(addr), indent=2)) # is_whisper_agent: true
modal run verify.py --addr 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
verify() re-runs the full server-side chain — reverse DNS, forward DNS, the DANE TLSA 3 1 1 b653a4ef…fcb82d1d pin under DNSSEC, and the JWS-signed identity document — and collapses it to one boolean; the underlying dane_ok/jws_ok/evidence fields are right there in the /verify-identity JSON (and rdap() returns the full IP-anchored RDAP record) when you need to log why, not just whether. See Verify an agent and DANE & TLSA for the full mechanism this call is standing on.
Keyed: egress.py — the function gets a routable /128
This is the half that pays off Modal's one advantage over the edge runtimes: a Modal function is a full Linux container with an unrestricted outbound network stack, so it can point requests at an arbitrary proxy — one of the few serverless runtimes where outbound traffic can actually leave from the agent's own address, not just where identity can be checked.
The egress bearer is a per-agent proxy URL — https://w:<egress-bearer>@connect.whisper.online:443 — issued for your agent by the control plane (see Connect & egress for how to mint one). It lives only in a Modal Secret, never in code:
modal secret create whisper-egress \
WHISPER_PROXY_URL='https://w:<egress-bearer>@connect.whisper.online:443' \
WHISPER_AGENT_128='2a04:2a01:...' # optional: assert the observed IP equals your /128
import json, os
import modal
image = modal.Image.debian_slim().pip_install("whisper-id", "requests")
app = modal.App("whisper-egress", image=image)
ECHO_URL = "https://v6.ident.me" # any IPv6 echo works; we verify the result below
@app.function(secrets=[modal.Secret.from_name("whisper-egress")])
def check_egress() -> dict:
import requests, whisper_id
proxy = os.environ["WHISPER_PROXY_URL"] # https://w:<bearer>@connect.whisper.online:443
seen_ip = requests.get(
ECHO_URL, proxies={"http": proxy, "https": proxy}, timeout=30
).text.strip()
expected = os.environ.get("WHISPER_AGENT_128")
return {
"seen_ip": seen_ip,
"is_whisper_agent": whisper_id.verify(seen_ip), # keyless — closes the loop
**({"matches_agent_address": seen_ip == expected} if expected else {}),
}
@app.local_entrypoint()
def main():
print(json.dumps(check_egress.remote(), indent=2))
modal run egress.py
There's no local daemon and no CLI to install: WHISPER_PROXY_URL is a plain HTTP CONNECT-over-TLS proxy (RFC 9110 §9.3.6), which requests/urllib3 (and curl --proxy) speak natively — you just hand it the URL. The far end at connect.whisper.online:443 authenticates the bearer, then binds the container's outbound connection to the agent's /128 via AnyIP + IP_FREEBIND over 2a04:2a01::/32, so packets really do leave carrying that source address — not a header claim layered on top of Modal's own egress IP. This is the exact Tier 1.5 pattern documented in Connect & egress.
Prove it, don't take the SDK's word for it
The same rule applies here as everywhere else in the platform: never trust a client-side claim about its own address — confirm the egress by fetching an echo through the proxy and then verifying that address keyless, because a client can lie about what IP it has but can't forge what the far end actually saw. That's why check_egress above pipes seen_ip straight into whisper_id.verify(). From your own shell the same proof is two commands:
proxy='https://w:<egress-bearer>@connect.whisper.online:443'
seen=$(curl -s --proxy "$proxy" https://v6.ident.me); echo "$seen"
# 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 <- must equal the agent's own /128
curl -s "https://rdap.whisper.online/verify-identity?ip=$seen" | jq .is_whisper_agent
# true
The /verify-identity echo-and-verify loop is the identical keyless proof used by Browserbase and every other Tier 1.5 recipe — no CLI, no secret, callable from any network path.
Governing the agent Modal is running
Because that egress bearer belongs to a real registered agent, everything else in the control plane applies to it: with your API key, whisper-id's policy() / logs() / revoke() work against the same agent from any Python process — a second Modal function or your laptop. revoke(agent) tears down the address, DNS, DANE pin, and egress in one call, provably: re-run modal run verify.py --addr <that /128> afterward and it returns is_whisper_agent: false. See the Python SDK for the full function reference and Egress governance for what policy() can restrict before you ever grant a container the key.
Next: Python SDK · Connect & egress