Capability tokens for AI agents.
Tenuo Cloud — Early Access
Managed control plane with revocation, observability, and multi-tenant warrant issuance.
Tenuo is cryptographic authorization infrastructure for AI agents. A useful mental model is a prepaid card instead of a corporate Amex: scoped capability tokens that expire with the task.
A warrant is a signed token specifying which tools an agent can call, under what constraints, and for how long. It is bound to a cryptographic key, and the caller must prove possession of that key (PoP). Verification is offline (~27μs), and delegation attenuates monotonically: authority can narrow but not expand. If an agent is prompt-injected, execution is still limited by warrant constraints.
Tenuo is designed for teams running tool-calling and multi-agent workflows where authorization must hold at runtime, not just at session start.
It can be deployed in-process or at boundary enforcement points (sidecar/gateway), with the same warrant semantics and enforcement behavior.
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"Even if the agent is prompt-injected, enforcement still happens at the tool boundary. The warrant allows *@company.com; attacker@evil.com is denied.
When the mint_sync block exits, the warrant expires naturally. No manual cleanup or revocation flow is required.
30-second architecture:
- Issuer mints a root warrant for a task.
- Delegator attenuates scope for downstream agents/tools.
- Authorizer verifies warrant + PoP at the tool-call boundary.
- Signed receipt records the authorization decision and execution context.
IAM answers "who are you?" Tenuo adds "what can this workload do right now for this task?" That gives teams a deterministic authorization boundary at agent speed.
| Failure mode in agent systems | Tenuo strength | Practical outcome |
|---|---|---|
| Session roles outlive individual tasks | Task-scoped warrants with TTL | Authority disappears when the task ends |
| Delegation chains increase blast radius | Monotonic attenuation at every hop | Scope only narrows, never expands |
| Bearer credentials can be replayed | Holder-bound proofs (PoP) | Stolen warrants are unusable without the key |
| Runtime policy calls add latency and dependency risk | Offline verification (~27μs) | Enforcement holds under load without network round-trips |
| Teams need defensible audit evidence | Signed authorization receipts | Each decision is attributable and reviewable |
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 — Tenuo does not rely on model instructions for security decisions.
- Not an LLM filter — Tenuo gates tool calls at execution time rather than filtering model text.
- 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 — Tool-call enforcement with streaming TOCTOU protection
from tenuo.openai import GuardBuilder, Subpath, UrlSafe, Range, Pattern
client = (GuardBuilder(openai.OpenAI())
.allow("read_file", path=Subpath("/data")) # Path traversal protection
.allow("fetch_url", url=UrlSafe()) # SSRF protection
.allow("transfer", amount=Range(max=1000)) # Value boundary enforcement
.allow("send_email", to=Pattern("*@company.com"))
.build())
# Out-of-scope recipient is denied at execution timeLangChain / 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-side verification
from tenuo.mcp import SecureMCPClient, MCPVerifier
# Client: 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 warrant-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) # Enforces constraints across crew toolsA2A (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 (
TenuoTemporalPlugin, TenuoPluginConfig, EnvKeyResolver,
AuthorizedWorkflow, execute_workflow_authorized,
)
plugin = TenuoTemporalPlugin(
TenuoPluginConfig(key_resolver=EnvKeyResolver(), trusted_roots=[issuer_public_key])
)
client = await Client.connect("localhost:7233", plugins=[plugin])
@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),
)
result = await execute_workflow_authorized(
client=client,
client_interceptor=plugin.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]" # + official MCP SDK, client & server verification (Python ≥3.10)
uv pip install "tenuo[fastmcp]" # + FastMCP (TenuoMiddleware / FastMCP servers)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.22 # Sidecar for warrant verification
docker pull tenuo/control:0.1.0-beta.22 # 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.
Self-hosted Tenuo is free forever. The core library and sidecar run entirely in your infrastructure — no external calls at verification time.
Self-hosted checklist:
- Store signing keys in a secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager) — not environment variables
- Configure
trusted_rootswith your control plane's public keys - Ensure
dry_runis disabled and warrants are required in all enforcement points - Enable audit callbacks and metrics for observability
See Security Model for the full threat model and production hardening guidance.
Tenuo Cloud adds managed warrant issuance, key rotation, revocation (SRL), observability dashboards, and multi-tenant isolation for teams that prefer a hosted control plane.
Building an authorizer sidecar, gateway, or high-throughput service in Rust? Use the core crate directly.
What you get in Rust:
- Warrant minting, derivation, and verification
- Monotonic attenuation enforcement across delegation hops
- Typed constraint evaluation at execution time
- Holder Proof of Possession (PoP) verification
- Signed receipt generation for allow/deny decisions
[dependencies]
tenuo = "0.1.0-beta.22"Use the Rust API when you need a language-native enforcement boundary without Python runtime dependencies.
- API docs: docs.rs/tenuo
- Security model: tenuo.ai/security
- Constraints reference: tenuo.ai/constraints
Tenuo builds on capability token ideas described in CaMeL (Debenedetti et al., 2025). Inspired by Macaroons, Biscuit, and UCAN.
The token format and delegation protocol are being standardized as draft-niyikiza-oauth-attenuating-agent-tokens in the IETF OAuth Working Group. The core attenuation rules are formally verified with Alloy (capability and argument-key monotonicity) and Z3 (constraint-type subsumption bounds).
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).
MIT OR Apache-2.0, at your option.