pi-openappa 0.1.0 → 0.3.0
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.
- package/README.md +63 -6
- package/extensions/index.ts +174 -28
- package/package.json +15 -1
- package/src/gate.ts +62 -6
- package/templates/appa.toml +1068 -0
- package/traces/coding-loop.appa +31 -0
- package/traces/memory-poison.appa +24 -0
- package/traces/push-day.appa +21 -0
- package/traces/research-then-code.appa +53 -0
- package/traces/secret-ops.appa +23 -0
- package/justfile +0 -32
- package/test/adapter.test.ts +0 -207
- package/test/extension.test.ts +0 -447
- package/test/fixtures/mock-appa.mjs +0 -104
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,1068 @@
|
|
|
1
|
+
# pi-openappa default policy — a Pi session's complete, self-contained guard.
|
|
2
|
+
#
|
|
3
|
+
# Install (either):
|
|
4
|
+
# cp templates/appa.toml ~/.config/appa/appa.toml # from a checkout
|
|
5
|
+
# /appa init # from inside Pi
|
|
6
|
+
# or scope it to one project:
|
|
7
|
+
# /appa init project # writes ./appa.toml + .pi/openappa
|
|
8
|
+
#
|
|
9
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
10
|
+
# THE FENCE MAP (what this file encodes; validate: `just policy-test`)
|
|
11
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
# appa gates flows, not tools. Sources mark data; sinks check it:
|
|
13
|
+
#
|
|
14
|
+
# web (curl/wget, web_explore, context7) ──suspicious──▶ the session
|
|
15
|
+
# .env / credentials ──self-narrow─▶ the session
|
|
16
|
+
#
|
|
17
|
+
# A session that carries suspicious or self-narrowed data is REFUSED at:
|
|
18
|
+
# • any edit to an existing file — except documentation (docs/, openspec/,
|
|
19
|
+
# *.md): research lands in docs, it does not rewrite code [day B]
|
|
20
|
+
# • writes to files that execute later: tests, devops (Makefile,
|
|
21
|
+
# justfile, scripts, CI), infra (terraform, k8s, helm…), and
|
|
22
|
+
# credential-shaped paths [day A]
|
|
23
|
+
# • git push / gh pr|issue|release|api|repo: no tainted data leaves
|
|
24
|
+
# the machine [day C]
|
|
25
|
+
# • web tools after reading .env: secrets may not reach a query [day D]
|
|
26
|
+
# • mem_save / mem_update: poisoned memory must not persist [day F]
|
|
27
|
+
# • edits to appa's own policy files [guard]
|
|
28
|
+
#
|
|
29
|
+
# A clean session (no web, no secrets) flows everywhere: reads, edits,
|
|
30
|
+
# tests, commits — nothing below ever trips. [day A]
|
|
31
|
+
#
|
|
32
|
+
# Reset: taint lives in the trajectory label and only narrows. The manual
|
|
33
|
+
# reset is a NEW session (resume/fork inherit the label). There is no
|
|
34
|
+
# /untaint by design.
|
|
35
|
+
#
|
|
36
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
37
|
+
# GROUND RULES (verified against appa 0.31.1; docs/wire-notes.md)
|
|
38
|
+
# ══════════════════════════════════════════════════════════════════════════
|
|
39
|
+
# 1. Tool naming: Pi built-ins map to Claude-Code policy names (bash→Bash,
|
|
40
|
+
# read→Read, edit→Edit, write→Write, grep→Grep, find→Glob, ls→LS,
|
|
41
|
+
# powershell→PowerShell); custom/MCP tools pass through verbatim
|
|
42
|
+
# (`subagent`, `mem_search`, …), declared here under their Pi names.
|
|
43
|
+
# 2. Argument naming: `tool_input` crosses the wire exactly as Pi sent it,
|
|
44
|
+
# so selectors use Pi's argument names — Read/Edit/Write take `path`,
|
|
45
|
+
# Bash/PowerShell take `command`. The stock claude-code battery selects
|
|
46
|
+
# on `file_path` and never matches a Pi call; this file spells it right.
|
|
47
|
+
# 3. No annotators, on purpose: `builtin = "claude-code"` annotators are
|
|
48
|
+
# host-coupled and unreachable under Pi (they fail closed and withhold
|
|
49
|
+
# results). This policy is static rules only — nothing here can be
|
|
50
|
+
# unreachable. Consequence: deny-undeclared is the only stance (a `*`
|
|
51
|
+
# wildcard REQUIRES an annotator; static deltas on it refuse to load).
|
|
52
|
+
# 4. Rules match in order; first match wins. Selector patterns are globs on
|
|
53
|
+
# the full argument value (`*` = any sequence).
|
|
54
|
+
# 5. Approximations: `write` cannot distinguish create from overwrite, so
|
|
55
|
+
# "research may create, never overwrite" is enforced for protected
|
|
56
|
+
# categories only; a tainted overwrite of unprotected src via `write`
|
|
57
|
+
# is a documented hole (use `edit` discipline). "Flow ends after a docs
|
|
58
|
+
# edit" is approximated by docs being the only tainted-editable sink.
|
|
59
|
+
|
|
60
|
+
[policy]
|
|
61
|
+
version = 2
|
|
62
|
+
|
|
63
|
+
# ─── Bash: commands that name requester credentials are `self` data ────────
|
|
64
|
+
#
|
|
65
|
+
# Narrows the session to `self`: the result is withheld and `redact-secrets`
|
|
66
|
+
# (below) masks tokens before the output returns to `public`. A selector is
|
|
67
|
+
# a glob on the command as written; a spelling these miss — inside `$(...)`,
|
|
68
|
+
# a heredoc, an alias — falls through to later rules. Extend freely.
|
|
69
|
+
|
|
70
|
+
[[policy.tool]]
|
|
71
|
+
name = "host/claude-code/Bash(command:*.env*)"
|
|
72
|
+
delta = { audience = ["self"] }
|
|
73
|
+
tags = ["credentials"]
|
|
74
|
+
|
|
75
|
+
[[policy.tool]]
|
|
76
|
+
name = "host/claude-code/Bash(command:*.claude.json*)"
|
|
77
|
+
delta = { audience = ["self"] }
|
|
78
|
+
tags = ["credentials"]
|
|
79
|
+
|
|
80
|
+
[[policy.tool]]
|
|
81
|
+
name = "host/claude-code/Bash(command:*.credentials.json*)"
|
|
82
|
+
delta = { audience = ["self"] }
|
|
83
|
+
tags = ["credentials"]
|
|
84
|
+
|
|
85
|
+
[[policy.tool]]
|
|
86
|
+
name = "host/claude-code/Bash(command:*.ssh/*)"
|
|
87
|
+
delta = { audience = ["self"] }
|
|
88
|
+
tags = ["credentials"]
|
|
89
|
+
|
|
90
|
+
[[policy.tool]]
|
|
91
|
+
name = "host/claude-code/Bash(command:*id_rsa*)"
|
|
92
|
+
delta = { audience = ["self"] }
|
|
93
|
+
tags = ["credentials"]
|
|
94
|
+
|
|
95
|
+
[[policy.tool]]
|
|
96
|
+
name = "host/claude-code/Bash(command:*id_ed25519*)"
|
|
97
|
+
delta = { audience = ["self"] }
|
|
98
|
+
tags = ["credentials"]
|
|
99
|
+
|
|
100
|
+
[[policy.tool]]
|
|
101
|
+
name = "host/claude-code/Bash(command:*.netrc*)"
|
|
102
|
+
delta = { audience = ["self"] }
|
|
103
|
+
tags = ["credentials"]
|
|
104
|
+
|
|
105
|
+
[[policy.tool]]
|
|
106
|
+
name = "host/claude-code/Bash(command:*.git-credentials*)"
|
|
107
|
+
delta = { audience = ["self"] }
|
|
108
|
+
tags = ["credentials"]
|
|
109
|
+
|
|
110
|
+
[[policy.tool]]
|
|
111
|
+
name = "host/claude-code/Bash(command:*.aws/*)"
|
|
112
|
+
delta = { audience = ["self"] }
|
|
113
|
+
tags = ["credentials"]
|
|
114
|
+
|
|
115
|
+
[[policy.tool]]
|
|
116
|
+
name = "host/claude-code/Bash(command:*.azure/*)"
|
|
117
|
+
delta = { audience = ["self"] }
|
|
118
|
+
tags = ["credentials"]
|
|
119
|
+
|
|
120
|
+
[[policy.tool]]
|
|
121
|
+
name = "host/claude-code/Bash(command:*.kube/*)"
|
|
122
|
+
delta = { audience = ["self"] }
|
|
123
|
+
tags = ["credentials"]
|
|
124
|
+
|
|
125
|
+
[[policy.tool]]
|
|
126
|
+
name = "host/claude-code/Bash(command:*.gnupg/*)"
|
|
127
|
+
delta = { audience = ["self"] }
|
|
128
|
+
tags = ["credentials"]
|
|
129
|
+
|
|
130
|
+
[[policy.tool]]
|
|
131
|
+
name = "host/claude-code/Bash(command:*.docker/config.json*)"
|
|
132
|
+
delta = { audience = ["self"] }
|
|
133
|
+
tags = ["credentials"]
|
|
134
|
+
|
|
135
|
+
[[policy.tool]]
|
|
136
|
+
name = "host/claude-code/Bash(command:*.config/gh/*)"
|
|
137
|
+
delta = { audience = ["self"] }
|
|
138
|
+
tags = ["credentials"]
|
|
139
|
+
|
|
140
|
+
[[policy.tool]]
|
|
141
|
+
name = "host/claude-code/Bash(command:*.config/gcloud/*)"
|
|
142
|
+
delta = { audience = ["self"] }
|
|
143
|
+
tags = ["credentials"]
|
|
144
|
+
|
|
145
|
+
[[policy.tool]]
|
|
146
|
+
name = "host/claude-code/Bash(command:*.npmrc*)"
|
|
147
|
+
delta = { audience = ["self"] }
|
|
148
|
+
tags = ["credentials"]
|
|
149
|
+
|
|
150
|
+
[[policy.tool]]
|
|
151
|
+
name = "host/claude-code/Bash(command:*.pypirc*)"
|
|
152
|
+
delta = { audience = ["self"] }
|
|
153
|
+
tags = ["credentials"]
|
|
154
|
+
|
|
155
|
+
[[policy.tool]]
|
|
156
|
+
name = "host/claude-code/Bash(command:*.password-store/*)"
|
|
157
|
+
delta = { audience = ["self"] }
|
|
158
|
+
tags = ["credentials"]
|
|
159
|
+
|
|
160
|
+
[[policy.tool]]
|
|
161
|
+
name = "host/claude-code/Bash(command:*.vault-token*)"
|
|
162
|
+
delta = { audience = ["self"] }
|
|
163
|
+
tags = ["credentials"]
|
|
164
|
+
|
|
165
|
+
[[policy.tool]]
|
|
166
|
+
name = "host/claude-code/Bash(command:*/proc/*environ*)"
|
|
167
|
+
delta = { audience = ["self"] }
|
|
168
|
+
tags = ["credentials"]
|
|
169
|
+
|
|
170
|
+
# ─── Bash: trusted documentation domains stay clean ────────────────────────
|
|
171
|
+
#
|
|
172
|
+
# The official default allow-lists trusted docs domains on WebFetch; Pi has
|
|
173
|
+
# no URL-argumented web tool, so the same carve-out is spelled on curl.
|
|
174
|
+
# Reading these keeps the session's label: docs sites whose authors are the
|
|
175
|
+
# platform vendors. Everything else fetched is foreign text (next block).
|
|
176
|
+
# Extend with the domains you trust; wget is not carved out — add mirrors
|
|
177
|
+
# if you use it for docs.
|
|
178
|
+
|
|
179
|
+
[[policy.tool]]
|
|
180
|
+
name = "host/claude-code/Bash(command:*curl*docs.anthropic.com*)"
|
|
181
|
+
delta = {}
|
|
182
|
+
|
|
183
|
+
[[policy.tool]]
|
|
184
|
+
name = "host/claude-code/Bash(command:*curl*code.claude.com*)"
|
|
185
|
+
delta = {}
|
|
186
|
+
|
|
187
|
+
[[policy.tool]]
|
|
188
|
+
name = "host/claude-code/Bash(command:*curl*openappa.com*)"
|
|
189
|
+
delta = {}
|
|
190
|
+
|
|
191
|
+
[[policy.tool]]
|
|
192
|
+
name = "host/claude-code/Bash(command:*curl*nodejs.org*)"
|
|
193
|
+
delta = {}
|
|
194
|
+
|
|
195
|
+
[[policy.tool]]
|
|
196
|
+
name = "host/claude-code/Bash(command:*curl*developer.mozilla.org*)"
|
|
197
|
+
delta = {}
|
|
198
|
+
|
|
199
|
+
[[policy.tool]]
|
|
200
|
+
name = "host/claude-code/Bash(command:*curl*typescriptlang.org*)"
|
|
201
|
+
delta = {}
|
|
202
|
+
|
|
203
|
+
[[policy.tool]]
|
|
204
|
+
name = "host/claude-code/Bash(command:*curl*doc.rust-lang.org*)"
|
|
205
|
+
delta = {}
|
|
206
|
+
|
|
207
|
+
[[policy.tool]]
|
|
208
|
+
name = "host/claude-code/Bash(command:*curl*docs.python.org*)"
|
|
209
|
+
delta = {}
|
|
210
|
+
|
|
211
|
+
# ─── Bash: publishing — tainted data may not leave the machine ─────────────
|
|
212
|
+
#
|
|
213
|
+
# A push, PR, issue, release, or API call carries the session's data to a
|
|
214
|
+
# remote: the injection-to-GitHub exfil channel. Deterministic "ask": the
|
|
215
|
+
# call runs only from a clean (fully trusted) trajectory. Normal `git
|
|
216
|
+
# commit` is NOT fenced — local history stays local. Blocked after
|
|
217
|
+
# research? Start a fresh session and push from there.
|
|
218
|
+
|
|
219
|
+
[[policy.tool]]
|
|
220
|
+
name = "host/claude-code/Bash(command:*git push*)"
|
|
221
|
+
requires = { trust = "trusted" }
|
|
222
|
+
delta = {}
|
|
223
|
+
|
|
224
|
+
[[policy.tool]]
|
|
225
|
+
name = "host/claude-code/Bash(command:*git -* push*)"
|
|
226
|
+
requires = { trust = "trusted" }
|
|
227
|
+
delta = {}
|
|
228
|
+
|
|
229
|
+
[[policy.tool]]
|
|
230
|
+
name = "host/claude-code/Bash(command:*gh pr *)"
|
|
231
|
+
requires = { trust = "trusted" }
|
|
232
|
+
delta = {}
|
|
233
|
+
|
|
234
|
+
[[policy.tool]]
|
|
235
|
+
name = "host/claude-code/Bash(command:*gh issue *)"
|
|
236
|
+
requires = { trust = "trusted" }
|
|
237
|
+
delta = {}
|
|
238
|
+
|
|
239
|
+
[[policy.tool]]
|
|
240
|
+
name = "host/claude-code/Bash(command:*gh release *)"
|
|
241
|
+
requires = { trust = "trusted" }
|
|
242
|
+
delta = {}
|
|
243
|
+
|
|
244
|
+
[[policy.tool]]
|
|
245
|
+
name = "host/claude-code/Bash(command:*gh api *)"
|
|
246
|
+
requires = { trust = "trusted" }
|
|
247
|
+
delta = {}
|
|
248
|
+
|
|
249
|
+
[[policy.tool]]
|
|
250
|
+
name = "host/claude-code/Bash(command:*gh repo *)"
|
|
251
|
+
requires = { trust = "trusted" }
|
|
252
|
+
delta = {}
|
|
253
|
+
|
|
254
|
+
[[policy.tool]]
|
|
255
|
+
name = "host/claude-code/Bash(command:*gh gist *)"
|
|
256
|
+
requires = { trust = "trusted" }
|
|
257
|
+
delta = {}
|
|
258
|
+
|
|
259
|
+
# ─── Bash: fetch-shaped commands are the web source ────────────────────────
|
|
260
|
+
#
|
|
261
|
+
# This is the middle path: not all shell output is foreign — `ls` and
|
|
262
|
+
# `npm test` are yours — but curl/wget return text written by strangers.
|
|
263
|
+
# Only these narrow trust. A session that curled a blog post keeps working
|
|
264
|
+
# everywhere except the fenced sinks (see the fence map).
|
|
265
|
+
|
|
266
|
+
[[policy.tool]]
|
|
267
|
+
name = "host/claude-code/Bash(command:*curl*)"
|
|
268
|
+
delta = { trust = "suspicious" }
|
|
269
|
+
|
|
270
|
+
[[policy.tool]]
|
|
271
|
+
name = "host/claude-code/Bash(command:*wget*)"
|
|
272
|
+
delta = { trust = "suspicious" }
|
|
273
|
+
|
|
274
|
+
# ─── Bash: every other command ─────────────────────────────────────────────
|
|
275
|
+
#
|
|
276
|
+
# Your own machine's output, trusted. (The strict alternative — marking all
|
|
277
|
+
# shell output suspicious — over-taints: one `ls` would refuse every
|
|
278
|
+
# trusted-only sink below forever, with no annotator to tell `ls` from
|
|
279
|
+
# `curl`. Fetch-selectors above are the honest split.)
|
|
280
|
+
#
|
|
281
|
+
# Scope note, cross-checked against community hardening guides: appa is an
|
|
282
|
+
# information-flow engine — it fences where DATA goes, not what a command
|
|
283
|
+
# DOES. Destructive-command deny-lists (`rm -rf /`, `sudo`, `chmod`,
|
|
284
|
+
# shutdown…) that Claude Code / Cursor configs carry are not expressible
|
|
285
|
+
# here (v2 has no static unconditional deny) and are deliberately out of
|
|
286
|
+
# scope: the OS, backups, and you remain that guard.
|
|
287
|
+
|
|
288
|
+
[[policy.tool]]
|
|
289
|
+
name = "host/claude-code/Bash"
|
|
290
|
+
description = "Runs one shell command and returns its output."
|
|
291
|
+
delta = {}
|
|
292
|
+
|
|
293
|
+
# ─── PowerShell: the Bash rules, spelled for Windows hosts ─────────────────
|
|
294
|
+
# Directory rules are written for both separators; `\\` is one literal
|
|
295
|
+
# backslash in a selector.
|
|
296
|
+
|
|
297
|
+
[[policy.tool]]
|
|
298
|
+
name = 'host/claude-code/PowerShell(command:*.env*)'
|
|
299
|
+
delta = { audience = ["self"] }
|
|
300
|
+
tags = ["credentials"]
|
|
301
|
+
|
|
302
|
+
[[policy.tool]]
|
|
303
|
+
name = 'host/claude-code/PowerShell(command:*.ssh/*)'
|
|
304
|
+
delta = { audience = ["self"] }
|
|
305
|
+
tags = ["credentials"]
|
|
306
|
+
|
|
307
|
+
[[policy.tool]]
|
|
308
|
+
name = 'host/claude-code/PowerShell(command:*.ssh\\*)'
|
|
309
|
+
delta = { audience = ["self"] }
|
|
310
|
+
tags = ["credentials"]
|
|
311
|
+
|
|
312
|
+
[[policy.tool]]
|
|
313
|
+
name = 'host/claude-code/PowerShell(command:*id_rsa*)'
|
|
314
|
+
delta = { audience = ["self"] }
|
|
315
|
+
tags = ["credentials"]
|
|
316
|
+
|
|
317
|
+
[[policy.tool]]
|
|
318
|
+
name = 'host/claude-code/PowerShell(command:*id_ed25519*)'
|
|
319
|
+
delta = { audience = ["self"] }
|
|
320
|
+
tags = ["credentials"]
|
|
321
|
+
|
|
322
|
+
[[policy.tool]]
|
|
323
|
+
name = 'host/claude-code/PowerShell(command:*.netrc*)'
|
|
324
|
+
delta = { audience = ["self"] }
|
|
325
|
+
tags = ["credentials"]
|
|
326
|
+
|
|
327
|
+
[[policy.tool]]
|
|
328
|
+
name = 'host/claude-code/PowerShell(command:*.git-credentials*)'
|
|
329
|
+
delta = { audience = ["self"] }
|
|
330
|
+
tags = ["credentials"]
|
|
331
|
+
|
|
332
|
+
[[policy.tool]]
|
|
333
|
+
name = 'host/claude-code/PowerShell(command:*.aws/*)'
|
|
334
|
+
delta = { audience = ["self"] }
|
|
335
|
+
tags = ["credentials"]
|
|
336
|
+
|
|
337
|
+
[[policy.tool]]
|
|
338
|
+
name = 'host/claude-code/PowerShell(command:*.aws\\*)'
|
|
339
|
+
delta = { audience = ["self"] }
|
|
340
|
+
tags = ["credentials"]
|
|
341
|
+
|
|
342
|
+
[[policy.tool]]
|
|
343
|
+
name = 'host/claude-code/PowerShell(command:*.kube/*)'
|
|
344
|
+
delta = { audience = ["self"] }
|
|
345
|
+
tags = ["credentials"]
|
|
346
|
+
|
|
347
|
+
[[policy.tool]]
|
|
348
|
+
name = 'host/claude-code/PowerShell(command:*.kube\\*)'
|
|
349
|
+
delta = { audience = ["self"] }
|
|
350
|
+
tags = ["credentials"]
|
|
351
|
+
|
|
352
|
+
[[policy.tool]]
|
|
353
|
+
name = 'host/claude-code/PowerShell(command:*.gnupg/*)'
|
|
354
|
+
delta = { audience = ["self"] }
|
|
355
|
+
tags = ["credentials"]
|
|
356
|
+
|
|
357
|
+
[[policy.tool]]
|
|
358
|
+
name = 'host/claude-code/PowerShell(command:*.gnupg\\*)'
|
|
359
|
+
delta = { audience = ["self"] }
|
|
360
|
+
tags = ["credentials"]
|
|
361
|
+
|
|
362
|
+
[[policy.tool]]
|
|
363
|
+
name = 'host/claude-code/PowerShell(command:*.docker/config.json*)'
|
|
364
|
+
delta = { audience = ["self"] }
|
|
365
|
+
tags = ["credentials"]
|
|
366
|
+
|
|
367
|
+
[[policy.tool]]
|
|
368
|
+
name = 'host/claude-code/PowerShell(command:*.docker\\config.json*)'
|
|
369
|
+
delta = { audience = ["self"] }
|
|
370
|
+
tags = ["credentials"]
|
|
371
|
+
|
|
372
|
+
[[policy.tool]]
|
|
373
|
+
name = 'host/claude-code/PowerShell(command:*.config/gh/*)'
|
|
374
|
+
delta = { audience = ["self"] }
|
|
375
|
+
tags = ["credentials"]
|
|
376
|
+
|
|
377
|
+
[[policy.tool]]
|
|
378
|
+
name = 'host/claude-code/PowerShell(command:*.config\\gh\\*)'
|
|
379
|
+
delta = { audience = ["self"] }
|
|
380
|
+
tags = ["credentials"]
|
|
381
|
+
|
|
382
|
+
[[policy.tool]]
|
|
383
|
+
name = 'host/claude-code/PowerShell(command:*curl*)'
|
|
384
|
+
delta = { trust = "suspicious" }
|
|
385
|
+
|
|
386
|
+
[[policy.tool]]
|
|
387
|
+
name = 'host/claude-code/PowerShell(command:*wget*)'
|
|
388
|
+
delta = { trust = "suspicious" }
|
|
389
|
+
|
|
390
|
+
[[policy.tool]]
|
|
391
|
+
name = 'host/claude-code/PowerShell(command:*git push*)'
|
|
392
|
+
requires = { trust = "trusted" }
|
|
393
|
+
delta = {}
|
|
394
|
+
|
|
395
|
+
[[policy.tool]]
|
|
396
|
+
name = 'host/claude-code/PowerShell(command:*git -* push*)'
|
|
397
|
+
requires = { trust = "trusted" }
|
|
398
|
+
delta = {}
|
|
399
|
+
|
|
400
|
+
[[policy.tool]]
|
|
401
|
+
name = "host/claude-code/PowerShell"
|
|
402
|
+
description = "Runs one PowerShell command and returns its output."
|
|
403
|
+
delta = {}
|
|
404
|
+
|
|
405
|
+
# ─── Read: the requester's own secrets are `self` data ─────────────────────
|
|
406
|
+
# A selector matches the argument as written, absolute or relative, not the
|
|
407
|
+
# resolved path. Every other path keeps the session's label; no rule here
|
|
408
|
+
# blocks a read or lowers its trust. Pi's argument is `path`.
|
|
409
|
+
|
|
410
|
+
[[policy.tool]]
|
|
411
|
+
name = "host/claude-code/Read(path:*/.env*)"
|
|
412
|
+
delta = { audience = ["self"] }
|
|
413
|
+
|
|
414
|
+
[[policy.tool]]
|
|
415
|
+
name = "host/claude-code/Read(path:.env*)"
|
|
416
|
+
delta = { audience = ["self"] }
|
|
417
|
+
|
|
418
|
+
[[policy.tool]]
|
|
419
|
+
name = "host/claude-code/Read(path:*/.ssh/*)"
|
|
420
|
+
delta = { audience = ["self"] }
|
|
421
|
+
|
|
422
|
+
[[policy.tool]]
|
|
423
|
+
name = "host/claude-code/Read(path:.ssh/*)"
|
|
424
|
+
delta = { audience = ["self"] }
|
|
425
|
+
|
|
426
|
+
[[policy.tool]]
|
|
427
|
+
name = "host/claude-code/Read(path:*/.aws/*)"
|
|
428
|
+
delta = { audience = ["self"] }
|
|
429
|
+
|
|
430
|
+
[[policy.tool]]
|
|
431
|
+
name = "host/claude-code/Read(path:*/.kube/*)"
|
|
432
|
+
delta = { audience = ["self"] }
|
|
433
|
+
|
|
434
|
+
[[policy.tool]]
|
|
435
|
+
name = "host/claude-code/Read(path:*/.gnupg/*)"
|
|
436
|
+
delta = { audience = ["self"] }
|
|
437
|
+
|
|
438
|
+
[[policy.tool]]
|
|
439
|
+
name = "host/claude-code/Read(path:*/.netrc)"
|
|
440
|
+
delta = { audience = ["self"] }
|
|
441
|
+
|
|
442
|
+
[[policy.tool]]
|
|
443
|
+
name = "host/claude-code/Read(path:*/.git-credentials)"
|
|
444
|
+
delta = { audience = ["self"] }
|
|
445
|
+
|
|
446
|
+
[[policy.tool]]
|
|
447
|
+
name = "host/claude-code/Read(path:*/.npmrc)"
|
|
448
|
+
delta = { audience = ["self"] }
|
|
449
|
+
|
|
450
|
+
[[policy.tool]]
|
|
451
|
+
name = "host/claude-code/Read(path:*/.vault-token)"
|
|
452
|
+
delta = { audience = ["self"] }
|
|
453
|
+
|
|
454
|
+
[[policy.tool]]
|
|
455
|
+
name = "host/claude-code/Read(path:*/.password-store/*)"
|
|
456
|
+
delta = { audience = ["self"] }
|
|
457
|
+
|
|
458
|
+
[[policy.tool]]
|
|
459
|
+
name = "host/claude-code/Read"
|
|
460
|
+
description = "Reads a file from the local filesystem."
|
|
461
|
+
delta = {}
|
|
462
|
+
|
|
463
|
+
# ─── Grep / Glob / LS: local search over this user's files ─────────────────
|
|
464
|
+
|
|
465
|
+
[[policy.tool]]
|
|
466
|
+
name = "host/claude-code/Grep"
|
|
467
|
+
description = "Searches file contents with a pattern."
|
|
468
|
+
delta = {}
|
|
469
|
+
|
|
470
|
+
[[policy.tool]]
|
|
471
|
+
name = "host/claude-code/Glob"
|
|
472
|
+
description = "Finds files by name pattern."
|
|
473
|
+
delta = {}
|
|
474
|
+
|
|
475
|
+
[[policy.tool]]
|
|
476
|
+
name = "host/claude-code/LS"
|
|
477
|
+
description = "Lists a directory."
|
|
478
|
+
delta = {}
|
|
479
|
+
|
|
480
|
+
# ─── Edit: research lands in docs, it does not rewrite code ────────────────
|
|
481
|
+
#
|
|
482
|
+
# Documentation first (the landing zone): docs directories, openspec, and
|
|
483
|
+
# markdown anywhere stay editable even by a tainted session — that is where
|
|
484
|
+
# research output belongs. Everything else below requires a fully trusted
|
|
485
|
+
# trajectory: a session that curled a blog post may create new files (see
|
|
486
|
+
# Write) and edit docs, but not touch existing code. This mirrors the
|
|
487
|
+
# read-only scout / full Engineer split: research sessions report, clean
|
|
488
|
+
# sessions build.
|
|
489
|
+
|
|
490
|
+
[[policy.tool]]
|
|
491
|
+
name = "host/claude-code/Edit(path:*/docs/*)"
|
|
492
|
+
delta = {}
|
|
493
|
+
|
|
494
|
+
[[policy.tool]]
|
|
495
|
+
name = "host/claude-code/Edit(path:docs/*)"
|
|
496
|
+
delta = {}
|
|
497
|
+
|
|
498
|
+
[[policy.tool]]
|
|
499
|
+
name = "host/claude-code/Edit(path:*/openspec/*)"
|
|
500
|
+
delta = {}
|
|
501
|
+
|
|
502
|
+
[[policy.tool]]
|
|
503
|
+
name = "host/claude-code/Edit(path:openspec/*)"
|
|
504
|
+
delta = {}
|
|
505
|
+
|
|
506
|
+
[[policy.tool]]
|
|
507
|
+
name = "host/claude-code/Edit(path:*.md)"
|
|
508
|
+
delta = {}
|
|
509
|
+
|
|
510
|
+
[[policy.tool]]
|
|
511
|
+
name = "host/claude-code/Edit(path:*.mdx)"
|
|
512
|
+
delta = {}
|
|
513
|
+
|
|
514
|
+
# The policy guards itself: a session may not rewrite the rules that bind
|
|
515
|
+
# it, tainted or not. Edit policies by hand, in a fresh session.
|
|
516
|
+
|
|
517
|
+
[[policy.tool]]
|
|
518
|
+
name = "host/claude-code/Edit(path:*/appa/appa.toml)"
|
|
519
|
+
requires = { trust = "trusted" }
|
|
520
|
+
delta = {}
|
|
521
|
+
|
|
522
|
+
[[policy.tool]]
|
|
523
|
+
name = "host/claude-code/Edit(path:*/appa/batteries/*)"
|
|
524
|
+
requires = { trust = "trusted" }
|
|
525
|
+
delta = {}
|
|
526
|
+
|
|
527
|
+
[[policy.tool]]
|
|
528
|
+
name = "host/claude-code/Edit"
|
|
529
|
+
description = "Applies targeted edits to an existing file."
|
|
530
|
+
requires = { trust = "trusted" }
|
|
531
|
+
delta = {}
|
|
532
|
+
|
|
533
|
+
# ─── Write: create freely; protect what executes later ─────────────────────
|
|
534
|
+
#
|
|
535
|
+
# `write` is the create path (new files, notes, reports) and stays open —
|
|
536
|
+
# with two families fenced. First, anything credential-shaped (an untrusted
|
|
537
|
+
# flow must not be laundered into secrets). Second, the files that execute
|
|
538
|
+
# later with more privilege than the session: tests (a poisoned test that
|
|
539
|
+
# always passes), devops (Makefile, justfile, scripts/, CI workflows,
|
|
540
|
+
# Dockerfiles), and infra (terraform, k8s, helm, ansible, pulumi). Poison
|
|
541
|
+
# that persists or executes is the memory-poisoning problem, spelled for
|
|
542
|
+
# files. NOTE: `write` cannot distinguish create from overwrite — a tainted
|
|
543
|
+
# overwrite of unprotected src is a known hole (documented, not fenced).
|
|
544
|
+
|
|
545
|
+
[[policy.tool]]
|
|
546
|
+
name = "host/claude-code/Write(path:*/.env*)"
|
|
547
|
+
requires = { trust = "trusted" }
|
|
548
|
+
delta = {}
|
|
549
|
+
|
|
550
|
+
[[policy.tool]]
|
|
551
|
+
name = "host/claude-code/Write(path:.env*)"
|
|
552
|
+
requires = { trust = "trusted" }
|
|
553
|
+
delta = {}
|
|
554
|
+
|
|
555
|
+
[[policy.tool]]
|
|
556
|
+
name = "host/claude-code/Write(path:*/.ssh/*)"
|
|
557
|
+
requires = { trust = "trusted" }
|
|
558
|
+
delta = {}
|
|
559
|
+
|
|
560
|
+
[[policy.tool]]
|
|
561
|
+
name = "host/claude-code/Write(path:*/.aws/*)"
|
|
562
|
+
requires = { trust = "trusted" }
|
|
563
|
+
delta = {}
|
|
564
|
+
|
|
565
|
+
[[policy.tool]]
|
|
566
|
+
name = "host/claude-code/Write(path:*/.kube/*)"
|
|
567
|
+
requires = { trust = "trusted" }
|
|
568
|
+
delta = {}
|
|
569
|
+
|
|
570
|
+
[[policy.tool]]
|
|
571
|
+
name = "host/claude-code/Write(path:*/.gnupg/*)"
|
|
572
|
+
requires = { trust = "trusted" }
|
|
573
|
+
delta = {}
|
|
574
|
+
|
|
575
|
+
[[policy.tool]]
|
|
576
|
+
name = "host/claude-code/Write(path:*/.netrc)"
|
|
577
|
+
requires = { trust = "trusted" }
|
|
578
|
+
delta = {}
|
|
579
|
+
|
|
580
|
+
[[policy.tool]]
|
|
581
|
+
name = "host/claude-code/Write(path:*/.git-credentials)"
|
|
582
|
+
requires = { trust = "trusted" }
|
|
583
|
+
delta = {}
|
|
584
|
+
|
|
585
|
+
[[policy.tool]]
|
|
586
|
+
name = "host/claude-code/Write(path:*/.npmrc)"
|
|
587
|
+
requires = { trust = "trusted" }
|
|
588
|
+
delta = {}
|
|
589
|
+
|
|
590
|
+
[[policy.tool]]
|
|
591
|
+
name = "host/claude-code/Write(path:*/.pypirc)"
|
|
592
|
+
requires = { trust = "trusted" }
|
|
593
|
+
delta = {}
|
|
594
|
+
|
|
595
|
+
[[policy.tool]]
|
|
596
|
+
name = "host/claude-code/Write(path:*/.vault-token)"
|
|
597
|
+
requires = { trust = "trusted" }
|
|
598
|
+
delta = {}
|
|
599
|
+
|
|
600
|
+
[[policy.tool]]
|
|
601
|
+
name = "host/claude-code/Write(path:*/.password-store/*)"
|
|
602
|
+
requires = { trust = "trusted" }
|
|
603
|
+
delta = {}
|
|
604
|
+
|
|
605
|
+
[[policy.tool]]
|
|
606
|
+
name = "host/claude-code/Write(path:*id_rsa*)"
|
|
607
|
+
requires = { trust = "trusted" }
|
|
608
|
+
delta = {}
|
|
609
|
+
|
|
610
|
+
[[policy.tool]]
|
|
611
|
+
name = "host/claude-code/Write(path:*id_ed25519*)"
|
|
612
|
+
requires = { trust = "trusted" }
|
|
613
|
+
delta = {}
|
|
614
|
+
|
|
615
|
+
[[policy.tool]]
|
|
616
|
+
name = "host/claude-code/Write(path:*/id_*)"
|
|
617
|
+
requires = { trust = "trusted" }
|
|
618
|
+
delta = {}
|
|
619
|
+
|
|
620
|
+
[[policy.tool]]
|
|
621
|
+
name = "host/claude-code/Write(path:*credentials*)"
|
|
622
|
+
requires = { trust = "trusted" }
|
|
623
|
+
delta = {}
|
|
624
|
+
|
|
625
|
+
[[policy.tool]]
|
|
626
|
+
name = "host/claude-code/Write(path:*secrets*)"
|
|
627
|
+
requires = { trust = "trusted" }
|
|
628
|
+
delta = {}
|
|
629
|
+
|
|
630
|
+
[[policy.tool]]
|
|
631
|
+
name = "host/claude-code/Write(path:*token)"
|
|
632
|
+
requires = { trust = "trusted" }
|
|
633
|
+
delta = {}
|
|
634
|
+
|
|
635
|
+
[[policy.tool]]
|
|
636
|
+
name = "host/claude-code/Write(path:*auth.json)"
|
|
637
|
+
requires = { trust = "trusted" }
|
|
638
|
+
delta = {}
|
|
639
|
+
|
|
640
|
+
[[policy.tool]]
|
|
641
|
+
name = "host/claude-code/Write(path:*.pem)"
|
|
642
|
+
requires = { trust = "trusted" }
|
|
643
|
+
delta = {}
|
|
644
|
+
|
|
645
|
+
[[policy.tool]]
|
|
646
|
+
name = "host/claude-code/Write(path:*.key)"
|
|
647
|
+
requires = { trust = "trusted" }
|
|
648
|
+
delta = {}
|
|
649
|
+
|
|
650
|
+
[[policy.tool]]
|
|
651
|
+
name = "host/claude-code/Write(path:*.p12)"
|
|
652
|
+
requires = { trust = "trusted" }
|
|
653
|
+
delta = {}
|
|
654
|
+
|
|
655
|
+
[[policy.tool]]
|
|
656
|
+
name = "host/claude-code/Write(path:*.pfx)"
|
|
657
|
+
requires = { trust = "trusted" }
|
|
658
|
+
delta = {}
|
|
659
|
+
|
|
660
|
+
[[policy.tool]]
|
|
661
|
+
name = "host/claude-code/Write(path:*.jks)"
|
|
662
|
+
requires = { trust = "trusted" }
|
|
663
|
+
delta = {}
|
|
664
|
+
|
|
665
|
+
[[policy.tool]]
|
|
666
|
+
name = "host/claude-code/Write(path:*.keystore)"
|
|
667
|
+
requires = { trust = "trusted" }
|
|
668
|
+
delta = {}
|
|
669
|
+
|
|
670
|
+
[[policy.tool]]
|
|
671
|
+
name = "host/claude-code/Write(path:/etc/ssh/*)"
|
|
672
|
+
requires = { trust = "trusted" }
|
|
673
|
+
delta = {}
|
|
674
|
+
|
|
675
|
+
[[policy.tool]]
|
|
676
|
+
name = "host/claude-code/Write(path:/etc/sudoers*)"
|
|
677
|
+
requires = { trust = "trusted" }
|
|
678
|
+
delta = {}
|
|
679
|
+
|
|
680
|
+
[[policy.tool]]
|
|
681
|
+
name = "host/claude-code/Write(path:*_history)"
|
|
682
|
+
requires = { trust = "trusted" }
|
|
683
|
+
delta = {}
|
|
684
|
+
|
|
685
|
+
# — files that execute later: tests —
|
|
686
|
+
|
|
687
|
+
[[policy.tool]]
|
|
688
|
+
name = "host/claude-code/Write(path:*/test/*)"
|
|
689
|
+
requires = { trust = "trusted" }
|
|
690
|
+
delta = {}
|
|
691
|
+
|
|
692
|
+
[[policy.tool]]
|
|
693
|
+
name = "host/claude-code/Write(path:*/tests/*)"
|
|
694
|
+
requires = { trust = "trusted" }
|
|
695
|
+
delta = {}
|
|
696
|
+
|
|
697
|
+
[[policy.tool]]
|
|
698
|
+
name = "host/claude-code/Write(path:test/*)"
|
|
699
|
+
requires = { trust = "trusted" }
|
|
700
|
+
delta = {}
|
|
701
|
+
|
|
702
|
+
[[policy.tool]]
|
|
703
|
+
name = "host/claude-code/Write(path:tests/*)"
|
|
704
|
+
requires = { trust = "trusted" }
|
|
705
|
+
delta = {}
|
|
706
|
+
|
|
707
|
+
[[policy.tool]]
|
|
708
|
+
name = "host/claude-code/Write(path:*.test.*)"
|
|
709
|
+
requires = { trust = "trusted" }
|
|
710
|
+
delta = {}
|
|
711
|
+
|
|
712
|
+
[[policy.tool]]
|
|
713
|
+
name = "host/claude-code/Write(path:*.spec.*)"
|
|
714
|
+
requires = { trust = "trusted" }
|
|
715
|
+
delta = {}
|
|
716
|
+
|
|
717
|
+
[[policy.tool]]
|
|
718
|
+
name = "host/claude-code/Write(path:*_test.*)"
|
|
719
|
+
requires = { trust = "trusted" }
|
|
720
|
+
delta = {}
|
|
721
|
+
|
|
722
|
+
[[policy.tool]]
|
|
723
|
+
name = "host/claude-code/Write(path:*_spec.*)"
|
|
724
|
+
requires = { trust = "trusted" }
|
|
725
|
+
delta = {}
|
|
726
|
+
|
|
727
|
+
[[policy.tool]]
|
|
728
|
+
name = "host/claude-code/Write(path:*/__tests__/*)"
|
|
729
|
+
requires = { trust = "trusted" }
|
|
730
|
+
delta = {}
|
|
731
|
+
|
|
732
|
+
# — files that execute later: devops / build / CI —
|
|
733
|
+
|
|
734
|
+
[[policy.tool]]
|
|
735
|
+
name = "host/claude-code/Write(path:*Makefile*)"
|
|
736
|
+
requires = { trust = "trusted" }
|
|
737
|
+
delta = {}
|
|
738
|
+
|
|
739
|
+
[[policy.tool]]
|
|
740
|
+
name = "host/claude-code/Write(path:*.mk)"
|
|
741
|
+
requires = { trust = "trusted" }
|
|
742
|
+
delta = {}
|
|
743
|
+
|
|
744
|
+
[[policy.tool]]
|
|
745
|
+
name = "host/claude-code/Write(path:*justfile*)"
|
|
746
|
+
requires = { trust = "trusted" }
|
|
747
|
+
delta = {}
|
|
748
|
+
|
|
749
|
+
[[policy.tool]]
|
|
750
|
+
name = "host/claude-code/Write(path:*Justfile*)"
|
|
751
|
+
requires = { trust = "trusted" }
|
|
752
|
+
delta = {}
|
|
753
|
+
|
|
754
|
+
[[policy.tool]]
|
|
755
|
+
name = "host/claude-code/Write(path:*/scripts/*)"
|
|
756
|
+
requires = { trust = "trusted" }
|
|
757
|
+
delta = {}
|
|
758
|
+
|
|
759
|
+
[[policy.tool]]
|
|
760
|
+
name = "host/claude-code/Write(path:scripts/*)"
|
|
761
|
+
requires = { trust = "trusted" }
|
|
762
|
+
delta = {}
|
|
763
|
+
|
|
764
|
+
[[policy.tool]]
|
|
765
|
+
name = "host/claude-code/Write(path:*/.github/workflows/*)"
|
|
766
|
+
requires = { trust = "trusted" }
|
|
767
|
+
delta = {}
|
|
768
|
+
|
|
769
|
+
[[policy.tool]]
|
|
770
|
+
name = "host/claude-code/Write(path:*.gitlab-ci*)"
|
|
771
|
+
requires = { trust = "trusted" }
|
|
772
|
+
delta = {}
|
|
773
|
+
|
|
774
|
+
[[policy.tool]]
|
|
775
|
+
name = "host/claude-code/Write(path:*/.circleci/*)"
|
|
776
|
+
requires = { trust = "trusted" }
|
|
777
|
+
delta = {}
|
|
778
|
+
|
|
779
|
+
[[policy.tool]]
|
|
780
|
+
name = "host/claude-code/Write(path:*Dockerfile*)"
|
|
781
|
+
requires = { trust = "trusted" }
|
|
782
|
+
delta = {}
|
|
783
|
+
|
|
784
|
+
[[policy.tool]]
|
|
785
|
+
name = "host/claude-code/Write(path:*docker-compose*)"
|
|
786
|
+
requires = { trust = "trusted" }
|
|
787
|
+
delta = {}
|
|
788
|
+
|
|
789
|
+
# — files that execute later: infrastructure —
|
|
790
|
+
|
|
791
|
+
[[policy.tool]]
|
|
792
|
+
name = "host/claude-code/Write(path:*.tf)"
|
|
793
|
+
requires = { trust = "trusted" }
|
|
794
|
+
delta = {}
|
|
795
|
+
|
|
796
|
+
[[policy.tool]]
|
|
797
|
+
name = "host/claude-code/Write(path:*.tfvars)"
|
|
798
|
+
requires = { trust = "trusted" }
|
|
799
|
+
delta = {}
|
|
800
|
+
|
|
801
|
+
[[policy.tool]]
|
|
802
|
+
name = "host/claude-code/Write(path:*/terraform/*)"
|
|
803
|
+
requires = { trust = "trusted" }
|
|
804
|
+
delta = {}
|
|
805
|
+
|
|
806
|
+
[[policy.tool]]
|
|
807
|
+
name = "host/claude-code/Write(path:*/k8s/*)"
|
|
808
|
+
requires = { trust = "trusted" }
|
|
809
|
+
delta = {}
|
|
810
|
+
|
|
811
|
+
[[policy.tool]]
|
|
812
|
+
name = "host/claude-code/Write(path:*/helm/*)"
|
|
813
|
+
requires = { trust = "trusted" }
|
|
814
|
+
delta = {}
|
|
815
|
+
|
|
816
|
+
[[policy.tool]]
|
|
817
|
+
name = "host/claude-code/Write(path:*/ansible/*)"
|
|
818
|
+
requires = { trust = "trusted" }
|
|
819
|
+
delta = {}
|
|
820
|
+
|
|
821
|
+
[[policy.tool]]
|
|
822
|
+
name = "host/claude-code/Write(path:*/pulumi/*)"
|
|
823
|
+
requires = { trust = "trusted" }
|
|
824
|
+
delta = {}
|
|
825
|
+
|
|
826
|
+
[[policy.tool]]
|
|
827
|
+
name = "host/claude-code/Write(path:*.cloudformation*)"
|
|
828
|
+
requires = { trust = "trusted" }
|
|
829
|
+
delta = {}
|
|
830
|
+
|
|
831
|
+
# — the policy guards itself, for writes too —
|
|
832
|
+
|
|
833
|
+
[[policy.tool]]
|
|
834
|
+
name = "host/claude-code/Write(path:*/appa/appa.toml)"
|
|
835
|
+
requires = { trust = "trusted" }
|
|
836
|
+
delta = {}
|
|
837
|
+
|
|
838
|
+
[[policy.tool]]
|
|
839
|
+
name = "host/claude-code/Write(path:*/appa/batteries/*)"
|
|
840
|
+
requires = { trust = "trusted" }
|
|
841
|
+
delta = {}
|
|
842
|
+
|
|
843
|
+
[[policy.tool]]
|
|
844
|
+
name = "host/claude-code/Write"
|
|
845
|
+
description = "Writes a file to the local filesystem (create path)."
|
|
846
|
+
delta = {}
|
|
847
|
+
|
|
848
|
+
# ─── Web sources: foreign text, and a query that leaves the machine ────────
|
|
849
|
+
#
|
|
850
|
+
# The official default's dual rule, spelled for Pi's tools: the content is
|
|
851
|
+
# suspicious (someone else wrote it), and the query itself is a public sink
|
|
852
|
+
# — so a session that has read `.env` (self-narrowed) is refused web
|
|
853
|
+
# research until it can prove the query shareable, which it cannot. Fresh
|
|
854
|
+
# sessions start public and research freely.
|
|
855
|
+
|
|
856
|
+
[[policy.tool]]
|
|
857
|
+
name = "host/claude-code/web_explore"
|
|
858
|
+
description = "Researches a web question with search and fetch passes."
|
|
859
|
+
requires = { audience = { contains = ["public"] } }
|
|
860
|
+
delta = { trust = "suspicious" }
|
|
861
|
+
|
|
862
|
+
[[policy.tool]]
|
|
863
|
+
name = "host/claude-code/resolve-library-id"
|
|
864
|
+
description = "Resolves a package name to a Context7 library ID."
|
|
865
|
+
requires = { audience = { contains = ["public"] } }
|
|
866
|
+
delta = { trust = "suspicious" }
|
|
867
|
+
|
|
868
|
+
[[policy.tool]]
|
|
869
|
+
name = "host/claude-code/query-docs"
|
|
870
|
+
description = "Queries Context7 documentation for a library."
|
|
871
|
+
requires = { audience = { contains = ["public"] } }
|
|
872
|
+
delta = { trust = "suspicious" }
|
|
873
|
+
|
|
874
|
+
# codemode composes other tools' results; each nested tool call is itself
|
|
875
|
+
# gated (they are events in the same trajectory), so taint enters at the
|
|
876
|
+
# nested source, not here. Marking codemode itself suspicious would
|
|
877
|
+
# over-taint every later edit after routine script use.
|
|
878
|
+
|
|
879
|
+
[[policy.tool]]
|
|
880
|
+
name = "host/claude-code/codemode"
|
|
881
|
+
description = "Runs JavaScript that calls other tools."
|
|
882
|
+
delta = {}
|
|
883
|
+
|
|
884
|
+
# ─── Subagent delegation ───────────────────────────────────────────────────
|
|
885
|
+
#
|
|
886
|
+
# Spawning is mediated as a plain tool call. `context_control` below makes
|
|
887
|
+
# the runtime treat the child's context as exactly the spawn call's prompt,
|
|
888
|
+
# so the child's return is checked as a branch of this trajectory — with
|
|
889
|
+
# `attest-schema` available to let a schema-matching structured return
|
|
890
|
+
# cross at this session's own trust.
|
|
891
|
+
|
|
892
|
+
[[policy.tool]]
|
|
893
|
+
name = "host/claude-code/subagent"
|
|
894
|
+
description = "Delegates work to a specialized subagent."
|
|
895
|
+
delta = {}
|
|
896
|
+
|
|
897
|
+
# ─── Harness bookkeeping: session-local state and memory ───────────────────
|
|
898
|
+
#
|
|
899
|
+
# LSP, focus, and session tools mutate nothing outside the session. Memory
|
|
900
|
+
# READS are open (your own store). Memory WRITES are fenced: a session
|
|
901
|
+
# carrying suspicious data may not persist it as trusted memory — poisoned
|
|
902
|
+
# learnings would outlive the session and be trusted by every later one.
|
|
903
|
+
# Cost: after heavy web research, save memories from a fresh session (or
|
|
904
|
+
# relax this rule if your scouts must save mid-research — it is a one-block
|
|
905
|
+
# delete).
|
|
906
|
+
|
|
907
|
+
[[policy.tool]]
|
|
908
|
+
name = "host/claude-code/lsp_diagnostics"
|
|
909
|
+
description = "Runs language-server diagnostics."
|
|
910
|
+
delta = {}
|
|
911
|
+
|
|
912
|
+
[[policy.tool]]
|
|
913
|
+
name = "host/claude-code/lsp_fix"
|
|
914
|
+
description = "Applies a language-server source fix."
|
|
915
|
+
delta = {}
|
|
916
|
+
|
|
917
|
+
[[policy.tool]]
|
|
918
|
+
name = "host/claude-code/work_focus"
|
|
919
|
+
description = "Shows or sets ephemeral session work metadata."
|
|
920
|
+
delta = {}
|
|
921
|
+
|
|
922
|
+
[[policy.tool]]
|
|
923
|
+
name = "host/claude-code/stage"
|
|
924
|
+
description = "Declares the current session stage."
|
|
925
|
+
delta = {}
|
|
926
|
+
|
|
927
|
+
[[policy.tool]]
|
|
928
|
+
name = "host/claude-code/subject"
|
|
929
|
+
description = "Shows or sets the session subject."
|
|
930
|
+
delta = {}
|
|
931
|
+
|
|
932
|
+
[[policy.tool]]
|
|
933
|
+
name = "host/claude-code/openspec_focus"
|
|
934
|
+
description = "Shows or sets OpenSpec Task focus."
|
|
935
|
+
delta = {}
|
|
936
|
+
|
|
937
|
+
[[policy.tool]]
|
|
938
|
+
name = "host/claude-code/mem_search"
|
|
939
|
+
description = "Searches Engram memory."
|
|
940
|
+
delta = {}
|
|
941
|
+
|
|
942
|
+
[[policy.tool]]
|
|
943
|
+
name = "host/claude-code/mem_save"
|
|
944
|
+
description = "Saves an Engram observation."
|
|
945
|
+
requires = { trust = "trusted" }
|
|
946
|
+
delta = {}
|
|
947
|
+
|
|
948
|
+
[[policy.tool]]
|
|
949
|
+
name = "host/claude-code/mem_update"
|
|
950
|
+
description = "Updates an Engram observation."
|
|
951
|
+
requires = { trust = "trusted" }
|
|
952
|
+
delta = {}
|
|
953
|
+
|
|
954
|
+
[[policy.tool]]
|
|
955
|
+
name = "host/claude-code/mem_capture_passive"
|
|
956
|
+
description = "Persists passive learnings to Engram."
|
|
957
|
+
requires = { trust = "trusted" }
|
|
958
|
+
delta = {}
|
|
959
|
+
|
|
960
|
+
[[policy.tool]]
|
|
961
|
+
name = "host/claude-code/mem_delete"
|
|
962
|
+
description = "Deletes an Engram observation."
|
|
963
|
+
delta = {}
|
|
964
|
+
|
|
965
|
+
[[policy.tool]]
|
|
966
|
+
name = "host/claude-code/mem_context"
|
|
967
|
+
description = "Loads Engram context."
|
|
968
|
+
delta = {}
|
|
969
|
+
|
|
970
|
+
[[policy.tool]]
|
|
971
|
+
name = "host/claude-code/mem_stats"
|
|
972
|
+
description = "Reports Engram statistics."
|
|
973
|
+
delta = {}
|
|
974
|
+
|
|
975
|
+
[[policy.tool]]
|
|
976
|
+
name = "host/claude-code/mem_timeline"
|
|
977
|
+
description = "Shows an Engram observation timeline."
|
|
978
|
+
delta = {}
|
|
979
|
+
|
|
980
|
+
[[policy.tool]]
|
|
981
|
+
name = "host/claude-code/mem_get_observation"
|
|
982
|
+
description = "Fetches one Engram observation."
|
|
983
|
+
delta = {}
|
|
984
|
+
|
|
985
|
+
[[policy.tool]]
|
|
986
|
+
name = "host/claude-code/mem_list_projects"
|
|
987
|
+
description = "Lists Engram projects."
|
|
988
|
+
delta = {}
|
|
989
|
+
|
|
990
|
+
[[policy.tool]]
|
|
991
|
+
name = "host/claude-code/mem_current_project"
|
|
992
|
+
description = "Reports the current Engram project."
|
|
993
|
+
delta = {}
|
|
994
|
+
|
|
995
|
+
[[policy.tool]]
|
|
996
|
+
name = "host/claude-code/mem_doctor"
|
|
997
|
+
description = "Runs Engram diagnostics."
|
|
998
|
+
delta = {}
|
|
999
|
+
|
|
1000
|
+
# ─── Sanitizers ────────────────────────────────────────────────────────────
|
|
1001
|
+
#
|
|
1002
|
+
# redact-secrets masks private-key blocks, well-known token shapes, the AWS
|
|
1003
|
+
# secret access key, secret-named assignment values, and long high-entropy
|
|
1004
|
+
# runs. It is offered for every withheld Bash/PowerShell result (see
|
|
1005
|
+
# confined_results below), so a credential-narrowed command still returns
|
|
1006
|
+
# useful, masked output. It runs inside the runtime.
|
|
1007
|
+
|
|
1008
|
+
[[policy.sanitizer]]
|
|
1009
|
+
name = "redact-secrets"
|
|
1010
|
+
on = ["tool_output"]
|
|
1011
|
+
|
|
1012
|
+
[policy.sanitizer.permits]
|
|
1013
|
+
audience = { from = ["self"], to = ["public"] }
|
|
1014
|
+
|
|
1015
|
+
# attest-schema is the reserved builtin for a structured subagent return: the
|
|
1016
|
+
# spawn call declares a JSON schema, and a final message matching it crosses
|
|
1017
|
+
# at this session's own trust instead of the rank the subagent's reads left
|
|
1018
|
+
# it at. Runs in the runtime itself; needs no externals entry.
|
|
1019
|
+
|
|
1020
|
+
[[policy.sanitizer]]
|
|
1021
|
+
name = "attest-schema"
|
|
1022
|
+
on = ["tool_output"]
|
|
1023
|
+
|
|
1024
|
+
[policy.sanitizer.permits]
|
|
1025
|
+
trust = { from = "suspicious", to = "trusted" }
|
|
1026
|
+
|
|
1027
|
+
# ─── Deployment: how subagents start, which results are held back ──────────
|
|
1028
|
+
|
|
1029
|
+
[policy.deployment]
|
|
1030
|
+
context_control = true
|
|
1031
|
+
confined_results = ["host/claude-code/Bash", "host/claude-code/PowerShell"]
|
|
1032
|
+
|
|
1033
|
+
# ─── Externals: only runtime builtins, nothing host-coupled ────────────────
|
|
1034
|
+
# timeout_ms and max_body_bytes are required fields of [externals].
|
|
1035
|
+
|
|
1036
|
+
[externals]
|
|
1037
|
+
timeout_ms = 5000
|
|
1038
|
+
max_body_bytes = 65536
|
|
1039
|
+
|
|
1040
|
+
[externals.sanitizers.redact-secrets]
|
|
1041
|
+
builtin = "redact-secrets"
|
|
1042
|
+
|
|
1043
|
+
# ─── Reporting ─────────────────────────────────────────────────────────────
|
|
1044
|
+
|
|
1045
|
+
[reporting]
|
|
1046
|
+
agent_yell = false
|
|
1047
|
+
|
|
1048
|
+
# ─── Undeclared tools ──────────────────────────────────────────────────────
|
|
1049
|
+
#
|
|
1050
|
+
# Deny-by-default, and under static rules the only stance: appa requires a
|
|
1051
|
+
# wildcard `*` entry to name an annotator and forbids static delta on it.
|
|
1052
|
+
# A tool this file does not name is refused before execution, with the
|
|
1053
|
+
# runtime's reason — declare new MCP tools by copying a block above, under
|
|
1054
|
+
# their verbatim Pi name.
|
|
1055
|
+
#
|
|
1056
|
+
# If you want per-call classification of unknown tools instead, register a
|
|
1057
|
+
# model annotator (builtin = "llm", see the OpenAPPA policy reference) and
|
|
1058
|
+
# bind the wildcard to it. The trade is reachability and latency in the
|
|
1059
|
+
# decision path — the host-coupled-annotator failure this policy exists to
|
|
1060
|
+
# avoid — so it stays commented out:
|
|
1061
|
+
#
|
|
1062
|
+
# [[policy.annotator]]
|
|
1063
|
+
# name = "classify-unknown"
|
|
1064
|
+
# builtin = "llm"
|
|
1065
|
+
#
|
|
1066
|
+
# [[policy.tool]]
|
|
1067
|
+
# name = "*"
|
|
1068
|
+
# annotator = "classify-unknown"
|