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.
wsk_).Connect an agent
Pick whichever fits. They all run through the same policy engine and the same log.
SDK
Python or Node, zero dependencies, one file. Full inspection, keys injected, echoes redacted.
One AI tool
Give the model a single web_request tool. Blocks come back as text, so the model learns the boundary.
MCP server
Claude Code, Claude Desktop, Cursor or any MCP client gets a web tool that goes through Warden.
Proxy mode
Set HTTPS_PROXY. Works with almost any HTTP client or agent framework, unchanged.
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_..." }
}
}
}
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.
X-Warden-Secrets: name key injection.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:
| Level | Setup | Stops |
|---|---|---|
| 1. Routed | SDK, AI tool or proxy variables only | Accidents, prompt-injected misuse, runaway loops. Not an agent that deliberately opens raw sockets. |
| 2. Firewalled | Hosted 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. Sealed | Warden 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
- Back up the
/datavolume. It holds the database, the log's signing key, the vault encryption key and the admin key. Lose it and stored keys and past proofs are gone. - The container runs as a non-root user with a read-only filesystem, no Linux capabilities, and a single worker (rate limits and live connections are in-process).
- The console is bound to localhost by default. Put TLS in front of it before exposing it to anyone else.
- Self-hosted logs are chained, signed and checkpointed locally. On-chain anchoring of checkpoints is available by connecting to Rip It Labs ProofRail; ask us.
- The self-hosted bundle is available on request: [email protected].
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.
- Phone: install the free ntfy app, subscribe to a hard-to-guess topic name, and enter the same name. Treat the name like a password.
- Slack: paste an incoming-webhook URL (
https://hooks.slack.com/services/...). It is stored encrypted and never shown again. - Email: enter one address. We send a confirmation link first; alerts start once someone clicks it.
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.
| Role | Can |
|---|---|
| Viewer | See agents, stats, the live feed, keys (names only) and the log. |
| Operator | Everything a viewer can, plus kill and release agents, stop a fleet, approve learned sites, download evidence, send test alerts. |
| Admin | Everything, 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_....
| Call | What it does |
|---|---|
POST /v1/fetch | Make a request through Warden: url, method, headers, body or body_b64, secrets (names) |
GET /v1/me | The agent's own policy and status |
POST /v1/admin/agents | Add 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 /release | Kill switch |
POST /v1/admin/groups/{group}/kill /release | Stop a whole fleet |
GET /v1/admin/agents/{id}/suggest | Learn mode: sites it reached for |
POST /v1/admin/agents/{id}/approve | {"hosts": [...], "enforce": true} |
POST /v1/admin/agents/{id}/honeytokens | Mint decoys for its environment |
POST /v1/admin/agents/{id}/rotate-token | New 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/events | Live feed (since_seq, agent, kind, limit) |
GET /v1/admin/overview | 24-hour stats |
GET /v1/admin/incidents/{id}/report | Signed evidence package |
GET /v1/admin/ledger/verify /export | Check or download the tamper-proof log |
PATCH /v1/admin/workspace | name, ntfy_topic |
GET /v1/ledger/pubkey | Warden's public key, for offline verification |
Honest limits
- Warden controls what agents do on the network. It does not read their minds, and it can't stop local actions on a machine they already control.
- Routing without a network lockdown is cooperative. See Lock the network.
- HTTPS in proxy mode is host-level only (no key injection or content checks, and no protection against domain fronting through an approved CDN).
- Warden never attacks back. It builds the case for the people who can act lawfully.