# Cloudflare Workers

**A Worker's `fetch()` leaves on Cloudflare's anycast IP, not your agent's - so the moment your agent lands on the edge, it loses the identity that made it an agent.**

`whisper-edge` gives it back: verify any Whisper identity with zero setup, and egress through a real routable `/128` with one secret - plus a drop-in MCP tool set for the Agents SDK.

## The problem: workerd has no raw egress identity

`workerd` (the Workers runtime) gives you `fetch()` and, behind the `cloudflare:sockets` flag, a raw TCP `connect()` - but no `bind()`, no source-address control, and no UDP, so WireGuard (Whisper's Tier‑1) is off the table entirely. Worse, `cloudflare:sockets`' `startTls()` pins the TLS SNI to the socket you already opened, so you cannot open a TCP connection to a SOCKS5/CONNECT proxy and then negotiate a *second*, nested TLS session to the real HTTPS origin inside it - the standard way a proxy client reaches an `https://` target (RFC 9110 §9.3.6, the `CONNECT` method) simply doesn't compose on workerd. That rules out Whisper's Tier‑1.5 SOCKS5/CONNECT egress for any `https://` target from a Worker.

**With stock tools** - proving the pain, no Whisper involved:

```ts
// A plain Worker, no Whisper. Every outbound request looks like Cloudflare, not your agent.
export default {
  async fetch() {
    const r = await fetch("https://api.ipify.org?format=json");
    return Response.json(await r.json());
    // → {"ip":"104.21.63.10"}  - a Cloudflare anycast address, unrelated to any agent
  },
};
```

There is no `dig`, `curl`, or `ip` binary inside `workerd` to fix this from within - the sandbox has no shell and no raw sockets worth using. The fix has to be an HTTPS-native protocol, which is exactly what Whisper's edge SDK and forward gateway are.

## Keyless tier: verify + RDAP, one `fetch()`

Before you route anything through an agent, you can check whether an address or hostname *is* a real Whisper agent identity - no API key, because this is public data: reverse DNS, a DNSSEC-validated forward-confirm, a DANE `TLSA` pin, and (per `whisper verify --trustless`) a transparency-log inclusion check.

Inside the Worker, `whisper-edge` runs the whole chain - reverse DNS, a DNSSEC-validated forward-confirm, the DANE `TLSA` pin, and a transparency-log check - in one `fetch()`, no resolver library and no crypto:

```ts
import { verify, verifyDetails, rdap } from "whisper-edge";

export default {
  async fetch(request: Request): Promise<Response> {
    const addr = new URL(request.url).searchParams.get("addr");
    if (!addr) return new Response("usage: ?addr={agent-128-or-fqdn}\n", { status: 400 });
    const ok = await verify(addr);                       // boolean
    return Response.json({ address: addr, is_whisper_agent: ok, details: ok ? await verifyDetails(addr) : null });
  },
};
```

`verifyDetails()` returns the individual verdicts (`dane_ok`, `dnssec_ok`, `ptr_ok`, …) so you can log *why* a check failed instead of a bare `false` - see [`sdk-edge`](/docs/sdk-edge) for the full type. The raw records behind those verdicts, and how to re-derive them from a terminal, are in [Verify an agent](/docs/verify).

## Egress: the forward gateway, not raw sockets

Since `cloudflare:sockets` can't nest TLS for a `CONNECT` tunnel, `whisper-edge`'s Cloudflare-egress path uses `transport: "forward"`: one HTTPS request, to `forward.whisper.online/forward`, carrying your API key and the target URL inside the (already-terminated) TLS session - no tunneling required. The gateway dials the real origin itself, sourced from your agent's own `/128` out of `2a04:2a01::/32` (announced by `AS219419`) - the same address binding every Whisper egress tier uses - and streams the response back over the same HTTPS connection it received the request on. One hop, standard HTTP semantics (RFC 9110), works identically on every runtime that only has `fetch()` - which is every edge runtime, not just Workers. Full protocol detail: [`forward-gateway`](/docs/forward-gateway).

**With stock tools** - the gateway is just HTTPS, so you can drive it with `curl` from anywhere to sanity-check a target before wiring it into a Worker:

```bash
curl -s -X POST https://forward.whisper.online/forward \
  -H "Authorization: Basic $(printf 'w:whisper_live_your_key' | base64)" \
  -H "X-Whisper-Target: https://api.ipify.org?format=json"
# → {"ip":"2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"}
#   the reply also carries  X-Whisper-Egress-Source: 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
```

**With Whisper**, the same call from inside a Worker:

```ts
import { agentEgress } from "whisper-edge";

export default {
  async fetch(request: Request, env: { WHISPER_API_KEY: string }): Promise<Response> {
    const egress = await agentEgress(env.WHISPER_API_KEY, "acef2002a323d40d4", { transport: "forward" });
    try {
      const res = await egress.fetch("https://api.ipify.org?format=json");
      return Response.json(await res.json());
      // → {"ip":"2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"} - the agent's own /128, not Cloudflare's edge
    } finally {
      egress.close();
    }
  },
};
```

The full two-tier reference Worker (`?addr=` for keyless verify, `?op=` for the control plane) is in [`whisper-examples/cloudflare/worker.ts`](https://github.com/whisper-sec/whisper-examples/tree/main/cloudflare):

```bash
npm i
wrangler secret put WHISPER_API_KEY   # optional - keyless endpoints work without it
wrangler deploy
```

## Agents SDK: `registerWhisperTools()`

If your Worker *is* an agent - a Cloudflare [Agents SDK](https://developers.cloudflare.com/agents/) `McpAgent` running as a Durable Object - `whisper-examples/cloudflare/agents-sdk` adds Whisper as seven MCP tools with one function call, so the LLM driving the agent gets identity, the security graph, and egress as tool calls, never as code it has to write itself:

```js
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerWhisperTools } from "./whisper-mcp.js";

export class WhisperMcp extends McpAgent {
  server = new McpServer({ name: "whisper", version: "1.0.0" });
  async init() {
    registerWhisperTools(this.server, this.env);   // + your own tools
  }
}
```

| tool | tier | what it does |
|---|---|---|
| `whisper_verify` | keyless | is this IPv6 `/128` or FQDN a real Whisper agent (PTR + DANE + DNSSEC + transparency)? |
| `whisper_rdap` | keyless | the RDAP registration record for an agent `/128` |
| `whisper_assess` | keyless; a key attributes it | threat posture + vendor identity for a host/IP from the security graph (`whisper.assess` + `whisper.identify`); a key attributes the call to you and changes no answer (measured 2026-09-28) |
| `whisper_agents` | keyed | control plane: `register` / `identity` / `list` / `policy` / `logs` / `connect` / `revoke` |
| `whisper_graph_query` | keyed | raw, read-only Cypher against the security graph, values bound as `$-parameters` |
| `whisper_graph_recipe` | keyed | run a named multi-step flow from the graph catalog (typosquat, attack-surface, subdomain-takeover, bgp-hijack-exposure, and the rest) |
| `whisper_egress_fetch` | keyed | fetch a URL sourced from the agent's routable `/128`, via `transport:"forward"` |

The keyed tools return a clear "set `WHISPER_API_KEY`" message rather than a stack trace when no key is configured - the keyless half (`whisper_verify`, `whisper_rdap`, `whisper_assess`) is always fully functional so the tool set is useful even before you provision a key, and `whisper_assess` simply serves rate-limited without one. `wrangler secret put WHISPER_API_KEY` unlocks the other four; the key lives in the Worker's secret store and the model never sees it. Point any MCP client (or another Agent via `this.mcp.connect(url)`) at `https://<your-worker>/mcp` (streamable HTTP) or `/sse`.

> Both example Workers - the plain two-tier fetch handler and the Agents-SDK MCP server - live under [`github.com/whisper-sec/whisper-examples/tree/main/cloudflare`](https://github.com/whisper-sec/whisper-examples/tree/main/cloudflare), proven end-to-end against a provisioned demo agent.

## Next

[`sdk-edge`](/docs/sdk-edge) - the full `whisper-edge` API (`verify`, `resolve`, `rdap`, `control`, `agentEgress`) · [`forward-gateway`](/docs/forward-gateway) - the HTTPS egress protocol behind `transport:"forward"` · [`mcp`](/docs/mcp) - the standalone Whisper MCP server for non-Workers hosts.
