# 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`](https://github.com/whisper-sec/whisper-examples/tree/main/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:

```python
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:

```bash
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:

```python
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
```

```bash
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](/docs/verify) and [DANE & TLSA](/docs/dane) 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](/docs/connect) for how to mint one). It lives only in a Modal Secret, never in code:

```bash
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
```

```python
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))
```

```bash
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](https://www.rfc-editor.org/rfc/rfc9110#section-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](/docs/connect).

### 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:

```bash
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](/docs/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](/docs/sdk-python) for the full function reference and [Egress governance](/docs/egress-governance) for what `policy()` can restrict before you ever grant a container the key.

---

**Next:** [Python SDK](/docs/sdk-python) · [Connect & egress](/docs/connect)
