Skip to content

Repository files navigation

🛡️ warden

Verifiable, delegated identity for AI agents

Your agents do tasks for you. warden proves it with cryptography.

License: AGPL v3 Python Standards Status


A note on language. The core sections below use ASD-STE100 Simplified Technical English — short sentences, the active voice, and one idea per sentence. Security infrastructure must be easy to read.

The problem

An AI agent is not a user. An AI agent is not a usual service. An AI agent is a proxy. It is software that does tasks for a person. It frequently operates without supervision. It operates across many tools and other agents.

This condition breaks identity. An agent can send money. An agent can send an email. An agent can call a different agent. For these actions, three questions have no good answer today:

  • Who does the action, and for whom?
  • What permissions does the agent hold?
  • Can you prove the action later — or stop the action before it starts?

A stolen API key cannot answer these questions. warden answers these questions.

What warden is

warden gives an agent a verifiable ID card. The ID card is a signed token. The token proves this statement:

Agent X does tasks for principal Y. The permissions are limited to scope Z. The token is valid until time T.

warden uses proven standards. The ID card is a signed JWT. warden shapes the token with RFC 8693 delegation claims. warden signs the token with Ed25519. A relying party verifies the token offline. warden does not call a central server.

You install and operate warden yourself. Thus the issuer keypair is your root of trust. You become your own trust anchor. This model is the same model as an OIDC provider that you operate.

Each token action emits a structured event. Each event has a version. Thus you, or the security tools that you connect, can see the actions of each agent. You can also see the principal for each action.

   principal (human)              warden issuer                    verifier / any service
        │                              │                                   │
        │  "let agent-007 read email"  │                                   │
        │─────────────────────────────▶│  mint signed ID card             │
        │                              │──────────────────────────────────▶│  verify offline ✔
        │                              │                                   │  → acts for: alice
        │                          emits token.issued              emits token.verified
        │                                    └──────────────┬──────────────┘
        │                                                   ▼
        │                                       your event stream → anomaly detection

60-second quickstart

uv sync

# 1. Become your own trust anchor (generate the issuer keypair)
uv run warden keygen

# 2. Mint an ID card: agent-007 may read email & calendar on Alice's behalf
uv run warden issue --principal alice --agent agent-007 --scope "email:read calendar:read"

# 3. Verify it (offline, against your public key)
uv run warden verify <paste-the-token>
#   VALID
#     principal: alice
#     agent:     agent-007
#     scope:     email:read calendar:read
#     issuer:    warden.local
#     expires:   2026-07-26T18:42:10+00:00

If you change one character, or you wait until after --ttl, verify rejects the token. verify then emits a token.rejected or token.expired event. The event gives the reason.

Delegation chains (agent → sub-agent)

Agents give work to other agents. warden lets an agent delegate a part of its authority. The scope can only become smaller along the chain. The scope can never become larger.

# Alice grants agent A read+send on email
PARENT=$(uv run warden issue --principal alice --agent A --scope "email:read email:send")

# A delegates to sub-agent B — but only email:read (a subset)
CHILD=$(uv run warden delegate "$PARENT" --agent B --scope "email:read")

uv run warden verify "$CHILD"
#   VALID
#     principal: alice          ← still on Alice's behalf, all the way down
#     agent:     B              ← current actor
#     scope:     email:read     ← attenuated

If you try to make the scope larger (--scope "email:read email:send calendar:read"), warden denies the request. warden also denies a cycle (for example A → B → A). warden also denies a chain that is longer than the depth limit. A child token cannot stay valid for longer than its parent token. You can get the full chain — alice → A → B — from any card with card.chain. Each delegation emits a token.delegated event.

As a library

from warden import Issuer, Verifier, EventBus, RequestContext, generate_keypair

private_key, public_key = generate_keypair()

# emit lifecycle events wherever you want them
bus = EventBus()
bus.on("token.issued",   lambda e: print("issued:",   e.jti))
bus.on("token.rejected", lambda e: print("rejected:", e.reason))

issuer   = Issuer(private_key, iss="warden.local", event_bus=bus)
verifier = Verifier(public_key, expected_iss="warden.local", event_bus=bus)

# capture ambient signals for downstream security profiling (opt-in, never resolved by warden)
ctx = RequestContext(source_ip="203.0.113.5", transport="mcp", client_id="agent-007")

token = issuer.issue("alice", "agent-007", ["email:read"], context=ctx)
card  = verifier.verify(token, context=ctx)   # → DelegationCard, or a typed error

Federation — trust across orgs

One deployment is its own trust anchor. Your agent can prove itself to the systems of a different organization. To do this, the other organization trusts your issuer. The other organization uses your public key or your published JWKS. There is no central authority.

from warden import FederatedVerifier

fed = (FederatedVerifier()
       .trust("org-a", org_a_public_key)          # by key
       .trust_jwks("org-b", httpx.get("https://org-b/.well-known/jwks.json").json()))  # by JWKS

card = fed.verify(token)     # picks the right key by the token's `iss`

An attacker can claim a trusted iss value. But the attack fails. warden checks the token against the real key of that issuer. warden rejects an unknown issuer with an untrusted_issuer event.

You can also discover an issuer across the network. trust_discovery reads the issuer configuration and the keys for you. The network call happens only at setup. Verification stays offline.

fed.trust_discovery("https://org-c.example")   # reads /.well-known + JWKS, then trusts

Run it as a service

warden includes an HTTP issuer service. Any service can trust it across the network. The service reads the JWKS from warden. The service then verifies tokens offline. You do not need a shared secret.

uv sync --extra server
uv run warden serve                       # http://127.0.0.1:8080

curl localhost:8080/.well-known/jwks.json                     # the issuer's public keys
curl -X POST localhost:8080/issue    -d '{"principal":"alice","agent":"A","scope":"email:read"}'
curl -X POST localhost:8080/delegate -d '{"parent_token":"...","agent":"B","scope":"email:read"}'
curl -X POST localhost:8080/verify   -d '{"token":"..."}'

You can also run the container (docker build -t warden . && docker run -p 8080:8080 warden). In Python, a relying party trusts the issuer with only its published keys:

import httpx
from warden import Verifier

jwks = httpx.get("http://issuer/.well-known/jwks.json").json()
card = Verifier.from_jwks(jwks, expected_iss="warden.local").verify(token)  # offline

Enforcement — from observe to control

Verification tells you that a card is valid. The gateway decides if a specific action is permitted. The gateway checks the scope of the card against a policy. The gateway can send a dangerous action to a human approver first. The gateway denies an unknown action by default.

from warden import Gateway, Policy, Rule, Verifier

policy = Policy(rules=[
    Rule("email.read", required_scope="email:read"),
    Rule("email.send", required_scope="email:send", require_approval=True),
])
gateway = Gateway(verifier, policy, approver=lambda card, action, ctx: ask_human(card, action))

gateway.authorize(token, "email.read")   # → ALLOW  (scope satisfied)
gateway.authorize(token, "email.send")   # → REQUIRE_APPROVAL, then ALLOW/DENY
gateway.authorize(token, "wire.money")   # → DENY   (no rule → deny by default)

If the approver fails, the gateway denies the action (it fails closed). Each decision emits an action.allowed, action.denied, or action.pending_approval event.

Revocation: give the verifier a RevocationStore. If you revoke a jti, that token fails verification. The direct delegations of that token also fail verification. warden emits a token.revoked event. Across HTTP, POST /revoke (admin only) does the same operation.

The event stream — build security on top

warden captures data and emits data. warden does not classify data. Each action produces an Event. The event contains the card and the request context that you supply (for example the raw source_ip, the transport, and the TLS fingerprint). warden does no GeoIP, ASN, or Tor lookups. warden gives you clean, signed, structured data. Thus you, or any security vendor, can build the detections on top of the data:

You emit Someone builds
identity + source_ip/geo + time impossible travel
identity + asn unseen ASN / network
source_ip Tor exit-node flags
identity + scope + time velocity / scope-escalation anomalies

The event schema has a version and is stable. This schema is the contract for downstream tools. warden also includes example detectors (UnseenASNDetector, TorExitDetector, ImpossibleTravelDetector). These detectors consume the stream. The detectors stay outside the core. They prove the model.

Monitor everything, on whose behalf

In a multi-agent mesh, you can only monitor the data that the agents propagate. warden carries the card (and a delegation trace id) across each hop — HTTP, MCP, A2A, or gRPC. warden uses one helper that works with any carrier. The AuthorityMonitor then rebuilds the full picture:

from warden import AuthorityMonitor, EventBus, Issuer, Verifier, inject, extract

bus = EventBus()
monitor = AuthorityMonitor().attach(bus)          # subscribes to every event
# ... issuer/verifier/gateway built with event_bus=bus ...

# propagate across a hop (agent A -> agent B): put it on the wire, read it off
trace = inject(outgoing_headers, token)           # HTTP headers / MCP metadata / A2A message
token, trace = extract(incoming_headers)

monitor.by_principal("alice")   # everything any agent did on Alice's behalf
monitor.by_trace(trace)         # the whole delegation tree for one request
monitor.denials()               # rejections / expirations / revocations / denied actions
monitor.as_spans()              # OpenTelemetry-style spans → any tracing backend

See it all together — a multi-agent demo

examples/langchain_multi_agent.py is a runnable LangChain multi-agent system on warden. It issues, delegates (and blocks a wider delegation), propagates the card across a hop, enforces every tool call through the gateway, federates to a partner org, revokes the whole chain, and monitors it all. It runs on a small local model (Ollama) or a deterministic fake model with no network:

uv run --group dev python examples/langchain_multi_agent.py

Roadmap

warden is built in phases. See PHASED_PLAN.md for detail.

  • Phase 0 — issue & verify on-behalf-of tokens · event stream · CLI
  • Phase 1 — delegation chains + attenuation (agent → sub-agent, scope only narrows)
  • Phase 2 — HTTP issuer + JWKS endpoint · offline verification by JWKS
  • Phase 3 — enforcement gateway (allow / deny / require-approval) + revocation
  • Phase 4monitoring plane (authority tracing across A2A/MCP) + enrichment plugins
  • Phase 5 — cross-org federation (trust many issuers, select by iss)

Design principles

  • Proven crypto, proven standards. warden uses JWT, Ed25519, RFC 8693, and OIDC. Trust comes from code that you can read.
  • Self-hosted, no central server. Verification is offline. Your keys, your data, your trust anchor.
  • Capture, do not classify. warden produces trustworthy data. Security products build on the data.
  • Simple to audit. If you cannot read the code, you cannot trust the code.

License

AGPL-3.0-or-later. Free to self-host, read, and modify. Building a hosted service on top? Talk to us about a commercial license.

Contributing

See AGENTS.md for setup, tests, architecture, and conventions — written for human and AI contributors alike.

About

Open-source trust anchor for AI agents - prove which agent is acting on whose behalf, cryptographically.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages