tenuo-claude-code 0.6.2__tar.gz → 0.6.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/CONTRIBUTING.md +2 -2
  2. tenuo_claude_code-0.6.4/PKG-INFO +453 -0
  3. tenuo_claude_code-0.6.4/README.md +421 -0
  4. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/README.md +23 -0
  5. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/tenuo_demo.py +13 -2
  6. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/DETAILS.md +1 -1
  7. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/policies/README.md +1 -1
  8. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/pyproject.toml +3 -3
  9. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/__init__.py +1 -1
  10. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/authorizer_runtime.py +1 -1
  11. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/cli.py +185 -13
  12. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/paths.py +13 -1
  13. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_authorizer_runtime.py +2 -2
  14. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_dx_improvements.py +86 -0
  15. tenuo_claude_code-0.6.2/PKG-INFO +0 -318
  16. tenuo_claude_code-0.6.2/README.md +0 -286
  17. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/.gitignore +0 -0
  18. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/LICENSE +0 -0
  19. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/.claude/agents/researcher.md +0 -0
  20. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/.mcp.json +0 -0
  21. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/docs/README.md +0 -0
  22. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/fake-secrets.env +0 -0
  23. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/ops_server.py +0 -0
  24. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/sandbox/checkout-evidence.log +0 -0
  25. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/sandbox/incident-report.md +0 -0
  26. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/sandbox/notes.txt +0 -0
  27. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/tenuo.yaml +0 -0
  28. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/ENTERPRISE.md +0 -0
  29. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/TROUBLESHOOTING.md +0 -0
  30. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/images/cloud-audit-stream.png +0 -0
  31. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/images/cloud-receipt-approval-detail.png +0 -0
  32. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/images/tenuo_claude_code_architecture.png +0 -0
  33. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/images/tenuo_claude_code_architecture.svg +0 -0
  34. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/managed/README.md +0 -0
  35. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/policies/dry-run-rollout.yaml +0 -0
  36. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/policies/enforce-with-mcp.yaml +0 -0
  37. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/policies/read-only-research.yaml +0 -0
  38. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/admin.py +0 -0
  39. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/data/harness_tools.yaml +0 -0
  40. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/filesystem-dev/pack.yaml +0 -0
  41. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/filesystem-dev/tenuo.yaml.tmpl +0 -0
  42. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/github-mcp/pack.yaml +0 -0
  43. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/github-mcp/tenuo.yaml.tmpl +0 -0
  44. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/http-api-safe/pack.yaml +0 -0
  45. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/http-api-safe/tenuo.yaml.tmpl +0 -0
  46. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs.py +0 -0
  47. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/templates/tenuo.yaml.example +0 -0
  48. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/verify.py +0 -0
  49. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/README.md +0 -0
  50. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/admin.env.example +0 -0
  51. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/cloud.env.example +0 -0
  52. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/tenuo.yaml.advanced.example +0 -0
  53. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/tenuo.yaml.cloud.example +0 -0
  54. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/tenuo.yaml.example +0 -0
  55. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/conftest.py +0 -0
  56. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_admin.py +0 -0
  57. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_approvals.py +0 -0
  58. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_authorize_call.py +0 -0
  59. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_check.py +0 -0
  60. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_cloud_bindings.py +0 -0
  61. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_constraints.py +0 -0
  62. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_ipc_transport.py +0 -0
  63. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_managed_template.py +0 -0
  64. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_mcp_proxy.py +0 -0
  65. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_packs.py +0 -0
  66. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_permissions.py +0 -0
  67. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_posture.py +0 -0
  68. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_routing.py +0 -0
  69. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_scaffold.py +0 -0
  70. {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_subagents.py +0 -0
@@ -5,8 +5,8 @@ Apache-2.0. See [LICENSE](LICENSE). Bug reports and PRs welcome.
5
5
  ## Getting started
6
6
 
7
7
  ```bash
8
- git clone https://github.com/tenuo-ai/claude-governance.git
9
- cd claude-governance
8
+ git clone https://github.com/tenuo-ai/tenuo-claude-code.git
9
+ cd tenuo-claude-code
10
10
  uv venv && uv sync && chmod +x bin/tenuo-claude
11
11
  cd demo && uv run tenuo-claude verify # reference project with sample tenuo.yaml
12
12
  ```
@@ -0,0 +1,453 @@
1
+ Metadata-Version: 2.5
2
+ Name: tenuo-claude-code
3
+ Version: 0.6.4
4
+ Summary: Tenuo governance for Claude Code — warrants, hooks, MCP proxy, and Cloud lifecycle
5
+ Project-URL: Homepage, https://tenuo.ai
6
+ Project-URL: Repository, https://github.com/tenuo-ai/claude-governance
7
+ Project-URL: Documentation, https://github.com/tenuo-ai/claude-governance#readme
8
+ Project-URL: Issues, https://github.com/tenuo-ai/claude-governance/issues
9
+ Author: Tenuo Contributors
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: agents,claude-code,governance,mcp,security,warrants
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Security
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: certifi>=2024.0
27
+ Requires-Dist: cryptography>=42.0
28
+ Requires-Dist: mcp<2,>=1.0
29
+ Requires-Dist: pyyaml>=6.0
30
+ Requires-Dist: tenuo<0.4,>=0.3.0
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Tenuo for Claude Code
34
+
35
+ [![PyPI](https://img.shields.io/pypi/v/tenuo-claude-code)](https://pypi.org/project/tenuo-claude-code/)
36
+ [![Python](https://img.shields.io/pypi/pyversions/tenuo-claude-code)](https://pypi.org/project/tenuo-claude-code/)
37
+ [![CI](https://github.com/tenuo-ai/tenuo-claude-code/actions/workflows/ci.yml/badge.svg)](https://github.com/tenuo-ai/tenuo-claude-code/actions/workflows/ci.yml)
38
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
39
+
40
+ **Task-scoped authorization for Claude Code.**
41
+
42
+ 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.
43
+
44
+ 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.
45
+
46
+ ## Why Tenuo?
47
+
48
+ Claude Code permissions prompt the user; Tenuo checks the **model's action** at the authorizer boundary. The difference matters:
49
+
50
+ | | Claude Code permissions | Tenuo warrant |
51
+ |---|---|---|
52
+ | **Trigger** | User prompt when Claude asks for access | Automatic check before every tool call |
53
+ | **Scope** | Tool name only | Tool name + argument values |
54
+ | **Bypass** | Prompt can be skipped | Cannot be disabled by the user |
55
+ | **Evidence** | Optional hook logs | Signed, tamper-proof receipts |
56
+ | **Revocation** | Edit rules (active sessions may keep access) | Instant fleet-wide revocation (Cloud) |
57
+ | **Enforcement** | Claude Code settings UI | PreToolUse hook + MCP proxy + authorizer |
58
+ | **Span** | Single session | Persists across MCP calls and subagents |
59
+
60
+ 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.
61
+
62
+ ## What It Looks Like
63
+
64
+ After setup, this is what you see:
65
+
66
+ ```
67
+ tenuo-claude audit --verify
68
+
69
+ DENY Read /etc/passwd constraint not satisfied
70
+ path outside workspace (subpath:/home/user/project)
71
+
72
+ ALLOW Read /home/user/project/main.py constraint satisfied
73
+ DENY Bash rm -rf / command not permitted
74
+ 'rm' not in allowlist [ls,pwd,cat,grep]
75
+
76
+ 3 signed receipts verified:
77
+ Ed25519 signatures valid
78
+ Hash chain unbroken
79
+ Constraints replayed and satisfied
80
+ ```
81
+
82
+ Each decision is:
83
+ - **Signed** with Ed25519 (cryptographic proof)
84
+ - **Hash-chained** (tampering is detected)
85
+ - **Constraint-replayed** (warrant terms verified at audit time)
86
+ - **Cloud-synced** (if configured, sent to tenant for fleet audit)
87
+
88
+ ## Quickstart
89
+
90
+ One command to try it:
91
+
92
+ ```bash
93
+ uv tool install tenuo-claude-code
94
+ tenuo-claude bootstrap --native --pack filesystem-dev
95
+ claude
96
+ ```
97
+
98
+ 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:
99
+
100
+ ```bash
101
+ tenuo-claude audit --verify
102
+ ```
103
+
104
+ to see the signed decision log.
105
+
106
+ ### What the quickstart does
107
+
108
+ 1. **`--pack filesystem-dev`** generates a preconfigured policy:
109
+ - Read/write files in your project directory
110
+ - Run: ls, pwd, cat, grep (safe commands)
111
+ - Deny everything else by default
112
+ - Start in `mode: dry-run` (log, don't block)
113
+
114
+ 2. **`--native`** uses the native authorizer binary (no Docker needed)
115
+
116
+ 3. **`bootstrap`** mints a session warrant, wires the PreToolUse hook, starts the authorizer, and runs a test
117
+
118
+ 4. **`claude`** opens Claude Code. The authorizer is now active; every tool call is checked.
119
+
120
+ After you see it working, edit `tenuo.yaml` to fit your project, switch to `mode: enforce`, and run:
121
+
122
+ ```bash
123
+ tenuo-claude refresh
124
+ ```
125
+
126
+ **Available policy packs:**
127
+ - `filesystem-dev` — Read/write project files, safe commands (default)
128
+ - `github-mcp` — GitHub MCP server, constrained API queries
129
+ - `http-api-safe` — WebFetch with SSRF protection and domain allowlists
130
+
131
+ Browse more: [examples/policies/](examples/policies/).
132
+
133
+ **For Cloud mode** (org root, revocation, human approval):
134
+
135
+ ```bash
136
+ tenuo-claude bootstrap --native --pack filesystem-dev --cloud
137
+ ```
138
+
139
+ Then follow the Cloud setup prompts. [Details: Cloud mode](#cloud-mode).
140
+
141
+ ## How It Works
142
+
143
+ You define what Claude can do in `tenuo.yaml`:
144
+
145
+ ```yaml
146
+ name: my-project
147
+ sandbox: ./workspace
148
+
149
+ enforce:
150
+ Read: "subpath:{sandbox}" # Read only project files
151
+ Bash: "shlex:ls,pwd,cat,grep" # Only these commands
152
+ WebFetch:
153
+ domains: ["api.github.com"] # Only this domain
154
+
155
+ subagents:
156
+ analyst:
157
+ tools: [Read, Grep] # Analyst: read-only
158
+
159
+ default: deny # Block everything else
160
+ mode: dry-run # Log, don't block (switch to 'enforce' later)
161
+ ```
162
+
163
+ Tenuo compiles this into a signed, expiring **warrant** (a capability token). Every Claude action is checked against the warrant before it runs:
164
+
165
+ - **File reads** — constrained by `subpath` (can't escape the directory)
166
+ - **Shell commands** — constrained by `shlex` (only allowlisted verbs, no pipes/chaining)
167
+ - **Web fetches** — constrained by domain, SSRF-safe
168
+ - **MCP calls** — constrained by tool name and per-argument rules
169
+ - **Subagent spawns** — gated to declared roles, each runs with narrower authority
170
+
171
+ The same boundary applies no matter why the model tried the call.
172
+
173
+ ### Constraints
174
+
175
+ - **`subpath:DIR`** — path must resolve inside DIR (symlink-safe)
176
+ - **`shlex:verb,verb`** — command executable must be in list; pipes/chaining/subshells denied
177
+ - **`domains:[host]`** — URL domain must match (with SSRF/encoded-IP hardening)
178
+ - **`oneof:a,b`** · **`exact:v`** · **`pattern:glob`** · **`regex:re`** · **`range:min,max`** — argument must satisfy the constraint
179
+ - See [Policy](#policy) for the full list and examples
180
+
181
+ ## Day-to-Day Commands
182
+
183
+ ```bash
184
+ tenuo-claude up # Start the authorizer
185
+ tenuo-claude refresh # Recompile after editing tenuo.yaml
186
+ tenuo-claude audit --verify # Review and verify the decision log
187
+ tenuo-claude down # Stop the authorizer
188
+ ```
189
+
190
+ Full command reference: [Commands](#commands).
191
+
192
+ ## Cloud Mode
193
+
194
+ For organizations: org-root warrants, instant fleet revocation, human approval gates, and a central audit trail.
195
+
196
+ ```bash
197
+ tenuo-claude bootstrap --native --pack filesystem-dev --cloud
198
+ ```
199
+
200
+ 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.
201
+
202
+ Human approval is optional: gate any tool to require sign-off (Slack, Telegram, console, etc.) from a named approver.
203
+
204
+ See [Cloud mode](#cloud-mode) for details.
205
+
206
+ ## What's in Scope
207
+
208
+ Tenuo governs **model-invoked tool calls** on the PreToolUse path:
209
+ - **Read**, **Write**, **Glob**, **Edit** — file access
210
+ - **Bash**, **Monitor**, **PowerShell** — command execution
211
+ - **WebFetch** — HTTP requests
212
+ - **MCP tools** — downstream server calls (via proxy)
213
+ - **Agent** — subagent spawning
214
+
215
+ It does **not** govern:
216
+ - The `!` TUI shell (operator command, not model-invoked)
217
+ - Approved operator actions outside the PreToolUse boundary
218
+
219
+ ## Fail-Closed Design
220
+
221
+ - Missing or broken `tenuo.yaml` denies every governed call until restored
222
+ - Private keys under `.state/` are owner-only (`0600`)
223
+ - A broken receipt sink surfaces loudly (not silent data loss)
224
+ - Cloud-disconnected nodes fall back to local policy (no open bypass)
225
+
226
+ ---
227
+
228
+ ## Policy
229
+
230
+ `tenuo.yaml` drives the whole system: warrant, authorizer, hooks, MCP proxy.
231
+
232
+ ```yaml
233
+ name: acme-backend
234
+ sandbox: ./workspace # a directory; {sandbox} expands to its absolute path
235
+ ttl_seconds: 3600 # optional: session warrant lifetime (default 3600 = 1h)
236
+ mode: enforce # block out-of-scope calls; 'dry-run' = log only
237
+
238
+ enforce:
239
+ Read: "subpath:{sandbox}"
240
+ Write: "subpath:{sandbox}"
241
+ Bash: "shlex:ls,pwd,echo,cat,grep"
242
+ WebFetch:
243
+ domains: ["api.github.com", "*.githubusercontent.com"]
244
+
245
+ allow: # permitted, unconstrained (logged)
246
+ - TodoRead
247
+
248
+ default: deny # anything unlisted is denied (recommended, fail-closed)
249
+
250
+ subagents: # optional: each role runs under a narrower warrant
251
+ analyst:
252
+ tools: [Read, Grep, Glob]
253
+
254
+ mcp: # optional: govern a downstream MCP server's tools
255
+ downstream: ./your_mcp_server.py
256
+ enforce:
257
+ read_file: "subpath:{sandbox}"
258
+ run_query:
259
+ arg: sql
260
+ constraint: "regex:^SELECT "
261
+ ```
262
+
263
+ ### Constraints
264
+
265
+ | Constraint | Applies to | Checks |
266
+ |------------|------------|--------|
267
+ | `subpath:DIR` | Read, Write, Edit, Glob, Grep | Path resolves inside DIR (symlink-safe) |
268
+ | `shlex:a,b,c` | Bash, Monitor, PowerShell | Executable is one of `a,b,c`; pipes/chaining/subshells denied |
269
+ | `domains:[list]` | WebFetch | URL domain matches allowlist; SSRF/encoded-IP hardening built-in |
270
+ | `oneof:a,b` | any tool | Value is in set |
271
+ | `notoneof:a,b` | any tool | Value not in set |
272
+ | `exact:v` | any tool | Value equals exactly |
273
+ | `pattern:glob` | any tool | Value glob-matches |
274
+ | `regex:re` | any tool | Value regex-matches |
275
+ | `range:min,max` | any tool | Value in numeric range |
276
+ | `urlpattern:url` | any tool | Value matches URL glob |
277
+ | `cidr:n/m` | any tool | Value in IP range |
278
+
279
+ `{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`.
280
+
281
+ 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.
282
+
283
+ **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).
284
+
285
+ **MCP arguments.** Under `mcp.enforce:`, a bare constraint targets `path`. Use `arg: NAME` for other arguments, and `args: {NAME: constraint, …}` for multiple constraints.
286
+
287
+ **Three lists, plus two switches.**
288
+ - **`enforce:`** constrained (blocked if constraint fails)
289
+ - **`allow:`** unconstrained (always allowed, logged)
290
+ - **`default:`** catch-all for unlisted tools
291
+ - **`default: deny`** recommended (fail-closed)
292
+ - **`default: approve`** routes to human approval (Cloud only)
293
+ - **`mode:`** global posture
294
+ - **`mode: enforce`** blocks denied calls
295
+ - **`mode: dry-run`** logs decisions, blocks nothing (use to shadow a policy before enforcing)
296
+ - **`subagents:`** declares roles; each runs with attenuated authority
297
+ - **`ttl_seconds:`** warrant lifetime in seconds (default 3600 = 1h); `up` re-mints
298
+
299
+ After any edit, run `tenuo-claude refresh`.
300
+
301
+ **Ready-made policies:** [examples/policies/](examples/policies/).
302
+
303
+ ## Commands
304
+
305
+ Day to day: `up` (start), `audit` (review), `refresh` (after editing policy).
306
+
307
+ | Command | What it does |
308
+ |---------|--------------|
309
+ | `onboard` | Interactive first-run wizard (`--local` / `--cloud`); same as `bootstrap` but with prompts. |
310
+ | `bootstrap` | Non-interactive setup: scaffold policy if none → `init` → `up` → `verify`. `--cloud` for Cloud mode; `--pack PACK` to use a policy pack. |
311
+ | `init` | Compile existing `tenuo.yaml`: mint warrant, wire hook and MCP proxy. |
312
+ | `up` / `down` | Start / stop the authorizer (auto-selects Docker or native; `--native` forces native). |
313
+ | `refresh` | Recompile after editing `tenuo.yaml` (restarts authorizer if running). |
314
+ | `verify [--deep]` | Self-test the policy against the authorizer (no Claude session needed). `--deep` adds SSRF/encoded-IP matrix and Bash deny cases. |
315
+ | `audit [--tail N] [--verify]` | Show the decision log (`.state/receipts.jsonl`). `--verify` checks receipt signatures, hash-chain links, and embedded evidence. |
316
+ | `check` | Preflight: dependencies, wiring, audit-sink health, leaked keys, Cloud bindings. |
317
+ | `status` | Warrant, mode, audit-sink health, Cloud summary. |
318
+ | `install-authorizer` | Install the pinned native authorizer to `~/.tenuo/bin`. |
319
+ | `bench [--json]` | Measure per-call overhead on your machine. |
320
+ | `revoke` | Revoke the current session warrant. |
321
+
322
+ 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).
323
+
324
+ ## Cloud Mode
325
+
326
+ For organizations: org root, fleet revocation, human approval, central audit.
327
+
328
+ ### Setup (first time)
329
+
330
+ 1. **Create a Tenuo tenant** at [cloud.tenuo.ai](https://cloud.tenuo.ai). Complete the **Infrastructure** onboarding (provisions KMS-backed signing keys).
331
+
332
+ 2. **Get credentials:**
333
+ - **Runtime key** (`tenuo_ct_…`) from Agents → Quick Connect → **Authorizer Only** → `.state/cloud.env`
334
+ - **Tenant-admin key** (`tc_…`) from Settings → API Keys → Create (admin role) → `~/.tenuo/admin.env`
335
+
336
+ 3. **Bootstrap with Cloud:**
337
+ ```bash
338
+ tenuo-claude bootstrap --native --pack filesystem-dev --cloud
339
+ ```
340
+ Behind the scenes: `tenuo-admin setup` registers this project, creates a Cloud trigger from `tenuo.yaml`, and mints a root-signed warrant.
341
+
342
+ 4. **(Optional) Add human approval:**
343
+ - Create an approver identity in Cloud (Dashboard → Channels → Identity Bindings)
344
+ - Give it a notification channel (Slack, Telegram, console, etc.)
345
+ - Gate any tool with `approval:` in `tenuo.yaml`
346
+
347
+ ### Day to day (Cloud)
348
+
349
+ ```bash
350
+ tenuo-claude check && tenuo-claude up
351
+ ```
352
+
353
+ If `check` reports a **Cloud bindings** failure, run `tenuo-admin setup` and retry.
354
+
355
+ After editing `tenuo.yaml`:
356
+ ```bash
357
+ tenuo-claude refresh
358
+ ```
359
+
360
+ The Cloud-issued warrant stays active until revoked (or TTL expires). To revoke fleet-wide:
361
+ ```bash
362
+ tenuo-claude revoke
363
+ ```
364
+
365
+ ~30 seconds later, all active sessions enforcing that warrant exit and must re-authorize.
366
+
367
+ **Important:** Once Cloud is configured, don't re-run plain `bootstrap` (it reverts to local mode). Use `check && up` on future sessions.
368
+
369
+ ### Human Approval (Cloud)
370
+
371
+ Add `approval:` to any enforced tool:
372
+
373
+ ```yaml
374
+ enforce:
375
+ WebFetch:
376
+ domains: ["api.github.com"]
377
+ approval: true # all URLs gated
378
+
379
+ delete_deployment: # MCP tool
380
+ arg: target
381
+ constraint: "oneof:staging,prod"
382
+ approval:
383
+ exempt: ["staging"] # staging bypasses approval
384
+ ```
385
+
386
+ When Claude tries a gated action:
387
+ 1. The hook opens a Cloud approval request
388
+ 2. The approver gets a Slack/Telegram/console notification
389
+ 3. They review and approve/deny
390
+ 4. The decision is recorded in the signed receipt (audit trail shows **who** approved)
391
+
392
+ **Setup:** See [docs/DETAILS.md § Approval setup runbook](docs/DETAILS.md#approval-setup-runbook).
393
+
394
+ For Cloud, receipts are sent to your tenant (central audit). Locally, receipts are always written to `.state/receipts.jsonl`.
395
+
396
+ ## Security Model
397
+
398
+ Tenuo runs alongside Claude Code permissions; it doesn't replace them. The difference:
399
+
400
+ - **Claude Code permissions:** Prompt the user; can be bypassed with `--dangerously-skip-permissions`
401
+ - **Tenuo warrant:** Signed, expiring authority enforced by an authorizer before each call; cannot be disabled by the user or model
402
+
403
+ 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.
404
+
405
+ **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.
406
+
407
+ **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).
408
+
409
+ **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](docs/DETAILS.md#managed-deployment) for the rollout checklist and hardened socket setup.
410
+
411
+ **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.
412
+
413
+ ---
414
+
415
+ ## Build from Source
416
+
417
+ For development or running from a git checkout:
418
+
419
+ ```bash
420
+ git clone https://github.com/tenuo-ai/tenuo-claude-code.git
421
+ cd tenuo-claude-code
422
+ uv venv && uv sync && source .venv/bin/activate # Windows: .venv\Scripts\activate
423
+ chmod +x bin/tenuo-claude
424
+ uv run tenuo-claude install-authorizer # only if not using Docker
425
+ ```
426
+
427
+ 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.
428
+
429
+ Contributors: [CONTRIBUTING.md](CONTRIBUTING.md).
430
+
431
+ ## Performance
432
+
433
+ Authorization is ~1–3 ms per call; the PreToolUse hook adds ~100–200 ms (mostly process startup). Measure on your machine:
434
+
435
+ ```bash
436
+ tenuo-claude bench
437
+ ```
438
+
439
+ ## About
440
+
441
+ **Repository:** [tenuo-ai/tenuo-claude-code](https://github.com/tenuo-ai/tenuo-claude-code)
442
+ **PyPI:** [tenuo-claude-code](https://pypi.org/project/tenuo-claude-code/)
443
+ **Tenuo core:** [tenuo-ai/tenuo](https://github.com/tenuo-ai/tenuo)
444
+ **License:** [Apache 2.0](LICENSE)
445
+
446
+ | | Path |
447
+ |---|---|
448
+ | **Package source** | `src/tenuo_claude_code/` |
449
+ | **Policy packs** | `examples/policies/` |
450
+ | **Reference demo** | `demo/` |
451
+ | **Implementation details** | `docs/DETAILS.md` |
452
+ | **Troubleshooting** | `docs/TROUBLESHOOTING.md` |
453
+ | **Security** | `SECURITY.md` |