权限与人工确认
读完这页你能做到四件事:
- 按场景选对权限模式,不再被每一步打断,也不至于让它乱改代码
- 写出精确的
allow/deny/ask规则(含 bash 通配、路径前缀、MCP、子代理) - 知道
--dangerously-skip-permissions跳过了哪些层(答案:全部,只留审计) - 看懂
→ 需确认(危险命令: …)这类日志,判断是规则拦的还是安全层拦的
完整字段类型与默认值不在这页,去settings.json 字段查; 工具名的确切拼写去内置工具。这页只讲怎么选、怎么写。
快速上手
三种最常用的调法,按侵入性从小到大:
# 1. 只放行本次要用的命令(最推荐,一次性)
sid-code --allow-tool "Bash(npm *)" --allow-tool "Bash(git status)"
# 2. 换个档位:文件修改自动接受,跑命令仍然问
sid-code --permission-mode acceptEdits
# 3. 全部不问(只在信得过的仓库用)
sid-code --dangerously-skip-permissions会话里临时加规则,不用重启:
/allow Bash(npm *) 仅本次会话生效
/allow Bash(npm *) -p 写进 ~/.sid-code/settings.json,跨会话保留
/deny Bash(curl *) -p --scope project 写进项目级配置,团队共享想固化成配置,写 ~/.sid-code/settings.json:
{
"permissions": {
"allow": ["Bash(ls *)", "Bash(npm *)", "Read(/src/**)"],
"deny": ["Read(./.env)", "Bash(curl *)"],
"ask": ["Bash(git push *)"]
}
}启动时会打印一行确认它被读到了(真实输出):
● [CONFIG] 权限规则: 2条 allow, 2条 deny, 1条 ask这行日志走 debug 输出——debug 默认开,但如果你在 settings 里显式设了 "debug": false,它不会出现。排除这种情况后这行还没出现就是配置没生效, 先检查文件路径和 JSON 语法,别急着怀疑规则写错。
八种权限模式
--permission-mode <mode>,或 settings 里的 permissionMode 字段。 第二列是状态栏显示的名字:
| mode | 状态栏显示 | 行为 | 什么时候用 |
|---|---|---|---|
default | Manual(手动) | 除只读操作外逐个问 | 第一次进一个陌生仓库 |
acceptEdits | 自动接受编辑 | 文件读写自动放行;bash 仍要问(工作目录内的 mkdir / touch / cp / sed 除外,rm / rmdir / mv 不在此列) | 日常最顺手的档位 |
plan | 计划模式 | 代码级强制只读,先出方案再动手 | 复杂改动,想先看它打算怎么干 |
auto | 自动模式 | 交给风险分类判断,低风险放行、高风险问;危险命令与受保护路径(hooks / commands / settings 等)分类器无权放行,仍要人工确认 | 熟悉的仓库里想少点打扰 |
always-allow | 全部允许 | 跳过规则与模式确认,安全层仍生效 | 一次性批量任务,你会盯着看 |
deny-write | 禁止写入 | 只读;写操作直接拒绝,不给确认机会 | 只让它分析、绝不许改 |
dontAsk | 静默拒绝 | 该问的一律当拒绝,不弹窗 | 无头脚本里不想挂住 |
dangerously-skip-permissions | 跳过权限(危险) | 最宽档,跳过全部检查(含危险命令拦截),只留审计记录。与 -y / --yes 不同——后者只自动批准普通的需确认操作,危险命令仍拦 | 容器 / 一次性沙箱 |
Shift+Tab 在会话里循环切换,实测顺序:
default → acceptEdits → auto → default → … # 常规
default → acceptEdits → auto → always-allow → default # 启动时开了 --dangerously-skip-permissionsplan 不在这个循环里——它是独立状态机,进出要用 /plan 或让它自己 exit_plan_mode;已经在 plan 态时按 Shift+Tab 会提示你走那条路。 deny-write / dontAsk 也不在循环里,只能用参数或配置指定。
always-allow 只在启动时就开了 --dangerously-skip-permissions 时才进循环(-y / --yes 不算;这是启动瞬间的快照, 不随会话中途切换漂移)。企业策略禁用 bypass 时(disableBypassPermissionsMode) 它也会被跳过,auto 直接回到 default。
plan 模式的读写边界
plan 模式下 read / grep 放行,edit / write / bash 一律拒绝, 提示是「计划模式下只允许只读操作」。唯一例外是它往 ~/.sid-code/plans/ 写计划文件—— 这条在代码里比只读检查更早放行,否则它连计划都存不下来。
规则语法
格式是 工具名 或 工具名(模式)。裸工具名匹配该工具的全部调用,带括号则再匹配参数。 下表每一行都逐条实测过:
| 规则 | 匹配什么 | 说明 |
|---|---|---|
Bash(*) | 所有 bash 命令 | * 跨 /,含带路径的命令 |
Bash(npm *) | npm run build ✅,npx foo ❌ | 尾部 * 是「这个命令加任意参数」 |
Bash(git status) | 只有这一条命令 | 无通配符即精确匹配 |
Bash(prefix:git ) | git 开头的命令 | 兼容语法,前缀语义(注意与 Bash(git *) 有一处差异:后者还匹配裸 git,prefix:git 不匹配) |
Read(/src/**) | <项目根>/src 下任意深度 | 单前导 / = 项目根相对,不是文件系统根 |
Read(./.env) | 当前目录的 .env | ./ 或裸路径 = cwd 相对 |
Edit(~/notes/**) | 主目录下 | ~/ = 主目录 |
Read(//etc/**) | 文件系统 /etc 下 | 双斜杠才是文件系统绝对路径 |
Agent(explore) | explore 类型的子代理 | 裸 Agent 匹配全部子代理 |
WebFetch(domain:github.com) | 抓取 github.com | 也支持 domain:*.example.com |
mcp__myserver | 该 server 的所有工具 | 服务器级匹配 |
mcp__* | 所有 MCP 工具 | 工具名位置也支持通配 |
最容易写错的一条
路径规则里 /src/** 指的是项目根下的 src,不是磁盘根目录的 /src。 要写文件系统绝对路径得用两个斜杠://etc/**。 这四种前缀(//、~/、/、./)会先归一成绝对路径再比对,写混了规则会静默不匹配—— 不报错,只是不生效。
三类规则的优先级
deny > ask > allow,且同类里带参数的规则优先于裸工具名。实测:
allow: ["Bash(git *)"] + deny: ["Bash(git push *)"]
git push --force → 规则拒绝: bash (匹配 Bash(git push *))
allow: ["Bash(*)"] + ask: ["Bash(rm *)"]
rm x → 规则要求确认: bash (匹配 Bash(rm *))所以「放行一大类、单独挖掉危险子集」是可行写法:allow: ["Bash(git *)"] 配 deny: ["Bash(git push *)", "Bash(git reset --hard *)"]。
deny 规则会提前告诉模型
deny 不只是拦调用,还会作为一段约束注入系统提示词,让模型一开始就不去试:
● [PROMPT] 附件: 权限约束(deny 规则)(0.0K tok, priority=38)实测配了 deny: ["Read(./.env)"] 后让它读 .env,它的回答直接引用了规则:
- `Read(./.env)` 被禁止(匹配即拒绝)
这是安全策略的一部分…我无法绕过该限制,也不会尝试变通手段
(如通过 bash、cat、子代理等方式间接读取)。这比「调用了再被拦」省一轮 token,也省掉一次无效工具调用。
配置层级
规则来自 9 个来源,每个来源有一个优先级数值,数值大的优先 (定义在 packages/core/src/permission/types.ts 的 RULE_SOURCE_PRIORITY):
| 优先级 | 来源 | 从哪来 | 典型用途 |
|---|---|---|---|
| -1(最低) | commandScoped | 斜杠命令 / Skill 自带的 allowed-tools | 只作用于触发它的那一轮,不与你的配置竞争 |
| 0 | session | 权限弹窗里选「总是允许」 | 本次会话临时放行 |
| 1 | command | /allow / /deny(不带 -p) | 会话里手动加 |
| 2 | cliArg | --allow-tool / --deny-tool | 一次性 |
| 3 | userSettings | ~/.sid-code/settings.json | 你自己的习惯 |
| 4 | projectSettings | <项目>/.sid-code/settings.json | 团队共享,提交 git |
| 5 | localSettings | <git 仓库根>/.sid-code/settings.local.json(启动目录下的旧文件也合并读取) | 你在这个项目里的私货,gitignore |
| 6 | flagSettings | --settings <文件或内联 JSON> 里的 permissions | 脚本 / CI 注入一套规则 |
| 7(最高) | policySettings | 企业策略 managed-settings.json | 公司管控;部署在系统级时用户改不掉,放在用户级时用户能删 |
Bash 权限弹窗里选「总是允许(持久)」时,规则写进 localSettings(git 仓库根的 settings.local.json),不写共享的 settings.json——你个人的放行规则不会随提交分发给整个团队 (对齐 Claude Code 的 "Yes, and don't ask again")。在仓库任意子目录启动,读写的都是同一份。
注意 CLI 参数(cliArg)的优先级低于你的用户级配置,--settings 注入的才高于磁盘上的三份文件。
企业策略文件按顺序取第一个存在的:平台系统级路径 (macOS /Library/Application Support/SidCode/managed-settings.json、 Linux /etc/sid-code/managed-settings.json、 Windows %PROGRAMDATA%\SidCode\managed-settings.json), 其次 ~/.sid-code/managed-settings.json。 /etc/sid-code/policy.json 是废弃路径,不再读取——写在那里的规则不会生效, 详见企业 policy 与安全边界。
allow / deny / ask 三个数组在各来源之间是合并而不是覆盖—— 项目级加的 deny 不会把用户级的 deny 冲掉;合并后仍按 deny > ask > allow 判定, 来源优先级决定的是同类规则的排序(也是 /permissions 阴影检测的依据)。
运行时查看与管理
配置写多了会忘记当前到底生效了哪些规则。两个命令补这块缺口:
/permissions:看当前生效的全部规则
无参数运行打开交互式权限管理面板,看当前权限模式与规则摘要(只读展示,增删规则仍走 /allow / /deny 子命令);带参数则输出文本视图:
/permissions输出按来源分组展示(用户级 / 项目级 / 本地级 / 会话级),每条标 ✓ allow / ✗ deny / ? ask:
权限模式: acceptEdits
[user] (3 条)
✓ allow: Bash(npm *)
✓ allow: Bash(git status)
✗ deny: Read(./.env)
[project] (2 条)
✗ deny: Bash(curl *)
? ask: Bash(git push *)比手翻 settings.json 多两个能力:
- 阴影检测:如果某条 allow 规则被更高优先级的 deny / ask 规则压制(deny 压制 = 永远拦死、ask 压制 = 永远要确认),会标出来提醒你——配得越多越容易出现,删掉就行。注意它只报行为冲突的遮蔽(allow 被 deny/ask 压),同类 allow 盖 allow 不在检测范围内
- 拒绝追踪:显示本次会话连续拒绝次数和累计拒绝次数,判断是不是卡在权限上反复撞墙
/add-dir:运行时加目录到白名单
写操作默认只允许启动时的工作目录(及 allowedDirectories 配置的目录);读操作没有工作区边界拦截,只有系统目录保护、symlink 逃逸检测这些安全层。临时需要让它写项目外的目录(或把读明确授权给它),不想改配置重启:
/add-dir /Users/me/reference-project实测输出:
✓ 已将目录加入当前会话可访问白名单: /Users/me/reference-project
(用户级运行时授权,仅本会话生效,不写入配置文件)三个要点:
- 仅当前会话生效,不落盘、不扩大项目配置白名单——关掉会话即失效
- 与
allowedDirectories配置项的区别:后者写进settings.json跨会话永久生效;/add-dir是一次性的 - 管理当前白名单:
/add-dir --list 查看当前会话加了哪些目录
/add-dir --remove <目录> 从本会话白名单移除这是用户主动授权,不是安全漏洞
/add-dir 是你在终端里亲手输入的,属于显式人工授权——与「项目配置自动扩大目录白名单」那种静默扩大攻击面的行为性质不同。后者是被安全层禁止的。
macOS Seatbelt 沙箱:操作系统级隔离
上面讲的规则层、危险命令层、敏感文件层都是应用内的软约束——它们拦的是「模型想让 sid-code 做什么」。 但 bash 工具真正执行命令时,命令本身能碰什么文件、能不能联网,应用层是管不到的。 macOS 上 sid-code 还有一道操作系统级的硬隔离:Seatbelt 沙箱(packages/core/src/permission/sandbox.ts)。
它和前面几层是什么关系
| 层 | 拦什么 | 谁执行 |
|---|---|---|
| 权限规则(allow/deny/ask) | 模型想调哪个工具 | sid-code 应用层 |
| 危险命令拦截 | 命令文本本身危险(rm -rf /) | sid-code 应用层 |
| 敏感文件保护 | 命令碰 .env/*.pem 等 | sid-code 应用层 |
| Seatbelt 沙箱 | 命令执行时能碰哪些文件/网络 | macOS 内核 |
前三层是「调不调用」的决策,沙箱是「调用之后、命令真正执行时」的操作系统兜底。即使前三层全放行了, 沙箱仍能在内核级挡住命令越界读写。它只在 macOS 上生效(process.platform === "darwin"), 其他平台降级为无沙箱,启动时会告警一次。
沙箱 profile 长什么样
沙箱用 macOS 自带的 sandbox-exec 生成一份 Seatbelt profile(packages/core/src/permission/sandbox.ts), 把每条 bash 命令包进 sandbox-exec -p '<profile>' /bin/sh -c '<command>'。profile 默认 (deny default)(默认全拒)后逐项放行,允许/禁止的路径:
| 类别 | profile 规则 | 说明 |
|---|---|---|
| 进程 | 允许 process-exec / process-fork | 不放行这个,任何命令都跑不起来 |
| 工作目录 | 允许读写 cwd 子树 | 这是你让它改的代码所在 |
| 系统工具链 | 只读 /usr/lib /usr/bin /usr/local /Library/Developer /Applications/Xcode.app | 跑编译/解释器需要 |
| 临时目录 | 读写 /tmp /private/tmp | 命令临时文件 |
| 家目录工具 | 只读 ~/.bun ~/.nvm ~/.npm ~/.cargo | 运行包管理器需要 |
| 敏感目录 | 显式 deny ~/.ssh ~/.gnupg ~/.sid-code | SSH 密钥、GPG、sid-code 自身配置与轨迹 |
| 网络 | 只放行 localhost(profile 里写的是它解析出的 IP,如 127.0.0.1:*) | 防 bash 命令外发数据;主机名解析失败时该主机被丢弃并告警 |
~/.ssh、~/.gnupg、~/.sid-code 是显式 deny 的——即使权限规则放行了 bash,命令也 碰不到这三处。这层防御不依赖应用层判断,是内核强制的。
怎么开
settings.json 里只有两个字段(定义在 packages/core/src/config/config.ts,CLI 在 packages/cli/src/cli.ts 构造沙箱时消费):
{
"enableSandbox": true,
"sandboxAutoAllowBash": false
}| 字段 | 默认 | 作用 |
|---|---|---|
enableSandbox | false | 总开关,开=bash 命令进沙箱 |
sandboxAutoAllowBash | false | 沙箱启用时是否自动放行 bash(少弹窗),要显式写 true 才生效 |
额外读写目录与网络白名单目前不能从 settings.json 配,profile 用上表的内置默认值。
sandboxAutoAllowBash 默认是 false,这是改过的(原先默认放行,理由是「开了沙箱就不用再逐条确认」)。 改的原因是那个理由的前提不成立:profile 放开了整个工作目录的写权限, echo x > .git/hooks/pre-commit 这类命令在内核层完全合法——沙箱兜不住工作区内的改动。 所以「少弹窗」改成你显式选择的行为,而不是装上就有。
即使打开自动放行,两条边界不变:危险命令拦截与敏感重定向检测在它之前,照样生效; plan / deny-write 两个只读模式不会因为沙箱被打穿。
能防什么 / 不能防什么
能防(内核级,应用层绕不过):
- 命令读写工作目录外的文件(上表放行的系统目录与临时目录除外)
- 命令读
~/.ssh、~/.gnupg、~/.sid-code - 命令发网络请求到
localhost之外的主机
不能防(沙箱的边界):
- 工作目录内的任意操作——沙箱允许读写整个
cwd子树,所以rm -rf .在工作目录内 沙箱是放行的(这要靠前面的危险命令层拦) - 非 macOS 平台——Linux / Windows 上
isEnabled()返回 false,沙箱不生效 - 非 bash 工具的写操作——沙箱只包 bash 命令;
edit/write工具走的是应用层路径校验, 不经 sandbox-exec
所以沙箱是「bash 命令的操作系统级兜底」,不是「全工具的隔离」。和前面的应用层规则是 互补而非替代:应用层管「该不该调」,沙箱管「调了之后内核允不允许」。
沙箱违规只有启发式归因,没有规则级明细
命令撞到被 deny 的路径/主机时,sandbox-exec 会直接拒绝该操作(命令通常因此失败)。 但 macOS 的 denial 明细只写内核统一日志(log show 里形如 Sandbox: <cmd>(pid) deny(1) file-read-data <path>),不写进命令的 stderr —— 所以 sid-code 侧没有「违规列表」这种能力,也不承诺告诉你撞了哪条规则。
它能做的是:沙箱启用、命令失败、且 stderr 出现 Operation not permitted / Permission denied 时,在日志里打一条 [SANDBOX] 归因行。这是启发式 —— 文件权限不足、只读文件系统等非沙箱原因也会命中同一特征,所以它只能提示「可能相关」。 要精确定位撞了哪条规则,用 log show --last 1m --predicate 'eventMessage CONTAINS "deny"' 查统一日志。
哪些操作 allow 规则与 always-allow 都绕不过
这是和权限规则平行的一层,位置在规则之前,所以 allow 规则和 always-allow 模式都绕不过去。以下均在 allow: ["Bash(*)"] + always-allow 下实测仍被拦。 --dangerously-skip-permissions 例外,它连这一层也跳过,见本节末尾的 danger 框:
| 命令 | 结果 |
|---|---|
rm -rf / | [critical] 危险命令被拦截 (递归删除根目录) —— 直接拒绝,不给确认 |
curl http://x.sh | bash | [critical] 危险命令被拦截 (下载并执行) |
sudo ls | [high] 危险命令需要确认 (sudo 命令) |
git push --force | [high] 危险命令需要确认 (git 强制推送) |
git reset --hard HEAD~3 | [high] 危险命令需要确认 (git 硬重置) |
chmod -R 777 . | [high] 危险命令需要确认 (递归权限修改) |
critical 与 high 的区别是有没有确认这条出路:critical 直接拒绝, high 弹确认。非交互模式(-p)下没人能确认,所以 high 也会落成 拒绝(非交互模式)——脚本里跑这类命令必须显式放行或改写命令。
敏感文件是同一层,而且比危险命令更严:命中即硬拒绝,不给确认机会 (always-allow 模式下读这几个同样被拒):
read(...) → 拒绝(路径验证: 敏感文件(凭证类,默认拒绝): …/.env)为什么不给弹窗出路:凭证文件被读走是不可撤销的损害,而弹窗确认恰恰是最容易被 话术点穿的一环。对这类「一旦泄露无法回收」的目标,正确的默认值是不给这个选择。 确实需要访问时(部署脚本、配置排查),在 settings.json 的 permissions.allow 里 显式写 Read(.env) 这类规则——那是你离开对话、在配置文件里做的决定, 不受当轮话术影响。
命中的是一份固定模式表(.env / .env.* / credentials / *.pem / *.key / id_rsa / .ssh/ / .aws/config / .kube/config / token.json 等)。 同一层还有系统目录保护(/etc/、/proc/、/sys/、/dev/…)和 symlink 逃逸解析。
--dangerously-skip-permissions 的真实边界
它跳过全部检查,包括上面的危险命令拦截、敏感文件保护和路径校验,只留一条审计记录。 rm -rf / 在它下面不会被拦。唯一能约束它的是企业策略 disableBypassPermissionsMode (禁用后带这个参数启动会直接报错退出)。 -y / --yes 不一样:它只自动批准普通的需确认操作,危险命令触发的确认仍然拦截。 sc 这个别名就等价于带上 --dangerously-skip-permissions,所以别在重要仓库里用 sc。 判断标准:这个目录里的东西全丢了你能不能接受。不能,就别用。
常见问题
它一直问,怎么少问一点
按这个顺序试,从最安全的开始:
- Shift+Tab 切到
acceptEdits——文件改动不问了,命令还问。多数人到这一步就够 - 把你反复批准的那几条命令写进
allow:/allow Bash(npm *) -p - 还嫌烦再考虑
auto模式,让风险分类替你判断 --dangerously-skip-permissions是最后手段,且只在一次性 / 沙箱环境
规则写了但没生效
三个检查点,按顺序:
# 1. 启动时那行有没有出现,条数对不对
sid-code 2>&1 | grep "权限规则"
# → ● [CONFIG] 权限规则: 2条 allow, 2条 deny, 1条 ask
# 2. 路径规则的前缀写对了吗(/src/** 是项目根相对,//src/** 才是文件系统根)
# 3. 有没有被更高优先级的来源覆盖(企业策略 > --settings > 本地 > 项目 > 用户 > --allow-tool)最常见的是第 2 条。Read(/Users/me/proj/src/**) 这种写法会被当成 「项目根下的 Users/me/proj/src」,永远匹配不上——要么改用 // 开头,要么写 /src/**。
非交互模式(-p)下工具全被拒了
-p 没有人能点确认,所以任何落到「需确认」的请求都会变成 拒绝(非交互模式)。脚本里要么用 --allow-tool 精确放行, 要么 --permission-mode acceptEdits,要么 --dangerously-skip-permissions(沙箱里)。
想知道某次到底是谁拦的
看日志里 [PERMISSION] 那行,括号里写了判定来源:
bash(ls -la) → 允许(工具级checkPermissions)
bash(sudo ls) → 需确认(危险命令: sudo 命令)
bash(sudo ls) → 拒绝(非交互模式)
write(...) → 允许(acceptEdits模式)危险命令 是安全层,规则拒绝 是你写的规则,模式 是权限档位。 三者对应三种不同的改法,别搞混。
allow 规则会穿透 plan 模式
一个需要知道的实现细节:allow 规则的判定早于 plan 模式的只读检查。 配了 allow: ["Bash(*)"] 之后进 plan 模式,bash 仍然能跑。 要让 plan 模式的只读保证真的成立,就不要给写类工具配宽泛的 allow 规则。
搜索词和抓取的网址会发往哪里
WebSearch 的搜索词由模型生成,可能夹带代码片段、文件路径或报错原文。 配了 search.searxngUrl(或环境变量 SEARXNG_URL)就发往那台 SearXNG,没配就发往 duckduckgo.com。 WebFetch 把模型要抓的整个 URL(含查询参数)发往对应网站。
想收紧,有两种做法:
换成自建 SearXNG,搜索词只到自己的服务器:
json{ "search": { "backend": "searxng", "searxngUrl": "https://searxng.internal.example.com" } }用权限规则禁用或限定范围,例如
"deny": ["WebSearch"]、"allow": ["WebFetch(domain:*.example.com)"]。
全部出网通道的对照见隐私与数据流向。
相关
隐私与数据流向 —— 什么会离开本机、隐私级别、脱敏边界
settings.json 字段 ——
permissions段的完整字段类型与默认值内置工具 —— 规则里工具名的确切拼写
交互模式与键位 —— Shift+Tab 及其他快捷键
Plan Mode 与 Todo —— plan 模式的完整用法
企业 policy 与安全边界 —— 用
managed-settings.json做团队管控