You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 62add72
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/spec/12-permissions.md
+25-14Lines changed: 25 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,7 +32,11 @@ type Ruleset []Rule
32
32
33
33
### Edit Tool Normalization
34
34
35
-
Tools `edit`, `write`, and `apply_patch` all map to the `"edit"` permission when checking disabled status via `Disabled()`. This ensures a single `deny edit *` rule blocks all file-writing tools.
35
+
Tools `edit`, `write`, `apply_patch`, and `todowrite` all map to the `"edit"` permission when checking disabled status via `Disabled()`. This ensures a single `deny edit *` rule blocks all file-writing tools.
36
+
37
+
### Permission Aliases
38
+
39
+
`bash`↔`shell` and `list`↔`glob` are aliases. `Evaluate` and `Disabled` expand both sides so a rule written as `allow bash *` matches an Ask with permission `shell` (and vice versa).
DefaultRules are automatically prepended during evaluation. They establish the baseline: file reads are allowed, but `.env*` files, web fetches, doom loop overrides, guardrail bypasses, and external directory access all require user approval.
52
+
DefaultRules are automatically prepended during evaluation. They establish the baseline: file reads are allowed, but `.env*` files, web fetches, and external directory access all require user approval.
53
+
54
+
Doom-loop detection is a hard-stop in the session processor (`checkDoomLoop`), not a permission Ask. Destructive shell commands use the `destructive-shell` permission from the shell/monitor tools.
51
55
52
56
## 12.3 Evaluation
53
57
@@ -87,7 +91,7 @@ Concatenates multiple rulesets into a single flat slice. Order is preserved -- e
Returns the set of tool names that are globally denied (pattern `"*"`, action `"deny"`). For tools in the `editTools` map (`edit`, `write`, `apply_patch`), the permission is normalized to `"edit"` before checking.
94
+
Returns the set of tool names that are globally denied (pattern `"*"`, action `"deny"`). For tools in the `editTools` map (`edit`, `write`, `apply_patch`, `todowrite`), the permission is normalized to `"edit"` before checking.
91
95
92
96
## 12.4 Wildcard Matching
93
97
@@ -199,23 +203,26 @@ type Service struct {
199
203
```
200
204
1. Lock mutex
201
205
2. If service is closed -> return ErrClosed
202
-
3. For each pattern in input.Patterns:
206
+
3. If Patterns is empty -> unlock, return ErrDenied (fail closed)
207
+
4. For each pattern in input.Patterns:
203
208
a. Evaluate(permission, pattern, baseRules, input.Ruleset, approved)
204
209
b. If any rule -> deny: unlock, return DeniedError (with matching rules)
205
210
c. If any rule -> ask: mark needsAsk = true
206
-
4. If all rules -> allow: unlock, return nil
207
-
5. Generate request ID (ascending sortable, "permission" prefix)
208
-
6. Create pending entry with buffered reply channel (cap 1)
209
-
7. Store in pending map
210
-
8. Unlock mutex
211
-
9. Publish "permission.asked" event on bus
212
-
10. Select:
211
+
5. If all rules -> allow: unlock, return nil
212
+
6. Generate request ID (ascending sortable, "permission" prefix)
213
+
7. Create pending entry with buffered reply channel (cap 1)
214
+
8. Store in pending map
215
+
9. Unlock mutex
216
+
10. Publish "permission.asked" event on bus
217
+
11. Select:
213
218
- Reply received on channel -> return result.err
214
219
- Context cancelled -> clean up pending entry, return ctx.Err()
215
220
```
216
221
217
222
The ask is a blocking call -- the goroutine waits on a channel until either the user replies or the context is cancelled.
218
223
224
+
Tool execution passes path or command strings as Patterns (via `askPatterns` in `internal/tool`), not just the tool name, so rules like `read .env*` match correctly.
225
+
219
226
## 12.7 Reply Flow
220
227
221
228
`Service.RespondToAsk(input)` handles user replies.
@@ -258,10 +265,14 @@ type RuleStore interface {
258
265
### Lifecycle
259
266
260
267
-`SetStore(store, projectID)` configures persistence and loads any previously saved rules into the `approved` list
261
-
- "Always" approvals are saved on each `ReplyAlways` response
268
+
- "Always" approvals are saved on each `ReplyAlways` response when a store is configured
262
269
- Rules are scoped to a project (identified by `projectID`)
263
270
- Persisted rules survive process restarts
264
271
272
+
### Session-only "always" (current default)
273
+
274
+
No `RuleStore` is wired in TUI/serve/ACP/run by default. In that case, `"always"` approvals live in the in-memory `approved` list for the lifetime of the process only — they do not survive restarts. `RespondToAsk` logs a warning when persisting without a store so this is not silent.
`isDoomLoop()` detects when the last N tool calls have identical signatures (same tool name and arguments). Default threshold: 3 consecutive identical calls (`defaultDoomThreshold`). Triggers `permission.Ask()` with `"doom_loop"`permission.
200
+
`isDoomLoop()` detects when the last N tool calls have identical signatures (same tool name and arguments). Default threshold: 3 consecutive identical calls (`defaultDoomThreshold`). This is a hard-stop in the processor (`checkDoomLoop` returns an error) — it does not go through `permission.Ask`.
0 commit comments