Skip to main content

Tenuo for Claude Code

PyPI Python CI License: Apache-2.0

Task-scoped authorization for Claude Code.

Give Claude Code access to your codebase and tools without giving it access to everything. Tenuo constrains what Claude can read, execute, fetch, and invoke before each action runs. You get signed evidence of what was allowed or denied—a tamper-proof audit trail.

Tenuo works alongside Claude Code permissions. It adds a signed, expiring authority token that stays enforced even when permission prompts are bypassed, across MCP calls and subagents.

Why Tenuo?

Claude Code permissions prompt the user; Tenuo checks the model's action at the authorizer boundary. The difference matters:

Claude Code permissions Tenuo warrant
Trigger User prompt when Claude asks for access Automatic check before every tool call
Scope Tool name only Tool name + argument values
Bypass Prompt can be skipped Cannot be disabled by the user
Evidence Optional hook logs Signed, tamper-proof receipts
Revocation Edit rules (active sessions may keep access) Instant fleet-wide revocation (Cloud)
Enforcement Claude Code settings UI PreToolUse hook + MCP proxy + authorizer
Span Single session Persists across MCP calls and subagents

Tenuo doesn't replace Claude Code permissions—it adds a cryptographic gate that works regardless of why the model tried the call: prompt injection, hallucination, poisoned tool output, or direct user request.

What It Looks Like

After setup, this is what you see:

tenuo-claude audit --verify

DENY   Read /etc/passwd                constraint not satisfied
       path outside workspace (subpath:/home/user/project)

ALLOW  Read /home/user/project/main.py  constraint satisfied
DENY   Bash rm -rf /                    command not permitted
       'rm' not in allowlist [ls,pwd,cat,grep]

3 signed receipts verified:
  Ed25519 signatures valid
  Hash chain unbroken
  Constraints replayed and satisfied

Each decision is:

  • Signed with Ed25519 (cryptographic proof)
  • Hash-chained (tampering is detected)
  • Constraint-replayed (warrant terms verified at audit time)
  • Cloud-synced (if configured, sent to tenant for fleet audit)

Quickstart

One command to try it:

uv tool install tenuo-claude-code
tenuo-claude bootstrap --native --pack filesystem-dev
claude

That's it. bootstrap with --pack filesystem-dev generates a safe starter policy (read/write your project, run safe commands), starts the authorizer, and runs a self-test. Open Claude Code, try something in-scope (it works) and out-of-scope (it's denied). Then:

tenuo-claude audit --verify

to see the signed decision log.

What the quickstart does

  1. --pack filesystem-dev generates a preconfigured policy:

    • Read/write files in your project directory
    • Run: ls, pwd, cat, grep (safe commands)
    • Deny everything else by default
    • Start in mode: dry-run (log, don't block)
  2. --native uses the native authorizer binary (no Docker needed)

  3. bootstrap mints a session warrant, wires the PreToolUse hook, starts the authorizer, and runs a test

  4. claude opens Claude Code. The authorizer is now active; every tool call is checked.

After you see it working, edit tenuo.yaml to fit your project, switch to mode: enforce, and run:

tenuo-claude refresh

Available policy packs:

  • filesystem-dev — Read/write project files, safe commands (default)
  • github-mcp — GitHub MCP server, constrained API queries
  • http-api-safe — WebFetch with SSRF protection and domain allowlists

Browse more: examples/policies/.

For Cloud mode (org root, revocation, human approval):

tenuo-claude bootstrap --native --pack filesystem-dev --cloud

Then follow the Cloud setup prompts. Details: Cloud mode.

How It Works

You define what Claude can do in tenuo.yaml:

name: my-project
sandbox: ./workspace

enforce:
  Read:  "subpath:{sandbox}"              # Read only project files
  Bash:  "shlex:ls,pwd,cat,grep"          # Only these commands
  WebFetch:
    domains: ["api.github.com"]            # Only this domain
    
subagents:
  analyst:
    tools: [Read, Grep]                    # Analyst: read-only

default: deny                               # Block everything else
mode: dry-run                               # Log, don't block (switch to 'enforce' later)

Tenuo compiles this into a signed, expiring warrant (a capability token). Every Claude action is checked against the warrant before it runs:

  • File reads — constrained by subpath (can't escape the directory)
  • Shell commands — constrained by shlex (only allowlisted verbs, no pipes/chaining)
  • Web fetches — constrained by domain, SSRF-safe
  • MCP calls — constrained by tool name and per-argument rules
  • Subagent spawns — gated to declared roles, each runs with narrower authority

The same boundary applies no matter why the model tried the call.

Constraints

  • subpath:DIR — path must resolve inside DIR (symlink-safe)
  • shlex:verb,verb — command executable must be in list; pipes/chaining/subshells denied
  • domains:[host] — URL domain must match (with SSRF/encoded-IP hardening)
  • oneof:a,b · exact:v · pattern:glob · regex:re · range:min,max — argument must satisfy the constraint
  • See Policy for the full list and examples

Day-to-Day Commands

tenuo-claude up              # Start the authorizer
tenuo-claude refresh         # Recompile after editing tenuo.yaml
tenuo-claude audit --verify  # Review and verify the decision log
tenuo-claude down            # Stop the authorizer

Full command reference: Commands.

Cloud Mode

For organizations: org-root warrants, instant fleet revocation, human approval gates, and a central audit trail.

tenuo-claude bootstrap --native --pack filesystem-dev --cloud

Setup takes ~2 minutes (Cloud tenant, runtime credentials, policy trigger). After that, Tenuo issues warrants signed by your org root, revokes them fleet-wide in ~30s, and sends signed receipts to your tenant for compliance audit.

Human approval is optional: gate any tool to require sign-off (Slack, Telegram, console, etc.) from a named approver.

See Cloud mode for details.

What's in Scope

Tenuo governs model-invoked tool calls on the PreToolUse path:

  • Read, Write, Glob, Edit — file access
  • Bash, Monitor, PowerShell — command execution
  • WebFetch — HTTP requests
  • MCP tools — downstream server calls (via proxy)
  • Agent — subagent spawning

It does not govern:

  • The ! TUI shell (operator command, not model-invoked)
  • Approved operator actions outside the PreToolUse boundary

Fail-Closed Design

  • Missing or broken tenuo.yaml denies every governed call until restored
  • Private keys under .state/ are owner-only (0600)
  • A broken receipt sink surfaces loudly (not silent data loss)
  • Cloud-disconnected nodes fall back to local policy (no open bypass)

Policy

tenuo.yaml drives the whole system: warrant, authorizer, hooks, MCP proxy.

name: acme-backend
sandbox: ./workspace        # a directory; {sandbox} expands to its absolute path
ttl_seconds: 3600           # optional: session warrant lifetime (default 3600 = 1h)
mode: enforce               # block out-of-scope calls; 'dry-run' = log only

enforce:
  Read:  "subpath:{sandbox}"
  Write: "subpath:{sandbox}"
  Bash:  "shlex:ls,pwd,echo,cat,grep"
  WebFetch:
    domains: ["api.github.com", "*.githubusercontent.com"]
  
allow:                      # permitted, unconstrained (logged)
  - TodoRead

default: deny               # anything unlisted is denied (recommended, fail-closed)

subagents:                  # optional: each role runs under a narrower warrant
  analyst:
    tools: [Read, Grep, Glob]

mcp:                        # optional: govern a downstream MCP server's tools
  downstream: ./your_mcp_server.py
  enforce:
    read_file: "subpath:{sandbox}"
    run_query:
      arg: sql
      constraint: "regex:^SELECT "

Constraints

Constraint Applies to Checks
subpath:DIR Read, Write, Edit, Glob, Grep Path resolves inside DIR (symlink-safe)
shlex:a,b,c Bash, Monitor, PowerShell Executable is one of a,b,c; pipes/chaining/subshells denied
domains:[list] WebFetch URL domain matches allowlist; SSRF/encoded-IP hardening built-in
oneof:a,b any tool Value is in set
notoneof:a,b any tool Value not in set
exact:v any tool Value equals exactly
pattern:glob any tool Value glob-matches
regex:re any tool Value regex-matches
range:min,max any tool Value in numeric range
urlpattern:url any tool Value matches URL glob
cidr:n/m any tool Value in IP range

{sandbox} expands to the directory in sandbox:. You can point subpath: at any path. Tools unlisted aren't individually governed; they're caught by default.

For internal egress, WebFetch also accepts cidrs: (e.g., cidrs: ["10.0.0.0/8"]) to allow by IP range. It's off by default; add deliberately.

Command tools. Bash, PowerShell, and Monitor execute commands independently; govern each in enforce:. Use shlex for POSIX (Bash/Monitor) and pattern/regex for PowerShell (different dialect).

MCP arguments. Under mcp.enforce:, a bare constraint targets path. Use arg: NAME for other arguments, and args: {NAME: constraint, …} for multiple constraints.

Three lists, plus two switches.

  • enforce: constrained (blocked if constraint fails)
  • allow: unconstrained (always allowed, logged)
  • default: catch-all for unlisted tools
    • default: deny recommended (fail-closed)
    • default: approve routes to human approval (Cloud only)
  • mode: global posture
    • mode: enforce blocks denied calls
    • mode: dry-run logs decisions, blocks nothing (use to shadow a policy before enforcing)
  • subagents: declares roles; each runs with attenuated authority
  • ttl_seconds: warrant lifetime in seconds (default 3600 = 1h); up re-mints

After any edit, run tenuo-claude refresh.

Ready-made policies: examples/policies/.

Commands

Day to day: up (start), audit (review), refresh (after editing policy).

Command What it does
onboard Interactive first-run wizard (--local / --cloud); same as bootstrap but with prompts.
bootstrap Non-interactive setup: scaffold policy if none → init → up → verify. --cloud for Cloud mode; --pack PACK to use a policy pack.
init Compile existing tenuo.yaml: mint warrant, wire hook and MCP proxy.
up / down Start / stop the authorizer (auto-selects Docker or native; --native forces native).
refresh Recompile after editing tenuo.yaml (restarts authorizer if running).
verify [--deep] Self-test the policy against the authorizer (no Claude session needed). --deep adds SSRF/encoded-IP matrix and Bash deny cases.
audit [--tail N] [--verify] Show the decision log (.state/receipts.jsonl). --verify checks receipt signatures, hash-chain links, and embedded evidence.
check Preflight: dependencies, wiring, audit-sink health, leaked keys, Cloud bindings.
status Warrant, mode, audit-sink health, Cloud summary.
install-authorizer Install the pinned native authorizer to ~/.tenuo/bin.
bench [--json] Measure per-call overhead on your machine.
revoke Revoke the current session warrant.

The warrant is short-lived (~1h TTL); up refreshes it before expiry. Generated files (don't commit): .state/ (keys, warrant, credentials), .claude/settings.json (hooks), .mcp.json (MCP proxy).

Cloud Mode

For organizations: org root, fleet revocation, human approval, central audit.

Setup (first time)

  1. Create a Tenuo tenant at cloud.tenuo.ai. Complete the Infrastructure onboarding (provisions KMS-backed signing keys).

  2. Get credentials:

    • Runtime key (tenuo_ct_…) from Agents → Quick Connect → Authorizer Only → .state/cloud.env
    • Tenant-admin key (tc_…) from Settings → API Keys → Create (admin role) → ~/.tenuo/admin.env
  3. Bootstrap with Cloud:

    tenuo-claude bootstrap --native --pack filesystem-dev --cloud
    

    Behind the scenes: tenuo-admin setup registers this project, creates a Cloud trigger from tenuo.yaml, and mints a root-signed warrant.

  4. (Optional) Add human approval:

    • Create an approver identity in Cloud (Dashboard → Channels → Identity Bindings)
    • Give it a notification channel (Slack, Telegram, console, etc.)
    • Gate any tool with approval: in tenuo.yaml

Day to day (Cloud)

tenuo-claude check && tenuo-claude up

If check reports a Cloud bindings failure, run tenuo-admin setup and retry.

After editing tenuo.yaml:

tenuo-claude refresh

The Cloud-issued warrant stays active until revoked (or TTL expires). To revoke fleet-wide:

tenuo-claude revoke

~30 seconds later, all active sessions enforcing that warrant exit and must re-authorize.

Important: Once Cloud is configured, don't re-run plain bootstrap (it reverts to local mode). Use check && up on future sessions.

Human Approval (Cloud)

Add approval: to any enforced tool:

enforce:
  WebFetch:
    domains: ["api.github.com"]
    approval: true              # all URLs gated
    
  delete_deployment:            # MCP tool
    arg: target
    constraint: "oneof:staging,prod"
    approval:
      exempt: ["staging"]       # staging bypasses approval

When Claude tries a gated action:

  1. The hook opens a Cloud approval request
  2. The approver gets a Slack/Telegram/console notification
  3. They review and approve/deny
  4. The decision is recorded in the signed receipt (audit trail shows who approved)

Setup: See docs/DETAILS.md § Approval setup runbook.

For Cloud, receipts are sent to your tenant (central audit). Locally, receipts are always written to .state/receipts.jsonl.

Security Model

Tenuo runs alongside Claude Code permissions; it doesn't replace them. The difference:

  • Claude Code permissions: Prompt the user; can be bypassed with --dangerously-skip-permissions
  • Tenuo warrant: Signed, expiring authority enforced by an authorizer before each call; cannot be disabled by the user or model

The warrant is a capability token: it lists what Claude can do and for how long. Each action is checked against the warrant before it runs. The same boundary applies regardless of why the model tried the call: prompt injection, hallucination, poisoned tool output, or direct user request.

Receipts: Every decision is signed with Ed25519 and hash-chained for tamper-evidence. Local audit --verify checks signatures, hash-chain links, warrant chains, and deterministic constraint replay. Cloud receipts are independently verified by the tenant.

Fail-closed: Missing or broken tenuo.yaml denies every governed call. Keys under .state/ are owner-only. A broken receipt sink surfaces loudly (not silent data loss).

Rolling out to a team: Keep tenuo.yaml in version control. Push hook/MCP wiring through Claude Code managed settings. Use Cloud for org-root warrants, central audit, and revocation. See docs/DETAILS.md § Managed deployment for the rollout checklist and hardened socket setup.

Turning it off: tenuo-claude disable unwires the hook (Claude stops calling the authorizer) and stops the authorizer, leaving policy and warrant in place. tenuo-claude uninstall also deletes .state/ (warrant, keys, credentials). tenuo.yaml is never touched.


Build from Source

For development or running from a git checkout:

git clone https://github.com/tenuo-ai/tenuo-claude-code.git
cd tenuo-claude-code
uv venv && uv sync && source .venv/bin/activate    # Windows: .venv\Scripts\activate
chmod +x bin/tenuo-claude
uv run tenuo-claude install-authorizer             # only if not using Docker

Run via ./bin/tenuo-claude --help, uv run tenuo-claude --help, or pip install -e .. Re-run tenuo-claude init (or refresh) after moving the repo or reinstalling.

Contributors: CONTRIBUTING.md.

Performance

Authorization is ~1–3 ms per call; the PreToolUse hook adds ~100–200 ms (mostly process startup). Measure on your machine:

tenuo-claude bench

About

Repository: tenuo-ai/tenuo-claude-code
PyPI: tenuo-claude-code
Tenuo core: tenuo-ai/tenuo
License: Apache 2.0

Path
Package source src/tenuo_claude_code/
Policy packs examples/policies/
Reference demo demo/
Implementation details docs/DETAILS.md
Troubleshooting docs/TROUBLESHOOTING.md
Security SECURITY.md

Release files for tenuo-claude-code 0.6.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tenuo-claude-code 0.6.3
File Size Uploaded
tenuo_claude_code-0.6.3.tar.gz 627.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tenuo-claude-code 0.6.3
File Interpreter ABI Platform
tenuo_claude_code-0.6.3-py3-none-any.whl Python 3 none any Details

Total release size: 740.7 kB

Release files / tenuo_claude_code-0.6.3.tar.gz

Download URL tenuo_claude_code-0.6.3.tar.gz
Size 627.3 kB
Tags Source
SHA-256 checksum
How to use checksums
afc80477e7d492ae6f009b5eac59c7e1048cceb4a32e071fbdf9de13667ebdcb
BLAKE2b-256 checksum
How to use checksums
06fd8272436e57f3fb177e76a459b8aeaf844ccce512ef2d89bc0d4c468b2998
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / tenuo_claude_code-0.6.3-py3-none-any.whl

Download URL tenuo_claude_code-0.6.3-py3-none-any.whl
Size 113.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
83e2534d6a2cd349deebe7d63f8df04e15fa86642cc986ce101ee71031b6e910
BLAKE2b-256 checksum
How to use checksums
a10bf9f22b4ef073fcdc31ad62c3c562183566447b4a1e813a8ab3b93fa06cf0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.6.4

2 release files

This release

0.6.3 This release

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page