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.
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/CONTRIBUTING.md +2 -2
- tenuo_claude_code-0.6.4/PKG-INFO +453 -0
- tenuo_claude_code-0.6.4/README.md +421 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/README.md +23 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/tenuo_demo.py +13 -2
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/DETAILS.md +1 -1
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/policies/README.md +1 -1
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/pyproject.toml +3 -3
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/__init__.py +1 -1
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/authorizer_runtime.py +1 -1
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/cli.py +185 -13
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/paths.py +13 -1
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_authorizer_runtime.py +2 -2
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_dx_improvements.py +86 -0
- tenuo_claude_code-0.6.2/PKG-INFO +0 -318
- tenuo_claude_code-0.6.2/README.md +0 -286
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/.gitignore +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/LICENSE +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/.claude/agents/researcher.md +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/.mcp.json +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/docs/README.md +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/fake-secrets.env +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/ops_server.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/sandbox/checkout-evidence.log +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/sandbox/incident-report.md +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/sandbox/notes.txt +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/demo/tenuo.yaml +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/ENTERPRISE.md +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/TROUBLESHOOTING.md +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/images/cloud-audit-stream.png +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/images/cloud-receipt-approval-detail.png +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/images/tenuo_claude_code_architecture.png +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/docs/images/tenuo_claude_code_architecture.svg +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/managed/README.md +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/policies/dry-run-rollout.yaml +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/policies/enforce-with-mcp.yaml +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/examples/policies/read-only-research.yaml +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/admin.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/data/harness_tools.yaml +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/filesystem-dev/pack.yaml +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/filesystem-dev/tenuo.yaml.tmpl +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/github-mcp/pack.yaml +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/github-mcp/tenuo.yaml.tmpl +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs/http-api-safe/pack.yaml +0 -0
- {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
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/packs.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/templates/tenuo.yaml.example +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/src/tenuo_claude_code/verify.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/README.md +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/admin.env.example +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/cloud.env.example +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/tenuo.yaml.advanced.example +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/tenuo.yaml.cloud.example +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/templates/tenuo.yaml.example +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/conftest.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_admin.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_approvals.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_authorize_call.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_check.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_cloud_bindings.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_constraints.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_ipc_transport.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_managed_template.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_mcp_proxy.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_packs.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_permissions.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_posture.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_routing.py +0 -0
- {tenuo_claude_code-0.6.2 → tenuo_claude_code-0.6.4}/tests/test_scaffold.py +0 -0
- {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-
|
|
9
|
-
cd claude-
|
|
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
|
+
[](https://pypi.org/project/tenuo-claude-code/)
|
|
36
|
+
[](https://pypi.org/project/tenuo-claude-code/)
|
|
37
|
+
[](https://github.com/tenuo-ai/tenuo-claude-code/actions/workflows/ci.yml)
|
|
38
|
+
[](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` |
|