tenuo
High-performance capability engine for AI agents. Cryptographically attenuated warrants, task-scoped authority, Rust core.
Capability tokens for AI agents.
Tenuo Cloud — Early Access
Managed control plane with revocation, observability, and multi-tenant warrant issuance.
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 tenuo
Quick Start
from 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="[email protected]")) # -> "Sent to [email protected]"
try:
send_email(to="[email protected]")
except AuthorizationDenied:
print("Blocked: [email protected]") # -> "Blocked: [email protected]"
The agent can be prompt-injected. The authorization layer doesn't care. The warrant says *@company.com. The request says [email protected]. Denied.
When the mint_sync block exits, the warrant is gone. No cleanup, no revocation — it just expires.
Why Tenuo?
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 |
How It Works
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
What Tenuo Is Not
- 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.
Key Features
| 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 |
Integrations
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="[email protected]") -> DENIED
LangChain / 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])) # LangGraph
MCP — 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 constraints
A2A (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
Documentation
| 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 |
Requirements
| 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) |
Optional Dependencies
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)
Docker & Kubernetes
Try the Demo — See the full delegation chain in action:
docker compose up
This 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.14 # Sidecar for warrant verification
docker pull tenuo/control:0.1.0-beta.14 # 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.
Roadmap
| 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 |
Rust
Building a sidecar or gateway? Use the core directly:
[dependencies]
tenuo = "0.1.0-beta.14"
See docs.rs/tenuo for Rust API.
Prior Art
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.
See Related Work for detailed comparison.
Featured In
- 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
Etymology
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.
Contributing
Contributions welcome. See CONTRIBUTING.md.
TypeScript SDK (Help Wanted)
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 [email protected].
Security issues: Email [email protected] with PGP (key, not public issues).
Deploying to Production?
Self-hosted is free forever. For a managed control plane with observability and revocation management, request early access to Tenuo Cloud.
License
MIT OR Apache-2.0, at your option.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi