Capability tokens for AI agents.
Tenuo is a cryptographic authorization primitive for AI agents. Think prepaid debit card, as opposed to corporate Amex: ephemeral, scoped capability tokens that expire when the task ends.
A warrant is a signed token specifying which tools an agent can call, under what constraints, and for how long. Bound to a cryptographic key, verified offline in ~27μs, and monotonically scoped: delegation can only narrow authority, never expand it. If an agent is prompt-injected, the warrant's bounds still hold.
Status: v0.1 Beta - Core semantics are stable. APIs may evolve. See CHANGELOG.
# Using uv (recommended)
uv pip install tenuo
# Or standard pip
pip install tenuofrom tenuo import configure, SigningKey, mint_sync, guard, Capability, Pattern
from tenuo.exceptions import AuthorizationDenied
# 1. One-time setup: generate a key and configure Tenuo
configure(issuer_key=SigningKey.generate(), dev_mode=True, audit_log=False)
# 2. Protect a function — calls are blocked unless a warrant allows them
@guard(tool="send_email")
def send_email(to: str) -> str:
return f"Sent to {to}"
# 3. Mint a warrant that only allows sending to @company.com
with mint_sync(Capability("send_email", to=Pattern("*@company.com"))):
print(send_email(to="alice@company.com")) # -> "Sent to alice@company.com"
try:
send_email(to="attacker@evil.com")
except AuthorizationDenied:
print("Blocked: attacker@evil.com") # -> "Blocked: attacker@evil.com"The agent can be prompt-injected. The authorization layer doesn't care. The warrant says *@company.com. The request says attacker@evil.com. Denied.
When the mint_sync block exits, the warrant is gone. No cleanup, no revocation — it just expires.
IAM answers "who are you?" Tenuo answers "what can you do right now?"
| Problem | Tenuo's Answer |
|---|---|
| Static IAM roles outlive tasks | Warrants expire with the task (TTL) |
| Broad permissions, big blast radius | Constraints narrow on every delegation |
| Tokens can be stolen and replayed | Proof-of-possession binds warrants to keys |
| Central policy servers add latency | Offline verification, no network calls |
Tenuo implements Subtractive Delegation: each step in the chain can only reduce authority, never expand it.
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Control Plane │ │ Orchestrator │ │ Worker │
│ │ │ │ │ │
│ Issues root │────▶│ Attenuates │────▶│ Executes with │
│ warrant │ │ for task │ │ proof │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Full scope --> Narrower --> Narrowest
- Control plane issues a root warrant with broad capabilities
- Orchestrator attenuates it for a specific task (scope can only shrink)
- Worker proves possession of the bound key and executes
- Warrant expires — no cleanup needed
- Not a sandbox — Tenuo authorizes actions, it doesn't isolate execution. Pair with containers/sandboxes/VMs for defense in depth.
- Not prompt engineering — No "please don't do bad things" instructions. Cryptographic enforcement, not behavioral.
- Not an LLM filter — We don't parse model outputs. We gate tool calls at execution time.
- Not a replacement for IAM — Tenuo complements IAM by adding task-scoped, attenuating capabilities on top of identity.
| Feature | Description |
|---|---|
| Offline verification | No network calls, ~27μs |
| Holder binding | Stolen tokens are useless without the key |
| Semantic constraints | 11 constraint types including Subpath, UrlSafe, Shlex, CEL — they parse inputs the way the target system will (why this matters) |
| Monotonic attenuation | Capabilities only shrink, never expand |
| Framework integrations | OpenAI, Google ADK, CrewAI, Temporal, LangChain, LangGraph, FastAPI, MCP, A2A, AutoGen |
OpenAI — Direct API protection with streaming TOCTOU defense
from tenuo.openai import GuardBuilder, Subpath, UrlSafe, Shlex, Pattern
client = (GuardBuilder(openai.OpenAI())
.allow("read_file", path=Subpath("/data")) # Path traversal protection
.allow("fetch_url", url=UrlSafe()) # SSRF protection
.allow("run_command", cmd=Shlex(allow=["ls"])) # Shell injection protection
.allow("send_email", to=Pattern("*@company.com"))
.build())
# Prompt injection -> send_email(to="attacker@evil.com") -> DENIEDLangChain / LangGraph
from tenuo.langchain import guard_tools
from tenuo.langgraph import TenuoToolNode
protected = guard_tools([search_tool, email_tool]) # LangChain
graph.add_node("tools", TenuoToolNode([search, email])) # LangGraphMCP — Model Context Protocol client and server verification
from tenuo.mcp import SecureMCPClient, MCPVerifier
# Client: Automatically injects warrant proofs into tool arguments
async with SecureMCPClient("python", ["server.py"]) as client:
async with mint(Capability("read_file", path=Subpath("/data"))):
result = await client.tools["read_file"](path="/data/file.txt")
# Server: Verifies tool constraints offline before execution
verifier = MCPVerifier(...)
@mcp.tool()
async def read_file(path: str, **kwargs) -> str:
clean = verifier.verify_or_raise("read_file", {"path": path, **kwargs})
return open(clean["path"]).read()More integrations: Google ADK, CrewAI, A2A, Temporal, FastAPI
Google ADK
from tenuo.google_adk import GuardBuilder
from tenuo.constraints import Subpath, UrlSafe
guard = (GuardBuilder()
.allow("read_file", path=Subpath("/data"))
.allow("web_search", url=UrlSafe(allow_domains=["*.google.com"]))
.build())
agent = Agent(name="assistant", before_tool_callback=guard.before_tool)CrewAI — Multi-agent crews with capability-based authorization
from tenuo.crewai import GuardBuilder
guard = (GuardBuilder()
.allow("search", query=Pattern("*"))
.allow("write_file", path=Subpath("/workspace"))
.build())
crew = guard.protect(my_crew) # All agents get enforced constraintsA2A (Agent-to-Agent) — Warrant-based inter-agent delegation
from tenuo.a2a import A2AServerBuilder
server = A2AServerBuilder().name("Search Agent").url("https://...").key(my_key).trust(orchestrator_key).build()
@server.skill("search", constraints={"url": UrlSafe})
async def search(query: str, url: str) -> dict:
return await do_search(query, url)
client = A2AClient("https://...")
warrant = await client.request_warrant(signing_key=worker_key, capabilities={"search": {}})
result = await client.send_task(skill="search", warrant=warrant, signing_key=worker_key)Temporal -- Durable workflows with warrant-based activity authorization
from tenuo.temporal import (
AuthorizedWorkflow,
TenuoClientInterceptor,
execute_workflow_authorized,
)
@workflow.defn
class MyWorkflow(AuthorizedWorkflow):
@workflow.run
async def run(self, path: str) -> str:
return await self.execute_authorized_activity(
read_file, args=[path], start_to_close_timeout=timedelta(seconds=30),
)
client_interceptor = TenuoClientInterceptor()
result = await execute_workflow_authorized(
client=client,
client_interceptor=client_interceptor,
workflow_run_fn=MyWorkflow.run,
workflow_id="wf-123",
warrant=warrant,
key_id="agent1",
args=["/data/report.txt"],
task_queue="my-queue",
)See full Temporal examples: demo.py | multi_warrant.py | delegation.py
FastAPI — Extracts warrant from headers, verifies PoP offline
@app.get("/search")
async def search(query: str, ctx: SecurityContext = Depends(TenuoGuard("search"))):
return {"results": do_search(query)}Kubernetes — See Kubernetes guide
| Resource | Description |
|---|---|
| Quickstart | Get running in 5 minutes |
| Concepts | Why capability tokens? |
| Constraints | All 11 constraint types explained |
| Security | Threat model and guarantees |
| OpenAI | Direct API protection with streaming |
| Google ADK | ADK agent tool protection |
| AutoGen | AgentChat tool protection |
| A2A | Inter-agent delegation |
| FastAPI | Zero-boilerplate API protection |
| LangChain | Tool protection |
| LangGraph | Multi-agent graph security |
| CrewAI | Multi-agent crew protection |
| Temporal | Durable workflow authorization |
| MCP | Model Context Protocol client + server verification |
| Component | Supported |
|---|---|
| Python | 3.9 – 3.14 |
| Node.js | Coming v0.2 |
| OS | Linux, macOS, Windows |
| Rust | Not required (binary wheels for macOS, Linux, Windows) |
uv pip install tenuo # Core only
uv pip install "tenuo[openai]" # + OpenAI Agents SDK
uv pip install "tenuo[google_adk]" # + Google ADK
uv pip install "tenuo[a2a]" # + A2A (inter-agent delegation)
uv pip install "tenuo[fastapi]" # + FastAPI integration
uv pip install "tenuo[langchain]" # + LangChain (langchain-core ≥0.2)
uv pip install "tenuo[langgraph]" # + LangGraph (includes LangChain)
uv pip install "tenuo[crewai]" # + CrewAI
uv pip install "tenuo[temporal]" # + Temporal workflows
uv pip install "tenuo[autogen]" # + AutoGen AgentChat (Python ≥3.10)
uv pip install "tenuo[mcp]" # + MCP client & server verification (Python ≥3.10)Try the Demo — See the full delegation chain in action:
docker compose upThis runs the orchestrator -> worker -> authorizer demo showing warrant issuance, delegation, and verification.
Official Images on Docker Hub:
docker pull tenuo/authorizer:0.1.0-beta.11 # Sidecar for warrant verification
docker pull tenuo/control:0.1.0-beta.11 # Control plane (demo/reference)Helm Chart:
helm install tenuo-authorizer ./charts/tenuo-authorizer \
--set config.trustedRoots[0]="YOUR_CONTROL_PLANE_PUBLIC_KEY"See Helm chart README and Kubernetes guide.
| Feature | Status |
|---|---|
| A2A integration | Implemented (tenuo[a2a]) |
| AutoGen integration | Implemented (tenuo[autogen]) |
| Google ADK integration | Implemented (tenuo[google_adk]) |
| MCP integration | Implemented (tenuo[mcp]) |
| Warrant guards (human approval) | Implemented (experimental) |
| Revocation (SRL) | Ongoing development |
| TypeScript/Node SDK | Planned for v0.2 |
| Context-aware constraints | Spec under development |
Building a sidecar or gateway? Use the core directly:
[dependencies]
tenuo = "0.1.0-beta.11"See docs.rs/tenuo for Rust API.
Tenuo builds on capability token ideas described in CaMeL (Debenedetti et al., 2025). Inspired by Macaroons, Biscuit, and UCAN.
See Related Work for detailed comparison.
- TLDR InfoSec - "The Map is not the Territory: The Agent-Tool Trust Boundary"
- TLDR InfoSec - "Capabilities Are the Only Way to Secure Agent Delegation"
- Awesome Object Capabilities - Curated list of capability-based security resources
- Awesome LangChain
- Awesome LLM Agent Security
- Awesome LLMSecOps
Tenuo (/tɛn-ju-oʊ/ • Ten-YOO-oh)
From Latin tenuare: "to make thin; to attenuate." Authority starts broad at the root and is attenuated as it flows down the delegation chain.
Contributions welcome. See CONTRIBUTING.md.
We're planning a TypeScript/Node SDK for v0.2. If you're interested in leading or contributing to this effort, open an issue or email us at dev@tenuo.ai.
Security issues: Email security@tenuo.ai with PGP (key, not public issues).
Self-hosted is free forever. For a managed control plane with observability and revocation management, request early access to Tenuo Cloud.
MIT OR Apache-2.0, at your option.