Skip to content

Commit 4c0587e

Browse files
committed
Merge branch 'permission-mechanism'
2 parents a5a53ea + 5d6f727 commit 4c0587e

20 files changed

Lines changed: 2214 additions & 24 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ dist/
55
.vscode/
66
*.tgz
77
*.log
8+
.deepcode/settings.json

README-en.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ When the AI assistant completes a task, Deep Code can automatically execute a no
137137

138138
For detailed configuration instructions, see: [docs/notify_en.md](docs/notify_en.md)
139139

140+
### Does Deep Code only support YOLO mode?
141+
142+
No. Deep Code has a built-in fine-grained permission control mechanism that lets you confirm operations before the AI assistant executes shell commands, reads/writes files, accesses the network, and more. You can configure each permission scope's policy — always allow, always ask, or deny — via the `permissions` field in `settings.json`. See [docs/permission.md](docs/permission.md) for details.
143+
140144
## Contributing
141145

142146
Contributions are welcome! Here's how to get started:

README-zh_CN.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ Deep Code 支持 MCP(Model Context Protocol),可以连接 GitHub、浏览
122122

123123
详细配置指南:[docs/notify.md](docs/notify.md)
124124

125+
### Deep Code 只支持 YOLO 模式吗?
126+
127+
不是。Deep Code 内置了细粒度的权限控制机制,支持在 AI 助手执行 Shell 命令、读写文件、访问网络等操作前进行确认。你可以通过 `settings.json` 中的 `permissions` 字段按需配置每种权限范围的策略:始终允许、始终询问、或直接拒绝。详见 [docs/permission.md](docs/permission.md)
128+
125129
### 是否支持 Coding Plan?
126130

127131
支持。只要把 `~/.deepcode/settings.json``env.BASE_URL` 配置为 OpenAI 兼容的接口地址就行。以火山方舟的 Coding Plan 为例:
@@ -136,6 +140,7 @@ Deep Code 支持 MCP(Model Context Protocol),可以连接 GitHub、浏览
136140
"thinkingEnabled": true
137141
}
138142
```
143+
139144
## 贡献
140145

141146
欢迎贡献代码!以下是参与方式:

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ Deep Code 支持 MCP(Model Context Protocol),可以连接 GitHub、浏览
122122

123123
详细配置指南:[docs/notify.md](docs/notify.md)
124124

125+
### Deep Code 只支持 YOLO 模式吗?
126+
127+
不是。Deep Code 内置了细粒度的权限控制机制,支持在 AI 助手执行 Shell 命令、读写文件、访问网络等操作前进行确认。你可以通过 `settings.json` 中的 `permissions` 字段按需配置每种权限范围的策略:始终允许、始终询问、或直接拒绝。详见 [docs/permission.md](docs/permission.md)
128+
125129
### 是否支持 Coding Plan?
126130

127131
支持。只要把 `~/.deepcode/settings.json``env.BASE_URL` 配置为 OpenAI 兼容的接口地址就行。以火山方舟的 Coding Plan 为例:
@@ -136,6 +140,7 @@ Deep Code 支持 MCP(Model Context Protocol),可以连接 GitHub、浏览
136140
"thinkingEnabled": true
137141
}
138142
```
143+
139144
## 贡献
140145

141146
欢迎贡献代码!以下是参与方式:

docs/issue_0522.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# Deep Code Permission System (设计文档)
2+
3+
scopes是枚举值,列表如下:
4+
5+
```
6+
# PermissionScope
7+
read-in-cwd
8+
read-out-cwd
9+
write-in-cwd
10+
write-out-cwd
11+
delete-in-cwd
12+
delete-out-cwd
13+
query-git-log
14+
mutate-git-log
15+
network
16+
mcp
17+
```
18+
19+
settings.json的配置项(例子):
20+
21+
```
22+
{
23+
"permissions": {
24+
"allow": [
25+
"write-in-cwd"
26+
],
27+
"deny": [
28+
"write-out-cwd"
29+
],
30+
"ask": [
31+
"read-out-cwd"
32+
],
33+
"defaultMode": "allowAll|askAll" // 默认是allowAll
34+
}
35+
}
36+
```
37+
38+
工具和PermissionScope可能的对应关系:
39+
40+
- read: read-in-cwd, read-out-cwd
41+
- write: write-in-cwd, write-out-cwd
42+
- edit: write-in-cwd, write-out-cwd
43+
- WebSearch: network
44+
- mcp__*: mcp
45+
- bash: 每一次bash命令需要的scope在sideEffects字段中。如果sideEffects字段为undefined|null,或者sideEffects包含了"unknown"则总是ask
46+
- 其他: 无权限要求,总是允许
47+
48+
## bash tool的参数schema新增sideEffects字段
49+
50+
目标:让LLM在每一次调用`bash`时显式声明该命令可能需要的权限范围,后端只信任这个结构化字段,不从自然语言`description`中推断权限。
51+
52+
需要同步修改两处schema:
53+
54+
1. `src/prompt.ts`里的`getTools()`内置`bash`工具定义。
55+
2. `templates/tools/bash.md`里的`bash`工具说明和JSON schema示例。
56+
57+
新增字段:
58+
59+
```
60+
sideEffects: PermissionScope[] | ["unknown"]
61+
```
62+
63+
`bash`可声明的scope只包含文件系统、Git历史和网络权限,不包含`mcp`
64+
65+
```
66+
read-in-cwd
67+
read-out-cwd
68+
write-in-cwd
69+
write-out-cwd
70+
delete-in-cwd
71+
delete-out-cwd
72+
query-git-log
73+
mutate-git-log
74+
network
75+
unknown
76+
```
77+
78+
建议schema如下:
79+
80+
```json
81+
{
82+
"type": "object",
83+
"properties": {
84+
"command": {
85+
"description": "The command to execute",
86+
"type": "string"
87+
},
88+
"description": {
89+
"description": "Clear, concise description of what this command does in active voice.",
90+
"type": "string"
91+
},
92+
"sideEffects": {
93+
"description": "Permission scopes required by this bash command. Use [] only for commands that do not read, write, delete, or access the network. Use [\"unknown\"] when the effects cannot be classified safely.",
94+
"type": "array",
95+
"items": {
96+
"type": "string",
97+
"enum": [
98+
"read-in-cwd",
99+
"read-out-cwd",
100+
"write-in-cwd",
101+
"write-out-cwd",
102+
"delete-in-cwd",
103+
"delete-out-cwd",
104+
"query-git-log",
105+
"mutate-git-log",
106+
"network",
107+
"unknown"
108+
]
109+
},
110+
"uniqueItems": true
111+
}
112+
},
113+
"required": [
114+
"command",
115+
"sideEffects"
116+
],
117+
"additionalProperties": false
118+
}
119+
```
120+
121+
字段语义:
122+
123+
- `sideEffects: []`表示命令不需要权限,例如`date``node --version`这类只读取进程环境或输出版本信息的命令。
124+
- `sideEffects`必须按最小必要权限填写;例如`rg foo src``["read-in-cwd"]``npm install`通常是`["write-in-cwd", "network"]`
125+
- 如果命令访问项目目录之外的路径,需要使用`*-out-cwd`;例如`cat /etc/hosts``["read-out-cwd"]`
126+
- 删除类操作使用`delete-*`;如果同一条命令还会写入其他文件,再同时声明对应的`write-*`
127+
- 查询Git历史使用`query-git-log`;例如`git log``git show HEAD``git blame``git diff HEAD~1..HEAD`这类读取提交历史、提交对象或历史diff的命令。
128+
- 修改Git历史或引用使用`mutate-git-log`;例如`git commit``git reset``git rebase``git merge``git cherry-pick``git tag`这类会创建提交、移动引用或改写提交图的命令。
129+
- Git命令如果同时读写工作区文件,也需要同时声明文件系统scope;例如`git checkout -- src/foo.ts`需要`["write-in-cwd"]``git reset --hard HEAD~1`需要`["write-in-cwd", "mutate-git-log"]`
130+
- `unknown`只能单独出现为`["unknown"]`,不能和其他scope混用。
131+
132+
示例:
133+
134+
```json
135+
{ "command": "date", "description": "Show current date", "sideEffects": [] }
136+
{ "command": "rg \"TODO\" src", "description": "Search TODO markers in source files", "sideEffects": ["read-in-cwd"] }
137+
{ "command": "npm install", "description": "Install package dependencies", "sideEffects": ["write-in-cwd", "network"] }
138+
{ "command": "rm -rf dist", "description": "Delete build output directory", "sideEffects": ["delete-in-cwd"] }
139+
{ "command": "curl -s https://example.com", "description": "Fetch example.com response", "sideEffects": ["network"] }
140+
{ "command": "git show --stat HEAD", "description": "Show file statistics for HEAD", "sideEffects": ["query-git-log"] }
141+
{ "command": "git blame src/prompt.ts", "description": "Show line authorship for prompt source", "sideEffects": ["read-in-cwd", "query-git-log"] }
142+
{ "command": "git reset --hard HEAD~1", "description": "Reset branch and worktree to previous commit", "sideEffects": ["write-in-cwd", "mutate-git-log"] }
143+
```
144+
145+
## 核心数据结构设计
146+
147+
```
148+
export type UserPromptContent = {
149+
text?: string;
150+
imageUrls?: string[];
151+
skills?: SkillInfo[];
152+
+ permissions?: [{toolCallId: "...", permission: "allow|deny"}];
153+
+ alwaysAllows?: ["<PermissionScope>"];
154+
};
155+
156+
export type SessionEntry = {
157+
id: string;
158+
...
159+
toolCalls: unknown[] | null; // 例如:[{"id":"...","function":{"name":"bash","arguments":"{\"command\": \"...\", \"description\": \"...\"}"}}]
160+
status: SessionStatus;
161+
+ askPermissions?: [{toolCallId: "...", scopes: ["<PermissionScope>"], name: "...", command: "...", description?: "..."}];
162+
};
163+
164+
export type SessionStatus = "... | "completed" | "interrupted" | "ask_permission"; // 新增 ask_permission 状态
165+
166+
export type SessionMessage = {
167+
...
168+
meta?: MessageMeta;
169+
...
170+
};
171+
172+
export type MessageMeta = {
173+
...
174+
+ permissions?: [{toolCallId: "...", permission: "allow|deny|ask"}];
175+
+ userPrompt?: UserPromptContent; //对于role为user的消息,持久化userPrompt可方便后续排查问题
176+
};
177+
```
178+
179+
## 前端流程
180+
181+
如果当前会话状态不是ask_permission,则保持现状。会话状态是ask_permission时:
182+
183+
对SessionEntry.askPermissions中每一个toolCallId的每一个scope,显示权限弹窗(示例):
184+
185+
```
186+
<toolName>
187+
188+
<command>
189+
<description>
190+
191+
Do you want to proceed?
192+
❯ 1. Yes
193+
2. Yes, and always allow <explain-of-the-scope>
194+
3. No
195+
```
196+
197+
注意对于read/write/edit的`<command>`,格式可以是"工具名称+相对或绝对文件路径",例如:`read ~/dev/main.c`
198+
199+
如果在权限弹窗过程中,用户按Esc,则走现有的interrupt流程(会话状态也应该变成"interrupted")。
200+
201+
提醒注意一种情况:例如askPermissions里面有好几个item的scopes是`["write-in-cwd"]`,如果用户已经在第一个权限弹窗选了"always allow write in CWD `~/dev/qrcode_test/`",则后面的几个scopes是`["write-in-cwd"]`的item就不用显示权限弹窗了。
202+
203+
如果用户完成了所有权限弹窗的选择,则判断:
204+
205+
1. 如果用户提交的结果中包含deny,则需要用户输入user prompt,按回车手动提交replySession()。
206+
- 如果用户没有输入user prompt就退出了,或者切换到了其他会话。则重新开始这个会话时,由于会话状态还是ask_permission,则会重新显示权限弹窗,要求用户选择。
207+
2. 如果用户提交的结果中不包含deny,则以`/continue`作为UserPromptContent.text内容,前端自动提交replySession()。
208+
209+
210+
## 后端流程
211+
212+
后端主要是对replySession()和activateSession()进行升级:
213+
214+
1. 支持传入UserPromptContent.permissions和alwaysAllows
215+
2. 如果UserPromptContent.alwaysAllows非空,将其中的scopes追加写入项目级别的settings.json配置文件(`permissions.allow`字段),避免重复写入已存在的项。
216+
3. 检查当前会话消息列表末尾是否存在连续的role为assistant的有tool_calls的消息,也就是"待执行消息"。如果没有,则走现有流程。
217+
4. 对于每一条待执行消息,先检查UserPromptContent.permissions中对应的toolCallId的用户授权是allow还是deny
218+
- 如果是allow,则正常执行这个toolCall
219+
- 如果是deny,则直接返回失败结果,报错信息提示LLM用户禁用相关权限。例如:
220+
```
221+
{
222+
"ok": false,
223+
"name": "edit",
224+
"error": "用户已禁用了在项目目录之外修改文件的权限,请不要尝试用任何方式修改目录之外的文件"
225+
}
226+
```
227+
5. 如果对于某条待执行消息,在UserPromptContent.permissions没有出现对应的toolCallId的用户授权,则检查它的 SessionMessage.meta.permissions[].permission 是allow还是deny还是ask
228+
- 如果是allow,则正常执行这个toolCall
229+
- 如果是deny,则直接返回失败结果,报错信息提示LLM用户禁用相关权限
230+
- 如果是ask,则直接返回失败结果,报错信息提示LLM用户未授权执行。例如:
231+
```
232+
{
233+
"ok": false,
234+
"name": "edit",
235+
"error": "用户暂未授权执行,如果有必要,可重新尝试执行"
236+
}
237+
```
238+
- 如果不存在,则正常执行这个toolCall(兼容老版本会话数据)
239+
6. 当LLM返回了新的待执行消息时,不要立即执行,而是:
240+
1. 根据配置的permissions和defaultMode,计算出SessionMessage.meta.permissions字段
241+
2. 如果存在一个待执行消息的SessionMessage.meta.permissions[].permission是ask,则把SessionEntry.status设置为"ask_permission",并设置好SessionEntry.askPermissions,然后退出activateSession,这样就回到了上面的前端流程。

docs/permission.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Deep Code 权限机制
2+
3+
Deep Code 内置了一套细粒度的权限控制机制,在 AI 助手执行工具调用(如执行 Shell 命令、读写文件、访问网络等)前,根据用户配置的策略决定是自动放行、直接拒绝、还是弹出交互式确认。
4+
5+
## 概述
6+
7+
每次 AI 助手调用工具时,系统会自动分析该操作涉及的**权限范围(Permission Scope)**,然后根据 `settings.json` 中的权限配置做出决策。对于需要用户确认的操作,会在终端中弹出交互式选择界面,用户可以选择:
8+
9+
- **Yes** — 仅本次放行
10+
- **Yes, and always allow** — 本次放行,并将该权限范围写入项目配置文件,后续同类操作不再询问
11+
- **No** — 拒绝本次操作
12+
13+
## 权限范围
14+
15+
Deep Code 定义了以下 10 种权限范围,覆盖了工具调用的各类风险场景:
16+
17+
| 权限范围 | 说明 |
18+
| -------- | ---- |
19+
| `read-in-cwd` | 读取当前工作区内的文件 |
20+
| `read-out-cwd` | 读取当前工作区外的文件 |
21+
| `write-in-cwd` | 在当前工作区内创建或覆写文件 |
22+
| `write-out-cwd` | 在当前工作区外创建或覆写文件 |
23+
| `delete-in-cwd` | 删除当前工作区内的文件 |
24+
| `delete-out-cwd` | 删除当前工作区外的文件 |
25+
| `query-git-log` | 查询 Git 历史(如 `git log``git show``git blame`|
26+
| `mutate-git-log` | 修改 Git 历史(如 `git commit``git rebase``git tag`|
27+
| `network` | 访问网络(如 `curl``npm install` 等联网操作) |
28+
| `mcp` | 调用 MCP 外部工具 |
29+
30+
此外还有一个特殊的 `unknown` 范围,当 LLM 无法准确分类命令的副作用时使用,**`unknown` 总是触发询问**
31+
32+
## 权限配置
33+
34+
`~/.deepcode/settings.json`(用户级)或 `.deepcode/settings.json`(项目级)中通过 `permissions` 字段配置:
35+
36+
```json
37+
{
38+
"permissions": {
39+
"allow": [],
40+
"deny": [],
41+
"ask": [],
42+
"defaultMode": "allowAll"
43+
}
44+
}
45+
```
46+
47+
### 配置项说明
48+
49+
| 字段 | 类型 | 说明 |
50+
| ---- | ---- | ---- |
51+
| `allow` | `string[]` | 始终自动放行的权限范围列表 |
52+
| `deny` | `string[]` | 始终自动拒绝的权限范围列表 |
53+
| `ask` | `string[]` | 始终弹出询问的权限范围列表 |
54+
| `defaultMode` | `"allowAll"` \| `"askAll"` | 未在 `allow`/`deny`/`ask` 中明确列出的权限范围的默认处理方式。默认为 `"allowAll"` |
55+
56+
### 优先级规则
57+
58+
当一个工具调用涉及多个权限范围时,决策按以下优先级进行:
59+
60+
1. 若任一范围命中 `deny`**拒绝**
61+
2. 若任一范围命中 `ask`**询问**
62+
3. 若所有范围均在 `allow` 中 → **自动放行**
63+
4. 否则 → 按 `defaultMode` 处理
64+
65+
### 示例:宽松模式(默认)
66+
67+
```json
68+
{
69+
"permissions": {
70+
"defaultMode": "allowAll"
71+
}
72+
}
73+
```
74+
75+
默认行为:所有操作自动放行,无需确认。
76+
77+
### 示例:严格模式
78+
79+
```json
80+
{
81+
"permissions": {
82+
"allow": ["read-in-cwd", "write-in-cwd", "query-git-log"],
83+
"defaultMode": "askAll"
84+
}
85+
}
86+
```
87+
88+
此配置的效果:
89+
- 工作区内读写、Git 查询 → 自动放行
90+
- 其他操作都需要用户确认。
91+
92+
93+
## 持久化机制
94+
95+
当用户在权限提示中选择 "Yes, and always allow" 后,对应的权限范围会被写入当前项目的 `.deepcode/settings.json` 文件中:
96+
97+
- 新增范围会追加到 `permissions.allow` 列表
98+
- 如果该范围之前存在于 `deny``ask` 中,会被自动移除
99+
- 不会重复写入已存在的范围
100+
101+
这样后续同类操作就不再询问。

0 commit comments

Comments
 (0)