Quickstart

Put your agents behind Warden in five minutes.

Warden becomes the only way your agents reach the internet. Each request is checked against the sites you approved, keys are added by Warden so the agent never holds them, and every decision is signed into a log nobody can quietly edit.

01Sign inOpen the console with your workspace key (wsk_).
02Add an agentStart it in learn mode. You get a token and copy-paste code.
03Run itNothing breaks. Warden records every site it reaches for.
04Approve and lockOne click approves what it really used and switches on enforcement.

Connect an agent

Pick whichever fits. They all run through the same policy engine and the same log.

MOST CONTROL

SDK

Python or Node, zero dependencies, one file. Full inspection, keys injected, echoes redacted.

FOR LLM AGENTS

One AI tool

Give the model a single web_request tool. Blocks come back as text, so the model learns the boundary.

ONE LINE OF CONFIG

MCP server

Claude Code, Claude Desktop, Cursor or any MCP client gets a web tool that goes through Warden.

ZERO CODE

Proxy mode

Set HTTPS_PROXY. Works with almost any HTTP client or agent framework, unchanged.

ANY LANGUAGE

Raw HTTP

One endpoint, POST /v1/fetch, with a bearer token.

Python SDK

Standard library only, Python 3.8+. Drop the file next to your agent.

curl -O https://warden.ripitlabs.com/sdk/warden.py
from warden import Warden, WardenBlocked

w = Warden("wdn_...")          # or set WARDEN_TOKEN

r = w.get("https://api.stripe.com/v1/balance", secrets=["stripe"])
print(r.status, r.json())

try:
    w.post("https://pastebin.com/api", data="...")
except WardenBlocked as b:
    print(b.kind)              # exfil_attempt, and the agent is now jailed

Check a setup any time with python warden.py doctor. It reads WARDEN_TOKEN, WARDEN_ADMIN_KEY and HTTPS_PROXY and tells you what is wrong in plain words.

Node SDK

Node 18+, Deno or Bun. No packages.

curl -O https://warden.ripitlabs.com/sdk/warden.mjs
import { Warden } from "./warden.mjs";

const w = new Warden("wdn_...");   // or WARDEN_TOKEN
const r = await w.get("https://api.github.com/user", { secrets: ["github"] });
console.log(r.status, r.json());

AI tool (function calling)

Hand the model one web tool. Every call it makes goes through Warden, and it never sees a real key: it asks for one by name.

import anthropic
from warden import Warden

w = Warden("wdn_...")
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "What is our Stripe balance?"}]

while True:
    resp = client.messages.create(model="claude-sonnet-5", max_tokens=2048,
                                  tools=[w.tool_spec()], messages=messages)
    messages.append({"role": "assistant", "content": resp.content})
    calls = [b for b in resp.content if b.type == "tool_use"]
    if not calls:
        break
    messages.append({"role": "user", "content": [
        {"type": "tool_result", "tool_use_id": c.id, "content": w.run_tool(c.input)} for c in calls]})

OpenAI-style tools: w.tool_spec("openai"). Node: w.toolSpec() and await w.runTool(input).

MCP (Claude, Cursor, any MCP client)

The Python SDK file is also an MCP server. Your agent gets two tools: web_request, which goes through Warden, and warden_status, which tells the model which sites it is approved for. Blocks come back as readable tool errors, so the model stops instead of hunting for a way around.

curl -O https://warden.ripitlabs.com/sdk/warden.py

# Claude Code
claude mcp add warden -e WARDEN_TOKEN=wdn_... -- python3 /path/to/warden.py mcp

Claude Desktop, Cursor and other clients take the same thing as JSON:

{
  "mcpServers": {
    "warden": {
      "command": "python3",
      "args": ["/path/to/warden.py", "mcp"],
      "env": { "WARDEN_TOKEN": "wdn_..." }
    }
  }
}
Make it the only way out: the MCP tool only protects traffic that uses it. If the same agent also has a built-in fetch or browser tool, turn that off, or it can go around Warden. See Lock the network.

Agent frameworks

Any framework that takes a Python function as a tool can use Warden. Wrap run_tool, and give the agent this tool instead of its own web access.

LangChain / LangGraph

from langchain_core.tools import tool
from warden import Warden

w = Warden()  # WARDEN_TOKEN from the environment

@tool
def web_request(url: str, method: str = "GET", body: str = "", secrets: list[str] = []) -> str:
    """Make an HTTP request to an approved site. Name stored keys in `secrets`; you never see them."""
    return w.run_tool({"url": url, "method": method, "body": body or None, "secrets": secrets})

OpenAI Agents SDK

from agents import Agent, function_tool
from warden import Warden

w = Warden()

@function_tool
def web_request(url: str, method: str = "GET", body: str | None = None,
                secrets: list[str] | None = None) -> str:
    """Make an HTTP request to an approved site. Name stored keys in `secrets`; you never see them."""
    return w.run_tool({"url": url, "method": method, "body": body, "secrets": secrets or []})

agent = Agent(name="ops-agent", instructions="...", tools=[web_request])

Claude Agent SDK

Use the MCP server, and switch off the built-in tools that reach the network on their own:

from claude_agent_sdk import ClaudeAgentOptions

options = ClaudeAgentOptions(
    mcp_servers={"warden": {"command": "python3", "args": ["/path/to/warden.py", "mcp"],
                            "env": {"WARDEN_TOKEN": "wdn_..."}}},
    disallowed_tools=["WebFetch", "WebSearch", "Bash"],  # Bash could curl out around Warden
)

In every framework, blocks come back to the model as text (BLOCKED by Warden: ...), so it stops instead of crashing or retrying.

Proxy mode

No code changes. Point the agent's standard proxy variables at Warden with its token:

export HTTPS_PROXY="http://warden:wdn_...@<warden-proxy-host>:8131"
export HTTP_PROXY="$HTTPS_PROXY"
python your_agent.py

The console shows the exact proxy address for your workspace. Approved sites, exfiltration jail, internal-address blocking, rate limits, strikes, swarm detection and the kill switch all apply. The kill switch also cuts live connections within about two seconds.

Trade-off: HTTPS through a proxy stays encrypted end to end, so Warden sees the site and the byte counts, not the content. Keys can't be injected on that path and body tripwires don't run. Use the SDK or the AI tool where you need those. Plain HTTP through the proxy gets the full pipeline, including X-Warden-Secrets: name key injection.
Approving a CDN: at the host level Warden can't see which site is behind a shared CDN front. An agent allowed to reach a CDN hostname through the proxy could use it to reach other sites on that CDN. Approve specific API hostnames, not CDN or wildcard hosts, and use the SDK path for anything sensitive.

Raw HTTP

curl -s https://warden.ripitlabs.com/v1/fetch \
  -H "Authorization: Bearer wdn_..." -H "Content-Type: application/json" \
  -d '{"url":"https://api.stripe.com/v1/balance","secrets":["stripe"]}'

Returns {"status", "headers", "body"} (binary responses come back as body_b64). A block returns HTTP 403 or 429 with {"error", "kind"}. Redirects are handed back, never followed.

Learn mode

New agents start in monitor (learn) mode by default in the SDK and console. Warden lets requests through and records every site the agent reached for. In the console, open the agent's Learn panel to see those sites with counts, tick the ones it really needs, and press Approve and enforce. Known exfiltration channels are flagged in red and can't be approved.

Some protections stay on even while learning: internal and cloud-metadata addresses, a real key showing up in a request, size limits and the kill switch.

Keys agents never see

Store a key once in the console (Keys tab) and bind it to the sites it belongs to. The agent passes the key's name; Warden adds the value to the request, only toward those sites, and scrubs it from any response that echoes it back. If the real value ever appears inside an agent's own request, the agent is jailed, because it could only have got it by stealing it.

Decoys

Tick Plant decoys when you add an agent (or press Decoys later). You get fake but realistic keys and an internal-looking URL. Put them in the agent's environment. No real task uses them, so the first use jails the agent. The decoy URL even trips when it is opened from outside your systems, and records who opened it.

Lock the network

Warden only jails traffic that goes through it. An agent that can still open its own connections can route around it. Match the lockdown to the risk:

LevelSetupStops
1. RoutedSDK, AI tool or proxy variables onlyAccidents, prompt-injected misuse, runaway loops. Not an agent that deliberately opens raw sockets.
2. FirewalledHosted Warden plus an egress firewall that allows only warden.ripitlabs.com by name (AWS Network Firewall, GCP FQDN policies, Azure Firewall, or similar)Agents trying to go around Warden.
3. SealedWarden inside your own network; the agent subnet has no other route out. See Self-host.Everything short of a host compromise.

Self-host (Docker)

Run Warden inside your own network, so your API keys and logs never leave it. The same console, SDKs, MCP server and proxy mode work against your instance: point them at it with WARDEN_URL.

The included compose file sets up the sealed layout: your agents sit on a Docker network with no route to the internet, and Warden is the only thing on it that can reach out.

# in the Warden bundle we send you
docker compose -f deploy/docker-compose.yml up -d
docker compose -f deploy/docker-compose.yml logs warden   # first start prints your admin key
# console: http://localhost:8130/console

# agents on the sealed network use
WARDEN_URL=http://warden:8130
# or proxy mode
HTTPS_PROXY=http://warden:<agent token>@warden:8131

Alerts (phone, Slack, email)

Set these under Settings in the console. Jails, decoy use, data-theft attempts and kill-switch use are sent to every channel you set up.

Critical alerts (an agent jailed or killed, a decoy used) have their own budget, so a burst of routine alerts can never crowd them out. If alerts are ever rate-limited, the next one that goes out says how many were held back. Press Send test alert to check your setup.

Team and roles

Give each person their own key under Settings, Team, instead of sharing the workspace key. Every action in the log then records who did it: their name plus their member ID, which can't be faked by choosing a name.

RoleCan
ViewerSee agents, stats, the live feed, keys (names only) and the log.
OperatorEverything a viewer can, plus kill and release agents, stop a fleet, approve learned sites, download evidence, send test alerts.
AdminEverything, including adding agents, storing keys, alert settings and managing people.

Sign in with Google: add the person's Google email when you add them. They can then press "Sign in with Google" on the console instead of pasting a key. Sessions last 12 hours and end the moment you remove the person.

Removing a person stops their key immediately. The workspace admin key can only be rotated with itself, so a person's key can never create an unattributed one.

Send the log to your SIEM

Under Settings, stream every Warden event to Splunk (HTTP Event Collector), Datadog (logs intake) or any HTTPS endpoint. Delivery is at-least-once: if your SIEM is down, events wait and resume where they stopped. Tick "Also send everything already in the log" to backfill history.

Each batch carries an X-Warden-Signature: ed25519=... header: the signature of the exact request body, checkable with the key from GET /v1/ledger/pubkey. Each event also keeps its own chain hash and signature.

The destination must be a public HTTPS address. Self-hosted installs whose SIEM is on the internal network can allow that with WARDEN_STREAM_ALLOW_PRIVATE=1.

Evidence and the log

Every decision is chained to the one before it and signed with Warden's Ed25519 key. The chain head is checkpointed on-chain through ProofRail. From an agent's page you can download a signed incident report: the sites it tried, who used its stolen decoys (IP, country, client), and proof nothing was edited. It is built for abuse desks, takedown requests and law enforcement. Warden never hacks back.

Your ledger export contains your entries in full; other customers' entries appear only as hashes and signatures. That keeps their data private and still lets you verify the whole chain.

API reference

Agent calls use Authorization: Bearer wdn_.... Admin calls use X-Warden-Admin: wsk_....

CallWhat it does
POST /v1/fetchMake a request through Warden: url, method, headers, body or body_b64, secrets (names)
GET /v1/meThe agent's own policy and status
POST /v1/admin/agentsAdd an agent: name, group, allow_hosts, secrets, rate_per_min, mode, decoys. Token shown once.
GET / PATCH / DELETE /v1/admin/agents/{id}Read, edit, retire
POST /v1/admin/agents/{id}/kill /releaseKill switch
POST /v1/admin/groups/{group}/kill /releaseStop a whole fleet
GET /v1/admin/agents/{id}/suggestLearn mode: sites it reached for
POST /v1/admin/agents/{id}/approve{"hosts": [...], "enforce": true}
POST /v1/admin/agents/{id}/honeytokensMint decoys for its environment
POST /v1/admin/agents/{id}/rotate-tokenNew token; the old one stops working
PUT / GET / DELETE /v1/admin/secrets/{name}Vault: value, hosts, header, prefix. Values are never readable.
GET /v1/admin/eventsLive feed (since_seq, agent, kind, limit)
GET /v1/admin/overview24-hour stats
GET /v1/admin/incidents/{id}/reportSigned evidence package
GET /v1/admin/ledger/verify /exportCheck or download the tamper-proof log
PATCH /v1/admin/workspacename, ntfy_topic
GET /v1/ledger/pubkeyWarden's public key, for offline verification

Honest limits