command-code 1.1.1 → 1.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/CHANGELOG.md +13 -13
- package/dist/bundled/command-code-knowledge/reference/models.md +2 -0
- package/dist/bundled/command-code-knowledge/reference/permissions.md +248 -212
- package/dist/bundled/command-code-knowledge/reference/product-help.md +2 -0
- package/dist/cli.mjs +3 -3
- package/package.json +4 -4
- package/vsix/commandcode-vscode.vsix +0 -0
|
@@ -2,76 +2,101 @@
|
|
|
2
2
|
|
|
3
3
|
# Permissions
|
|
4
4
|
|
|
5
|
-
Permissions decide what Command Code
|
|
5
|
+
Permissions decide what Command Code may do **before** it does it. Every tool call - shell, file edit, web fetch, MCP tool, sub-agent - hits one engine that answers one question:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> **allow, ask, or deny?**
|
|
8
|
+
|
|
9
|
+
New? Read [the one-minute version](#the-one-minute-version) and [common recipes](#common-recipes). For full reference, jump to [how a decision is made](#how-a-decision-is-made) and the [decision table](#the-decision-table).
|
|
10
|
+
|
|
11
|
+
Rules are enforced by Command Code, not the model. Your prompt or `AGENTS.md` shapes what the agent *tries*; it doesn't change what's *allowed*. To grant or revoke, use a rule, a mode, or a `PreToolUse` hook.
|
|
8
12
|
|
|
9
13
|
---
|
|
10
14
|
|
|
11
|
-
##
|
|
15
|
+
## The one-minute version
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
Three things cover almost everything.
|
|
14
18
|
|
|
15
|
-
1.
|
|
16
|
-
2. **Ask rules** - always prompt, even under bypass. In `dont-ask` mode the would-be prompt becomes a deny.
|
|
17
|
-
3. **External-directory gate** - a read, write, or shell working directory **outside the workspace** first asks to admit the directory into the session's root set (like `/add-dir` / `permissions.additionalDirectories`); then the normal mode rule applies. Bypass grants the directory silently. The **OS temp directory** (`/tmp`, `$TMPDIR`, `os.tmpdir()`) is granted silently in every mode - reads there never prompt, and writes skip the escalation but still follow the normal mode rule. Registered skill directories are readable without a grant; plan-file writes in plan mode skip this gate.
|
|
18
|
-
4. **Plan mode gate** - operation-based: reads, searches, and read-only shell run; everything that writes or runs a mutating command is denied. The only write allowed is a plan file under `~/.commandcode/plans/`.
|
|
19
|
-
5. **Taste-directory writes** - a product workflow guard (allowed when taste learning is on, otherwise redirected to the `taste` tool); not a safety prompt, so bypass skips it.
|
|
20
|
-
6. **Malformed writes** - a write/edit with no resolvable file path is denied up front with a correction.
|
|
21
|
-
7. **Read-only fast path** - reads and read-only shell commands (`ls`, `cat`, `grep`, `echo`, `head`, …) run without a prompt, in every mode. A compound command counts when **every** part is read-only and nothing redirects - `cat file | head -5 || echo none` is free. A read-only command pointed at secret material (`cat .env`, `head id_rsa`) falls out of the fast path and prompts.
|
|
22
|
-
8. **Root/home removal circuit breaker** - `rm -rf /`, `rm -rf ~`, `rm -rf $HOME` and spoofed variants **ask even under bypass** (deny in `dont-ask`/`plan`, where nothing can ask).
|
|
23
|
-
9. **Bypass** - everything that survived the gates above auto-allows.
|
|
24
|
-
10. **Sensitive-write ask** - writing a secret file, a persistence vector (`.bashrc`, `.gitconfig`, …), or a control surface (`.git/**`, `.ssh/**`, `.commandcode` settings) prompts in `default` and `auto-accept` unless a content-specific allow rule opts in.
|
|
25
|
-
11. **Allow rules** - pre-approved calls run without a prompt.
|
|
26
|
-
12. **Accept-edits fast path** - in `auto-accept` mode, file edits and safe filesystem commands inside the workspace run without a prompt. Recursive deletes are excluded - they still prompt.
|
|
27
|
-
13. Otherwise, **ask** - and in `dont-ask` mode, **deny**.
|
|
19
|
+
**1. A mode sets the baseline.** Cycle with **shift+tab**, or switch with **`/mode`**.
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
The mode and the flag that turns it on are different names - so both columns:
|
|
30
22
|
|
|
31
|
-
|
|
23
|
+
| Mode | How to set it | In one line |
|
|
24
|
+
| :--- | :--- | :--- |
|
|
25
|
+
| `default` | the default; `/mode:default`, shift+tab | Prompt before anything that changes things. Reads are free. |
|
|
26
|
+
| `auto-accept` | `/mode:auto-accept`, shift+tab, `--auto-accept` | Do normal edits (and safe file commands) without asking. |
|
|
27
|
+
| `plan` | `/mode:plan`, shift+tab, `--plan` | Read-only. Explore and plan; only the plan file may be written. |
|
|
28
|
+
| `bypass` | `--yolo` / `--dangerously-skip-permissions` (launch only) | Do basically everything without asking. |
|
|
29
|
+
| `dont-ask` | `"defaultMode": "dont-ask"` in settings, or `--permission-mode dont-ask` | Never ask. Run what's pre-approved; deny the rest. The CI/CD mode. |
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
**2. Three rule lists tune it.** Order: `deny` wins, then `ask`, then `allow`.
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
```json {{ title: '.commandcode/settings.json' }}
|
|
34
|
+
{
|
|
35
|
+
"permissions": {
|
|
36
|
+
"deny": ["Read(secrets/**)"],
|
|
37
|
+
"ask": ["Shell(git push:*)"],
|
|
38
|
+
"allow": ["Shell(git status:*)", "mcp__github__get_issue"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
**3. Some things never prompt.** Reads and read-only shell (`ls`, `cat`, `git status`), plus the control-flow tools `ask_user_question`, `agent`, and `run_command`.
|
|
38
44
|
|
|
39
|
-
A mode
|
|
45
|
+
**deny beats ask beats allow, in every mode.** A mode only decides calls the rules leave unresolved. `bypass` still can't run past a `deny` rule, an `ask` rule, or the root/home delete breaker.
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
| :------------ | :------------------------------------------------------------------------------------------------ |
|
|
43
|
-
| `default` | Prompt for anything mutating; reads are free. |
|
|
44
|
-
| `auto-accept` | Do normal workspace edits (and safe filesystem shell: `mkdir`, `touch`, `cp`, `mv`, non-recursive `rm`, `rmdir`, `sed`) without asking. Does **not** auto-accept arbitrary shell, MCP tools, or recursive deletes. |
|
|
45
|
-
| `plan` | Read-only exploration. Reads, searches, and read-only shell (`git status`) run; the only write allowed is the plan file. |
|
|
46
|
-
| `bypass` | Do basically everything without asking. Deny rules, explicit ask rules, and the root/home removal breaker still apply. Enter with `--yolo` / `--dangerously-skip-permissions`; use only in throwaway environments. |
|
|
47
|
-
| `dont-ask` | Never ask. Only run things already allowed by policy (reads, read-only shell, `allow` rules); **deny** the rest. The mirror image of bypass: for unattended runs where a wrong prompt should fail, not wait. |
|
|
47
|
+
---
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
## Common recipes
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Drop any of these into `.commandcode/settings.json` (shared, checked in) or `.commandcode/settings.local.json` (personal, gitignored).
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
**Read-only git, ask before pushing, block history rewrites:**
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
```json {{ title: '.commandcode/settings.json' }}
|
|
56
|
+
{
|
|
57
|
+
"permissions": {
|
|
58
|
+
"allow": ["Shell(git status:*)", "Shell(git log:*)", "Shell(git diff:*)"],
|
|
59
|
+
"ask": ["Shell(git push:*)", "Shell(git pull:*)"],
|
|
60
|
+
"deny": ["Shell(git push --force*)", "Shell(git reset --hard*)", "Shell(git clean -*)"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
**Gate secret reads** (free by default - reads are never blocked unless a rule says so):
|
|
66
|
+
|
|
67
|
+
```json {{ title: '.commandcode/settings.json' }}
|
|
68
|
+
{
|
|
69
|
+
"permissions": {
|
|
70
|
+
"ask": ["Read(.env*)"],
|
|
71
|
+
"deny": ["Read(secrets/**)", "Read(~/.ssh/**)"]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
64
74
|
```
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
**Protect lockfiles and the git directory:**
|
|
77
|
+
|
|
78
|
+
```json {{ title: '.commandcode/settings.json' }}
|
|
79
|
+
{
|
|
80
|
+
"permissions": {
|
|
81
|
+
"ask": ["Edit(package.json)", "Edit(pnpm-lock.yaml)"],
|
|
82
|
+
"deny": ["Edit(.git/**)", "Edit(.ssh/**)"]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
67
86
|
|
|
68
|
-
|
|
87
|
+
**Allow one MCP tool, or a whole server:**
|
|
69
88
|
|
|
70
|
-
|
|
89
|
+
```json {{ title: '.commandcode/settings.json' }}
|
|
90
|
+
{
|
|
91
|
+
"permissions": {
|
|
92
|
+
"allow": ["mcp__github__get_issue", "mcp__linear__*"]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
71
96
|
|
|
72
|
-
|
|
97
|
+
**Run unattended (CI/CD)** - never prompt; pre-approve exactly what CI may touch:
|
|
73
98
|
|
|
74
|
-
```json
|
|
99
|
+
```json {{ title: '.commandcode/settings.json' }}
|
|
75
100
|
{
|
|
76
101
|
"permissions": {
|
|
77
102
|
"defaultMode": "dont-ask",
|
|
@@ -80,99 +105,97 @@ Switching modes also resolves a permission prompt already on screen: entering `a
|
|
|
80
105
|
}
|
|
81
106
|
```
|
|
82
107
|
|
|
83
|
-
Those edits run
|
|
108
|
+
Those edits run silently; anything outside the allowlist is denied. `dont-ask` is a **fail-closed allowlist**, not an auto-edit mode - a wrong call fails instead of hanging on a prompt no one answers.
|
|
84
109
|
|
|
85
110
|
---
|
|
86
111
|
|
|
87
|
-
##
|
|
112
|
+
## Permission modes
|
|
88
113
|
|
|
89
|
-
|
|
114
|
+
A mode sets the baseline. Cycle with **shift+tab**, or switch with **`/mode`**.
|
|
90
115
|
|
|
91
|
-
|
|
|
92
|
-
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| Read under a registered skill directory | allow | allow | allow | allow | allow | Skill resources are readable without a grant; writes into skill dirs still gate. |
|
|
101
|
-
| Web / read-only native tool | allow | allow | allow | allow | allow | Non-filesystem read-only tools are free unless rules say otherwise. |
|
|
102
|
-
| `ask_user_question` | allow | allow | allow | allow | allow | Asking the user is the interaction; a permission prompt must never stack in front of the question. |
|
|
103
|
-
| `agent` | allow | allow | allow | allow | allow | Delegation itself is always allowed. Every tool used by the sub-agent is still checked under its own permission policy. |
|
|
104
|
-
| `run_command` | allow | allow | - | allow | allow | Dispatches a slash command you named, exactly as if you'd typed it - removed from the toolset in plan mode (a write tool) and never granted to sub-agents. |
|
|
105
|
-
| Read-only shell, e.g. `git status`, `cat README.md \| head -5` | allow | allow | allow | allow | allow | Plan may explore with read-only shell. Compound commands qualify when every part is read-only and nothing redirects. Shell cwd follows the external-dir row below. |
|
|
106
|
-
| Secret-looking read via a file tool, e.g. `read_file .env` | allow | allow | allow | allow | allow | Reads are free by default; no built-in secret-read prompt for file tools. Use `ask`/`deny` rules for sensitive reads. |
|
|
107
|
-
| Secret-looking read via shell, e.g. `cat .env` | ask | ask | deny | allow | deny unless allow rule | A read-only shell command with a secret-material argument (`.env`, `id_rsa`, `*.pem`) falls out of the read-only fast path. |
|
|
108
|
-
| Workspace file edit | ask | allow | deny | allow | deny unless allow rule | `auto-accept` accepts normal edits; `dont-ask` only runs pre-approved edits. |
|
|
109
|
-
| Protected workspace write, e.g. `.env` or `.git/config` | ask | ask | deny | allow | deny unless pre-approved | Bypass skips normal write-risk prompts; auto-accept does not. |
|
|
110
|
-
| Direct file write outside workspace | ask external-dir, then ask edit/write | ask external-dir, then allow normal edit/write; sensitive still asks | deny, except plan-file writes | allow | deny unless pre-approved | External-dir gate comes first, then the mode-specific edit/write rule. |
|
|
111
|
-
| Plan file write under `~/.commandcode/plans/` | ask like outside write | ask external-dir, then allow | allow (no external-dir prompt) | allow | deny unless pre-approved | Plan mode's sanctioned output; never gated in plan mode. |
|
|
112
|
-
| Safe filesystem shell inside workspace: `mkdir`, `touch`, `cp`, `mv`, non-recursive `rm`, `rmdir`, `sed` | ask | allow | deny | allow | deny unless allow rule | Accepting edits extends to safe filesystem commands, constrained to non-sensitive workspace targets. Recursive `rm` is excluded - see the recursive-delete row. |
|
|
113
|
-
| Shell cwd outside workspace | ask external-dir, then apply shell rule/mode | ask external-dir, then apply shell rule/mode | ask external-dir for read-only shell; deny mutating shell | allow | deny unless pre-approved | The shell working directory is external-dir gated. |
|
|
114
|
-
| Shell command args outside workspace | shell rule/mode | shell rule/mode | shell rule/mode | allow | shell rule/mode | Path arguments inside a command string are never external-dir gated (not a reliable file boundary); use shell `ask`/`deny` rules for sensitive commands. |
|
|
115
|
-
| Other mutating shell | ask | ask | deny | allow | deny unless allow rule | Shell command arguments are governed by shell rules and the active mode. |
|
|
116
|
-
| MCP / custom side-effecting or unknown tool | ask | ask | deny | allow | deny unless allow rule | Unknown side effects prompt unless bypass or an explicit rule. |
|
|
117
|
-
| Root/home removal, e.g. `rm -rf /` or `rm -rf ~` | ask | ask | deny | **ask** | deny | The circuit breaker survives bypass. |
|
|
118
|
-
| Recursive workspace delete, e.g. `rm -rf dist` | ask | ask | deny | allow | deny unless allow rule | Recursive deletes prompt outside bypass - accepting edits is not accepting bulk irreversible deletion. A content-specific allow rule (`Shell(rm -rf dist)`) still auto-allows. |
|
|
116
|
+
| Mode | Meaning |
|
|
117
|
+
| :------------ | :------------------------------------------------------------------------------------------------ |
|
|
118
|
+
| `default` | Prompt for anything mutating; reads are free. |
|
|
119
|
+
| `auto-accept` | Do normal workspace edits (and safe filesystem shell: `mkdir`, `touch`, `cp`, `mv`, non-recursive `rm`, `rmdir`, `sed`) without asking. Does **not** auto-accept arbitrary shell, MCP tools, or recursive deletes. |
|
|
120
|
+
| `plan` | Read-only exploration. Reads, searches, and read-only shell (`git status`) run; only the plan file may be written. |
|
|
121
|
+
| `bypass` | Do basically everything without asking. Deny rules, explicit ask rules, and the root/home removal breaker still apply. Enter with `--yolo` / `--dangerously-skip-permissions`; throwaway environments only. |
|
|
122
|
+
| `dont-ask` | Never ask. Run only what policy already allows (reads, read-only shell, `allow` rules); **deny** the rest. The mirror of bypass: for unattended runs where a wrong prompt should fail, not wait. |
|
|
123
|
+
|
|
124
|
+
Aliases: `manual` → `default`, `acceptEdits` → `auto-accept`, `dontAsk` → `dont-ask`, `bypassPermissions` → `bypass`. Legacy `standard` also maps to `default`.
|
|
119
125
|
|
|
120
|
-
|
|
126
|
+
### Switching modes
|
|
127
|
+
|
|
128
|
+
Three ways, mid-session:
|
|
129
|
+
|
|
130
|
+
- **shift+tab** cycles `default` → `auto-accept` → `plan` → `default`. Launched with `--yolo`, `bypass` joins as a fourth rung after `plan`. (`alt+m` is an equivalent binding for terminals that swallow shift+tab.) `dont-ask` isn't a rung - set it via settings or CLI; shift+tab from it re-enters the cycle at `auto-accept`.
|
|
131
|
+
- **`/mode`** shows the current mode; **`/mode:default`**, **`/mode:auto-accept`**, **`/mode:plan`** (or `/mode <name>`, any spelling) switch directly. Bypass is deliberately **not** slash-switchable - slash commands are agent-invokable, so a mid-session route into bypass would let the model kill its own prompts. `/mode:yolo` is gone; the command says "launch with `--yolo`."
|
|
132
|
+
- **CLI flags** pick the start mode:
|
|
133
|
+
|
|
134
|
+
```bash {{ title: 'Terminal' }}
|
|
135
|
+
cmd --permission-mode auto-accept # default | standard | plan | auto-accept | dont-ask
|
|
136
|
+
cmd --auto-accept # shorthand for --permission-mode auto-accept
|
|
137
|
+
cmd --plan # start in plan mode
|
|
138
|
+
cmd --yolo # bypass - alias for --dangerously-skip-permissions; use with care
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`--permission-mode` wins over `--plan`. Bypass has no `--permission-mode` value - it's **launch-flag-only** (`--yolo` / `--dangerously-skip-permissions`).
|
|
142
|
+
|
|
143
|
+
A mode switch also answers a prompt already on screen: `auto-accept` or `bypass` **approves** it (you said "stop asking"); `plan` **denies** it. A safety-forced prompt - destructive command, sensitive file, explicit `ask` rule - is never answered this way; it stays put for an explicit yes/no.
|
|
121
144
|
|
|
122
145
|
---
|
|
123
146
|
|
|
124
|
-
##
|
|
147
|
+
## Writing rules
|
|
125
148
|
|
|
126
|
-
Three lists
|
|
149
|
+
Three lists under `permissions`, checked in order: **deny** (always wins) → **ask** (always prompts, even in bypass; denies in dont-ask) → **allow**. Three tools skip the lists: `ask_user_question` (its UI is the interaction), `agent` (delegation has no side effect), and `run_command` (it just dispatches a slash command you named). Tools a sub-agent calls are still gated.
|
|
127
150
|
|
|
128
|
-
A rule is a tool name, optionally with a specifier
|
|
151
|
+
A rule is a tool name, optionally with a specifier:
|
|
129
152
|
|
|
130
|
-
```
|
|
153
|
+
```text {{ title: 'Rule shape' }}
|
|
131
154
|
Tool # the whole tool
|
|
132
155
|
Tool(specifier) # a specific use
|
|
133
156
|
```
|
|
134
157
|
|
|
135
158
|
| Rule | Effect |
|
|
136
159
|
| :-------------------------------- | :------------------------------------------------- |
|
|
137
|
-
| `Shell` |
|
|
138
|
-
| `Shell(git status)` |
|
|
139
|
-
| `Shell(npm run *)` |
|
|
140
|
-
| `Read(./.env)` |
|
|
141
|
-
| `Edit(src/**)` |
|
|
142
|
-
| `WebFetch(domain:example.com)` |
|
|
143
|
-
| `mcp__github__get_issue` |
|
|
144
|
-
| `mcp__github__*` |
|
|
145
|
-
| `mcp__github__get_*` |
|
|
146
|
-
| `mcp__*` |
|
|
147
|
-
| `*` |
|
|
148
|
-
| `Shell(run_in_background:true)` |
|
|
160
|
+
| `Shell` | Every shell command |
|
|
161
|
+
| `Shell(git status)` | `git status` (and `git status --short`) |
|
|
162
|
+
| `Shell(npm run *)` | Any `npm run …` command |
|
|
163
|
+
| `Read(./.env)` | Reading `.env` in the project root |
|
|
164
|
+
| `Edit(src/**)` | Edits anywhere under `src/` |
|
|
165
|
+
| `WebFetch(domain:example.com)` | Fetches to `example.com` |
|
|
166
|
+
| `mcp__github__get_issue` | One tool from the `github` MCP server |
|
|
167
|
+
| `mcp__github__*` | Every tool from the `github` server |
|
|
168
|
+
| `mcp__github__get_*` | Every `get_` tool from the `github` server |
|
|
169
|
+
| `mcp__*` | Every tool from every MCP server |
|
|
170
|
+
| `*` | Every tool |
|
|
171
|
+
| `Shell(run_in_background:true)` | A shell command run in the background |
|
|
149
172
|
|
|
150
173
|
Tool names are case-insensitive. `Tool()` and `Tool(*)` both mean the whole tool.
|
|
151
174
|
|
|
152
175
|
### Wildcards
|
|
153
176
|
|
|
154
|
-
A `*` in a **tool name** (not a specifier) matches by name
|
|
177
|
+
A `*` in a **tool name** (not a specifier) matches by name:
|
|
155
178
|
|
|
156
|
-
- `*` - every permission-gated tool (`ask_user_question`, `agent`,
|
|
179
|
+
- `*` - every permission-gated tool (`ask_user_question`, `agent`, `run_command` stay allowed).
|
|
157
180
|
- `mcp__*` - every tool from every MCP server.
|
|
158
181
|
- `mcp__github__get_*` - every read-style tool from one server.
|
|
159
182
|
- `edit_*` - every tool whose name starts with `edit_`.
|
|
160
183
|
|
|
161
|
-
Tool-name wildcards
|
|
184
|
+
Tool-name wildcards work in `deny` and `ask` (`deny: ["mcp__*"]` blocks all MCP tools). They're **ignored in `allow`** unless the server is named (`mcp__github__get_*` is fine; a bare `*` or `mcp__*` is not) - an allow rule should say what it grants, not hand out everything. Specifier wildcards (`Shell(npm *)`, `Edit(src/**)`, `WebFetch(domain:*.example.com)`) work everywhere.
|
|
162
185
|
|
|
163
186
|
### Match by input parameter
|
|
164
187
|
|
|
165
|
-
|
|
188
|
+
A `deny` or `ask` rule can gate on one **top-level input parameter** with `Tool(param:value)`:
|
|
166
189
|
|
|
167
190
|
- `Shell(run_in_background:true)` - a background shell command.
|
|
168
191
|
|
|
169
|
-
The value takes a `*` wildcard
|
|
192
|
+
The value takes a `*` wildcard; a parameter the model **omits** never matches. One parameter per rule - to gate two, write two rules.
|
|
170
193
|
|
|
171
|
-
Parameter matching is **deny/ask only
|
|
194
|
+
Parameter matching is **deny/ask only** - an allow rule for one parameter can't prove the whole call is safe. The fields a tool already matches with its own syntax - `command` (Shell), `file_path` (Read/Edit/Write), `path` (Grep/Glob), `notebook_path`, `url` (WebFetch) - are **not** parameter-matchable; use the specifier (`Shell(rm *)`, not `Shell(command:rm *)`).
|
|
172
195
|
|
|
173
196
|
### Tool names
|
|
174
197
|
|
|
175
|
-
Use the friendly
|
|
198
|
+
Use the friendly names, or the exact tool names the model sees:
|
|
176
199
|
|
|
177
200
|
| Friendly | Covers |
|
|
178
201
|
| :----------- | :----------------------------------------------------------- |
|
|
@@ -183,31 +206,24 @@ Use the friendly capitalized names, or the exact tool names the model sees:
|
|
|
183
206
|
| `WebFetch` | `web_fetch` |
|
|
184
207
|
| `WebSearch` | `web_search` |
|
|
185
208
|
|
|
186
|
-
`Shell` is the name
|
|
209
|
+
`Shell` is the name, because the shell tool is `shell_command`. `Bash(...)` is a legacy alias for `Shell(...)` so old configs keep working - but use `Shell`.
|
|
187
210
|
|
|
188
211
|
### Shell patterns
|
|
189
212
|
|
|
190
|
-
Shell specifiers
|
|
213
|
+
Shell specifiers take `*` wildcards:
|
|
191
214
|
|
|
192
|
-
- `Shell(npm run build)` -
|
|
215
|
+
- `Shell(npm run build)` - exact command.
|
|
193
216
|
- `Shell(npm run *)` - any command starting with `npm run `.
|
|
194
217
|
- `Shell(git * main)` - `git checkout main`, `git merge main`, …
|
|
195
|
-
- `Shell(git:*)` - the `:*` suffix is a trailing wildcard,
|
|
196
|
-
|
|
197
|
-
The space before `*` matters: `Shell(ls *)` matches `ls -la` but not `lsof`; `Shell(ls*)` matches both.
|
|
198
|
-
|
|
199
|
-
Command Code understands shell operators, so a rule like `Shell(git status)` does **not** grant `git status && rm -rf .`. Every subcommand of a compound command (`&&`, `||`, `;`, `|`, `&`, newlines) must match on its own.
|
|
218
|
+
- `Shell(git:*)` - the `:*` suffix is a trailing wildcard, same as `Shell(git *)`.
|
|
200
219
|
|
|
201
|
-
|
|
220
|
+
The space matters: `Shell(ls *)` matches `ls -la` but not `lsof`; `Shell(ls*)` matches both.
|
|
202
221
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
- **Deny and ask rules match aggressively:** They see through leading environment variables (`FOO=bar rm -rf x` matches `Shell(rm *)`), process wrappers (`timeout 30 …`, `nice …`), and every subcommand of a compound command; a command the parser can't fully understand is matched against the raw string; path matching folds case (on macOS/Windows `.ENV` opens `.env`) and fires when either the given path or its symlink target matches.
|
|
206
|
-
- **Allow rules match conservatively:** No env stripping (`PATH=/tmp git status` does **not** match an allow for `Shell(git status:*)`); a compound command allows only when *every* subcommand matches some allow pattern; an unparseable command never auto-allows; path matching is case-exact and requires **both** the given path and its symlink target to match.
|
|
222
|
+
Command Code parses shell operators, so `Shell(git status)` does **not** grant `git status && rm -rf .`. Every subcommand of a compound command (`&&`, `||`, `;`, `|`, `&`, newlines) must match on its own.
|
|
207
223
|
|
|
208
224
|
### File-path patterns
|
|
209
225
|
|
|
210
|
-
`Read` and `Edit`
|
|
226
|
+
`Read` and `Edit` use gitignore-style matching with four anchors:
|
|
211
227
|
|
|
212
228
|
| Pattern | Anchored at | Example |
|
|
213
229
|
| :------------- | :----------------------------------- | :----------------------------- |
|
|
@@ -216,83 +232,151 @@ The matcher deliberately treats the lists differently - failing toward a prompt
|
|
|
216
232
|
| `/path` | Project root | `Edit(/src/**)` |
|
|
217
233
|
| `path` | Any depth under the project | `Read(.env)` ≡ `Read(**/.env)` |
|
|
218
234
|
|
|
219
|
-
`*` matches within one path segment; `**`
|
|
235
|
+
`*` matches within one path segment; `**` crosses directories. For a symlink, both link and target are checked. A trailing slash can't dodge an exact-path rule (`read_directory /repo/secrets/` still matches `Read(//repo/secrets)`).
|
|
236
|
+
|
|
237
|
+
Reads are allowed by default. `allow: ["Read(src/**)"]` is accepted but does **not** block reads outside `src/**` - use `ask`/`deny` for sensitive reads. A strict read allowlist would be a separate change (see [Known limits](#known-limits)).
|
|
238
|
+
|
|
239
|
+
### Aggressive deny, conservative allow
|
|
220
240
|
|
|
221
|
-
|
|
241
|
+
The lists are matched differently on purpose - failing toward a prompt is the safe direction:
|
|
242
|
+
|
|
243
|
+
- **Deny and ask match aggressively:** they see through leading env vars (`FOO=bar rm -rf x` matches `Shell(rm *)`), process wrappers (`timeout 30 …`, `nice …`), and every subcommand; an unparseable command is matched against the raw string; path matching folds case (on macOS/Windows `.ENV` opens `.env`) and fires when either the path or its symlink target matches.
|
|
244
|
+
- **Allow matches conservatively:** no env stripping (`PATH=/tmp git status` does **not** match `Shell(git status:*)`); a compound command allows only when *every* subcommand matches; an unparseable command never auto-allows; path matching is case-exact and needs **both** the path and its symlink target to match.
|
|
222
245
|
|
|
223
246
|
### MCP and sub-agents
|
|
224
247
|
|
|
225
|
-
MCP tools are
|
|
248
|
+
MCP tools are keyed by their canonical `mcp__server__tool` name - display names are never keys. `mcp__github` and `mcp__github__*` both match the whole `github` server; `mcp__github__get_*` matches its `get_` tools; `mcp__*` (deny/ask only) matches every server.
|
|
226
249
|
|
|
227
|
-
The `agent` tool
|
|
250
|
+
The `agent` tool is always allowed and never prompts. A sub-agent runs the **same ordered pipeline** as the main loop, with one difference: where the main loop would draw an interactive prompt, the sub-agent policy **auto-allows** - it runs autonomously, with no human to answer. The hard guards hold: a sub-agent can't run a tool your `deny` rules block, can't write in plan mode, and anything the main loop would prompt on for **safety** - destructive command, sensitive file, outside-workspace write, explicit `ask` rule - fails **closed** (denied).
|
|
228
251
|
|
|
229
252
|
---
|
|
230
253
|
|
|
231
|
-
##
|
|
254
|
+
## What happens at a prompt
|
|
232
255
|
|
|
233
|
-
Every
|
|
256
|
+
Every prompt offers three choices: allow once, allow **and remember**, or deny with feedback. The remembered scope matches the subject:
|
|
234
257
|
|
|
235
|
-
- **File
|
|
236
|
-
- **Shell
|
|
237
|
-
- **MCP / other
|
|
258
|
+
- **File ops** - *"Yes, allow all edits this session"* turns on auto-accept for the session (same as shift+tab).
|
|
259
|
+
- **Shell** - *"Yes, don't ask again for `git` commands in this project"* persists a `Shell(git:*)` allow rule to project settings. For a compound command, one rule per non-read-only base. Bases that run arbitrary code (`bash`, `python`, `curl`, …) persist the exact command, not a prefix.
|
|
260
|
+
- **MCP / other** - *"Yes, don't ask again for `mcp__server__tool` in this project"* persists the tool name to project settings.
|
|
238
261
|
|
|
239
262
|
### Command explanations on demand
|
|
240
263
|
|
|
241
|
-
The shell
|
|
264
|
+
The shell prompt can explain the command: press **ctrl+e** (**ctrl+y** in VS Code-family terminals, where the IDE grabs ctrl+e) and a background model call summarizes it inline. Governed by the `on-demand-tool-descriptions` setting (**on by default**): explanations only when asked. Turn it off (via `/config`) to generate every one upfront.
|
|
242
265
|
|
|
243
266
|
---
|
|
244
267
|
|
|
245
268
|
## Safety behaviors
|
|
246
269
|
|
|
247
|
-
|
|
270
|
+
Some prompts are forced by a safety check, not a mode or rule. They offer only one-time **Yes**/**No**, plus a banner naming the check. Session and project choices are hidden on purpose - destructive-command, sensitive-path, outside-workspace, and explicit `ask` approvals are never cached, so the next risky call asks again. To stop a recurring one, add a content-specific allow rule (e.g. `Edit(.commandcode/settings.json)`).
|
|
248
271
|
|
|
249
272
|
### The root/home removal circuit breaker
|
|
250
273
|
|
|
251
|
-
The **only** built-in safety prompt that survives `bypass`: a removal
|
|
274
|
+
The **only** built-in safety prompt that survives `bypass`: a removal of the filesystem root or your home dir - `rm -rf /`, `rm -rf ~`, `rm -rf $HOME` - always stops for confirmation, in every mode that can ask (`dont-ask` and `plan` deny instead).
|
|
252
275
|
|
|
253
|
-
|
|
276
|
+
Spoof-resistant. It catches removals through env-var prefixes, process wrappers (`timeout`, `nice`, …), compound commands, quoted payloads (`bash -c "rm -rf /; echo ok"`), command substitution (`echo $(rm -rf /;)`), glued separators (`/;`), and `$HOME`/`${HOME}` spellings (quoted or not, with `/` or `/*` suffixes - `$HOMEDIR` stays literal).
|
|
254
277
|
|
|
255
278
|
### Recursive deletes prompt under auto-accept
|
|
256
279
|
|
|
257
|
-
The `auto-accept` safe-filesystem fast path refuses `rm -r/-R/-rf/--recursive` (and `find … -delete`):
|
|
280
|
+
The `auto-accept` safe-filesystem fast path refuses `rm -r/-R/-rf/--recursive` (and `find … -delete`): `rm -rf dist` prompts exactly as in `default`. Plain single-file `rm` stays fast. Deliberate, safe direction: accepting edits isn't accepting bulk irreversible deletion. A content-specific allow rule (`Shell(rm -rf dist)`) still auto-allows, and `bypass` runs it.
|
|
258
281
|
|
|
259
282
|
### Sensitive writes
|
|
260
283
|
|
|
261
|
-
Writing these prompts in `default` and `auto-accept` unless a content-specific allow rule opts in (`bypass` skips this gate):
|
|
284
|
+
Writing any of these prompts in `default` and `auto-accept` unless a content-specific allow rule opts in (`bypass` skips this gate):
|
|
262
285
|
|
|
263
|
-
```
|
|
286
|
+
```text {{ title: 'Sensitive paths (prompt on write)' }}
|
|
264
287
|
Secret material: .env, .env.*, *.pem, *.key, id_rsa, id_ed25519, credentials
|
|
265
288
|
Persistence vectors: .bashrc, .zshrc, .profile, .gitconfig, .gitmodules, .mcp.json, …
|
|
266
289
|
Control surfaces: .git/**, .ssh/**, .aws/**, .gnupg/**, .kube/**, .vscode/**, .idea/**,
|
|
267
290
|
.husky/**, .devcontainer/**, node_modules/.bin/**, .commandcode settings
|
|
268
291
|
```
|
|
269
292
|
|
|
270
|
-
A "content-specific" allow rule must
|
|
293
|
+
A "content-specific" allow rule must name the path (`Edit(.env.local)`) - a bare whole-tool allow (`Edit`) won't wave a call past a safety check.
|
|
271
294
|
|
|
272
|
-
Sensitive **reads** through file tools
|
|
295
|
+
Sensitive **reads** through file tools don't prompt. Reading `.env` is allowed by default. Gate it with a rule: `ask: ["Read(.env*)"]` or `deny: ["Read(.env*)", "Read(secrets/**)"]`. The read-only **shell** fast path is stricter: `cat .env` or `head id_rsa` falls out and prompts.
|
|
273
296
|
|
|
274
297
|
### External directories
|
|
275
298
|
|
|
276
|
-
Outside-workspace reads, writes, and shell working
|
|
299
|
+
Outside-workspace reads, writes, and shell working dirs first ask to admit the directory into the session's root set - the same grant as `/add-dir` or `permissions.additionalDirectories` - then the normal mode rule applies.
|
|
277
300
|
|
|
278
|
-
- `bypass` grants the directory silently; `dont-ask` denies unless
|
|
279
|
-
- The **OS temp
|
|
301
|
+
- `bypass` grants the directory silently; `dont-ask` denies unless it's pre-approved in settings.
|
|
302
|
+
- The **OS temp dir** (`/tmp`, `$TMPDIR`, `os.tmpdir()`) is granted silently in every mode - temp files are disposable, so asking to "extend access" to the machine's own temp dir is friction, not protection. Reads run prompt-free; writes skip the escalation but still confirm like any workspace write (sensitive names like `/tmp/.env` still force the prompt). `deny`/`ask` rules still outrank the grant, and a symlink resolving outside temp takes the normal gate.
|
|
280
303
|
- Approving an `ask` rule on an outside path **also grants the directory**, so the tool-level backstop never rejects a call you just approved.
|
|
281
|
-
- **Registered skill directories** are readable without a grant (skill resources live outside the workspace by design). Writes into
|
|
282
|
-
- **Plan-file writes are exempt in plan mode** -
|
|
283
|
-
- Absolute paths **inside a shell command string** are *not* external-dir gated - a command
|
|
304
|
+
- **Registered skill directories** are readable without a grant (skill resources live outside the workspace by design). Writes into them still gate.
|
|
305
|
+
- **Plan-file writes are exempt in plan mode** - the mode's sanctioned output.
|
|
306
|
+
- Absolute paths **inside a shell command string** are *not* external-dir gated - a command arg isn't a reliable file boundary. Use shell `ask`/`deny` rules.
|
|
284
307
|
|
|
285
308
|
### Disabling bypass
|
|
286
309
|
|
|
287
|
-
Set `permissions.disableBypass` to `"disable"` (or `true`) to make bypass
|
|
310
|
+
Set `permissions.disableBypass` to `"disable"` (or `true`) to make bypass unenterable. Enforced at every layer: the engine evaluates a requested `bypass` as `default`, and the CLI neutralizes `--dangerously-skip-permissions` (and `--yolo`) at entry, so the TUI mode cycle and stored auto-approve decisions can't silently answer prompts as if bypass were live. Best set in the user-global file a project can't edit.
|
|
288
311
|
|
|
289
312
|
---
|
|
290
313
|
|
|
291
|
-
##
|
|
314
|
+
## How a decision is made
|
|
315
|
+
|
|
316
|
+
The whole machine. Every tool call runs a fixed order - the order **is** the spec, and tests pin it:
|
|
317
|
+
|
|
318
|
+
1. **Deny rules** - blocked in every mode, including bypass.
|
|
319
|
+
2. **Ask rules** - always prompt, even under bypass. In `dont-ask` the would-be prompt becomes a deny.
|
|
320
|
+
3. **External-directory gate** - a read, write, or shell cwd **outside the workspace** first asks to admit the directory (like `/add-dir` / `permissions.additionalDirectories`); then the normal mode rule applies. Bypass grants silently. The **OS temp dir** (`/tmp`, `$TMPDIR`, `os.tmpdir()`) is granted silently in every mode - reads never prompt, writes skip the escalation but follow the normal mode rule. Skill dirs are readable without a grant; plan-file writes in plan mode skip this gate.
|
|
321
|
+
4. **Plan mode gate** - operation-based: reads, searches, read-only shell run; everything that writes or mutates is denied. The only write allowed is a plan file under `~/.commandcode/plans/`.
|
|
322
|
+
5. **Taste-directory writes** - a product-workflow guard (allowed when taste learning is on, else redirected to the `taste` tool); not a safety prompt, so bypass skips it.
|
|
323
|
+
6. **Malformed writes** - a write/edit with no resolvable file path is denied up front with a correction.
|
|
324
|
+
7. **Read-only fast path** - reads and read-only shell (`ls`, `cat`, `grep`, `echo`, `head`, …) run without a prompt, every mode. A compound command counts when **every** part is read-only and nothing redirects - `cat file | head -5 || echo none` is free. A read-only command aimed at secret material (`cat .env`, `head id_rsa`) falls out and prompts.
|
|
325
|
+
8. **Root/home removal circuit breaker** - `rm -rf /`, `rm -rf ~`, `rm -rf $HOME` and spoofed variants **ask even under bypass** (deny in `dont-ask`/`plan`).
|
|
326
|
+
9. **Bypass** - everything past the gates above auto-allows.
|
|
327
|
+
10. **Sensitive-write ask** - writing a secret file, a persistence vector (`.bashrc`, `.gitconfig`, …), or a control surface (`.git/**`, `.ssh/**`, `.commandcode` settings) prompts in `default` and `auto-accept` unless a content-specific allow rule opts in.
|
|
328
|
+
11. **Allow rules** - pre-approved calls run without a prompt.
|
|
329
|
+
12. **Accept-edits fast path** - in `auto-accept`, file edits and safe filesystem commands inside the workspace run without a prompt. Recursive deletes excluded - they still prompt.
|
|
330
|
+
13. Otherwise, **ask** - and in `dont-ask`, **deny**.
|
|
331
|
+
|
|
332
|
+
Two consequences. First, **deny beats ask beats allow**, every mode. Second, `bypass` sits *after* the deny/ask rules, the external-dir gate, and the breaker - so `--yolo` skips ordinary prompts and the sensitive-write prompt, but not your explicit rules and not the root/home breaker.
|
|
292
333
|
|
|
293
|
-
|
|
334
|
+
Every policy denial (deny rule, mode gate, malformed input) carries guidance the model can act on, and the turn continues. Only a **human** "no" ends the turn.
|
|
335
|
+
|
|
336
|
+
---
|
|
294
337
|
|
|
295
|
-
|
|
338
|
+
## The decision table
|
|
339
|
+
|
|
340
|
+
The full matrix - every operation against every mode. Authoritative; the tests pin each row.
|
|
341
|
+
|
|
342
|
+
| Operation | `default` | `auto-accept` | `plan` | `bypass` | `dont-ask` | Why |
|
|
343
|
+
|---|---|---|---|---|---|---|
|
|
344
|
+
| Matches `deny` rule | deny | deny | deny | deny | deny | Deny rules win. `ask_user_question`, `agent`, `run_command` are unconditional exceptions. |
|
|
345
|
+
| Matches `ask` rule | ask | ask | ask | ask | deny | Ask survives bypass; `dont-ask` can't prompt. Same three tools exempt. |
|
|
346
|
+
| Matches `allow` rule | allow | allow | allow if plan-compatible | allow | allow | Honored after deny/ask and mode gates. |
|
|
347
|
+
| Direct file read/search inside workspace | allow | allow | allow | allow | allow | Reads are free unless rules say otherwise. |
|
|
348
|
+
| Direct file read/search outside workspace | ask external-dir, then allow | ask external-dir, then allow | ask external-dir, then allow | allow | deny unless pre-approved | Outside dirs gated separately, then reads allowed. |
|
|
349
|
+
| Read under the OS temp dir (`/tmp`, `$TMPDIR`) | allow (dir granted silently) | allow (dir granted silently) | allow (dir granted silently) | allow | allow | Disposable working data - escalation skipped. Deny/ask still outrank; a symlink resolving outside temp takes the normal gate. |
|
|
350
|
+
| Write under the OS temp dir | ask like a workspace write | allow; sensitive still asks | deny | allow | deny unless allow rule | No escalation, then the normal mode edit/write rule (sensitive names like `/tmp/.env` still force the prompt). |
|
|
351
|
+
| Read under a registered skill dir | allow | allow | allow | allow | allow | Skill resources readable without a grant; writes into skill dirs still gate. |
|
|
352
|
+
| Web / read-only native tool | allow | allow | allow | allow | allow | Non-filesystem read-only tools are free unless rules say otherwise. |
|
|
353
|
+
| `ask_user_question` | allow | allow | allow | allow | allow | Asking the user is the interaction; no prompt may stack in front of it. |
|
|
354
|
+
| `agent` | allow | allow | allow | allow | allow | Delegation is always allowed. Every sub-agent tool is still checked. |
|
|
355
|
+
| `run_command` | allow | allow | - | allow | allow | Dispatches a slash command you named - removed in plan mode (a write tool), never granted to sub-agents. |
|
|
356
|
+
| Read-only shell, e.g. `git status`, `cat README.md \| head -5` | allow | allow | allow | allow | allow | Plan may explore with read-only shell. Compound qualifies when every part is read-only and nothing redirects. Shell cwd follows the external-dir row. |
|
|
357
|
+
| Secret-looking read via a file tool, e.g. `read_file .env` | allow | allow | allow | allow | allow | Reads free by default; no built-in secret-read prompt for file tools. Use `ask`/`deny`. |
|
|
358
|
+
| Secret-looking read via shell, e.g. `cat .env` | ask | ask | deny | allow | deny unless allow rule | A read-only shell command with a secret arg (`.env`, `id_rsa`, `*.pem`) falls out of the fast path. |
|
|
359
|
+
| Workspace file edit | ask | allow | deny | allow | deny unless allow rule | `auto-accept` accepts normal edits; `dont-ask` runs only pre-approved edits. |
|
|
360
|
+
| Protected workspace write, e.g. `.env` or `.git/config` | ask | ask | deny | allow | deny unless pre-approved | Bypass skips normal write-risk prompts; auto-accept doesn't. |
|
|
361
|
+
| Direct file write outside workspace | ask external-dir, then ask edit/write | ask external-dir, then allow normal edit/write; sensitive still asks | deny, except plan-file writes | allow | deny unless pre-approved | External-dir gate first, then the mode edit/write rule. |
|
|
362
|
+
| Plan file write under `~/.commandcode/plans/` | ask like outside write | ask external-dir, then allow | allow (no external-dir prompt) | allow | deny unless pre-approved | Plan mode's sanctioned output; never gated in plan mode. |
|
|
363
|
+
| Safe filesystem shell inside workspace: `mkdir`, `touch`, `cp`, `mv`, non-recursive `rm`, `rmdir`, `sed` | ask | allow | deny | allow | deny unless allow rule | Accepting edits extends to safe filesystem commands on non-sensitive workspace targets. Recursive `rm` excluded. |
|
|
364
|
+
| Shell cwd outside workspace | ask external-dir, then apply shell rule/mode | ask external-dir, then apply shell rule/mode | ask external-dir for read-only shell; deny mutating shell | allow | deny unless pre-approved | The shell working dir is external-dir gated. |
|
|
365
|
+
| Shell command args outside workspace | shell rule/mode | shell rule/mode | shell rule/mode | allow | shell rule/mode | Path args inside a command string are never external-dir gated; use shell `ask`/`deny` rules. |
|
|
366
|
+
| Other mutating shell | ask | ask | deny | allow | deny unless allow rule | Governed by shell rules and the active mode. |
|
|
367
|
+
| MCP / custom side-effecting or unknown tool | ask | ask | deny | allow | deny unless allow rule | Unknown side effects prompt unless bypass or an explicit rule. |
|
|
368
|
+
| Root/home removal, e.g. `rm -rf /` or `rm -rf ~` | ask | ask | deny | **ask** | deny | The circuit breaker survives bypass. |
|
|
369
|
+
| Recursive workspace delete, e.g. `rm -rf dist` | ask | ask | deny | allow | deny unless allow rule | Recursive deletes prompt outside bypass. A content-specific allow rule (`Shell(rm -rf dist)`) still auto-allows. |
|
|
370
|
+
|
|
371
|
+
Reading a column: a mode only changes calls the rules leave **unresolved**. `deny` and `ask` behave the same in every interactive mode; `dont-ask` turns every would-be ask into a deny; `bypass` auto-allows everything but deny rules, ask rules, and the breaker.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Settings reference
|
|
376
|
+
|
|
377
|
+
Rules live under `permissions` in `.commandcode/settings.json` (shared, checked in) or `.commandcode/settings.local.json` (personal, gitignored). Interactive approvals write to `settings.json` normally, and to `settings.local.json` when started with `--local`:
|
|
378
|
+
|
|
379
|
+
```json {{ title: '.commandcode/settings.json' }}
|
|
296
380
|
{
|
|
297
381
|
"permissions": {
|
|
298
382
|
"defaultMode": "default",
|
|
@@ -317,91 +401,43 @@ Permission rules live under `permissions` in `.commandcode/settings.json` (share
|
|
|
317
401
|
}
|
|
318
402
|
```
|
|
319
403
|
|
|
320
|
-
- **`defaultMode`** - the
|
|
321
|
-
- **`allow` / `ask` / `deny`** - rule lists.
|
|
322
|
-
- **`additionalDirectories`** - extra
|
|
323
|
-
- **`disableBypass`** -
|
|
404
|
+
- **`defaultMode`** - the start mode (`default`, `auto-accept`, `plan`, `bypass`, `dont-ask`; alternate spellings like `acceptEdits` and legacy `standard` work too).
|
|
405
|
+
- **`allow` / `ask` / `deny`** - rule lists. They **accumulate across settings files**: a `deny` in your user-global settings can't be erased by a project file.
|
|
406
|
+
- **`additionalDirectories`** - extra dirs that count as inside the workspace for reads and writes (same accumulation).
|
|
407
|
+
- **`disableBypass`** - `"disable"` (or `true`) makes bypass unenterable.
|
|
324
408
|
|
|
325
409
|
### Precedence
|
|
326
410
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
---
|
|
330
|
-
|
|
331
|
-
## Examples
|
|
332
|
-
|
|
333
|
-
**Read-only git, ask before pushing, block history rewrites:**
|
|
334
|
-
|
|
335
|
-
```json
|
|
336
|
-
{
|
|
337
|
-
"permissions": {
|
|
338
|
-
"allow": ["Shell(git status:*)", "Shell(git log:*)", "Shell(git diff:*)"],
|
|
339
|
-
"ask": ["Shell(git push:*)", "Shell(git pull:*)"],
|
|
340
|
-
"deny": ["Shell(git push --force*)", "Shell(git reset --hard*)", "Shell(git clean -*)"]
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
**Gate secret reads (they're free by default):**
|
|
346
|
-
|
|
347
|
-
```json
|
|
348
|
-
{
|
|
349
|
-
"permissions": {
|
|
350
|
-
"ask": ["Read(.env*)"],
|
|
351
|
-
"deny": ["Read(secrets/**)", "Read(~/.ssh/**)"]
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
**Protect lockfiles and the git directory:**
|
|
357
|
-
|
|
358
|
-
```json
|
|
359
|
-
{
|
|
360
|
-
"permissions": {
|
|
361
|
-
"ask": ["Edit(package.json)", "Edit(pnpm-lock.yaml)"],
|
|
362
|
-
"deny": ["Edit(.git/**)", "Edit(.ssh/**)"]
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
**Allow one MCP tool, or a whole server:**
|
|
368
|
-
|
|
369
|
-
```json
|
|
370
|
-
{
|
|
371
|
-
"permissions": {
|
|
372
|
-
"allow": ["mcp__github__get_issue", "mcp__linear__*"]
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
```
|
|
411
|
+
Denied at any level, allowed at none. A managed or user-global `deny` beats a project `allow`; deny beats ask; ask beats allow. Specificity doesn't reorder anything - a broad `deny` still blocks a narrower `allow`.
|
|
376
412
|
|
|
377
413
|
---
|
|
378
414
|
|
|
379
415
|
## Design decisions
|
|
380
416
|
|
|
381
|
-
The
|
|
417
|
+
The reasoning behind the behavior, so you don't reverse-engineer it:
|
|
382
418
|
|
|
383
|
-
1. **Rules over a hardcoded safety matrix.**
|
|
419
|
+
1. **Rules over a hardcoded safety matrix.** No built-in `.env` file-read prompt, no bypass-surviving recursive-delete prompt. Sensitive reads are a rules concern (`ask`/`deny`), so each project decides what's sensitive. One carve-out: the read-only **shell** fast path screens secret args, so `cat .env` prompts instead of riding `cat`'s free pass.
|
|
384
420
|
2. **One ordered pipeline.** The order in [How a decision is made](#how-a-decision-is-made) is the spec; tests pin it.
|
|
385
|
-
3. **Asymmetric
|
|
386
|
-
4. **External directories are a separate gate**,
|
|
421
|
+
3. **Asymmetric matching.** Deny/ask aggressive, allow conservative. Failing toward a prompt is the safe direction.
|
|
422
|
+
4. **External directories are a separate gate**, before the mode rule, with skill read roots and plan-file writes exempted where the mode sanctions them.
|
|
387
423
|
5. **The circuit breaker survives bypass and is spoof-resistant.**
|
|
388
|
-
6. **`disableBypass` is enforced at every layer** - engine, CLI entry, TUI mode cycle,
|
|
389
|
-
7. **Shell command
|
|
390
|
-
8. **Generic tools get a real prompt.** MCP/custom tools render a generic permission prompt
|
|
391
|
-
9. **Recursive deletes prompt under auto-accept.**
|
|
392
|
-
10. **Policy denials guide the model; only human vetoes end the turn.** Every engine denial carries actionable guidance so the model adapts and the turn continues.
|
|
424
|
+
6. **`disableBypass` is enforced at every layer** - engine, CLI entry, TUI mode cycle, decision store.
|
|
425
|
+
7. **Shell command args aren't a file boundary.** Absolute paths in a command string follow shell rules and the mode, not the file-tool boundary.
|
|
426
|
+
8. **Generic tools get a real prompt.** MCP/custom tools render a generic permission prompt, not a fake file-edit prompt.
|
|
427
|
+
9. **Recursive deletes prompt under auto-accept.** Safe direction: bulk irreversible deletion isn't an edit.
|
|
428
|
+
10. **Policy denials guide the model; only human vetoes end the turn.** Every engine denial carries actionable guidance, so the model adapts and the turn continues.
|
|
393
429
|
|
|
394
430
|
---
|
|
395
431
|
|
|
396
432
|
## Known limits
|
|
397
433
|
|
|
398
|
-
Documented edges of the current engine
|
|
434
|
+
Documented edges of the current engine:
|
|
399
435
|
|
|
400
|
-
- **Wildcard `include` patterns in `read_multiple_files` expand inside the tool**, so an exact-file deny rule (`Read(.env)`)
|
|
401
|
-
- **No strict read allowlist:** `allow: ["Read(src/**)"]`
|
|
402
|
-
- **Shell path
|
|
403
|
-
- **An allow rule never matches an opaque command:**
|
|
404
|
-
- **Parameter rules only see parameters the model sends:**
|
|
436
|
+
- **Wildcard `include` patterns in `read_multiple_files` expand inside the tool**, so an exact-file deny rule (`Read(.env)`) can't match a wildcard include (`*.env`) before expansion. Directory-tree rules (`Read(//etc/**)`) still cover this, because the tool's `targetDirectory` is matched as a touched path.
|
|
437
|
+
- **No strict read allowlist:** `allow: ["Read(src/**)"]` doesn't deny reads outside `src/**` - reads are free by default; only `ask`/`deny` restrict them. A deny-everything-not-listed read mode is out of scope as a separate change.
|
|
438
|
+
- **Shell path args aren't external-dir gated:** `cat /outside/file` follows shell rules and the mode, not the workspace boundary. Gate it with `deny: ["Shell(cat /outside/*)"]`-style rules.
|
|
439
|
+
- **An allow rule never matches an opaque command:** if the parser can't fully understand a command, it can still be deny/ask-matched against the raw string, but never auto-allows - it prompts.
|
|
440
|
+
- **Parameter rules only see parameters the model sends:** an omitted parameter never matches.
|
|
405
441
|
|
|
406
442
|
---
|
|
407
443
|
|
|
@@ -409,4 +445,4 @@ Documented edges of the current engine - behaviors to know about when writing ru
|
|
|
409
445
|
|
|
410
446
|
- [Plan Mode](./plan-mode.md) - read-only exploration and planning.
|
|
411
447
|
- [Hooks](./hooks.md) - run your own scripts to allow, deny, or audit tool calls at runtime.
|
|
412
|
-
- [MCP](./mcp.md) - connect external tools
|
|
448
|
+
- [MCP](./mcp.md) - connect external tools the engine gates by server and tool name.
|