dsh-auto-approval-frame
English summary · 中文完整说明(默认)
DeepSeek Harness 的审批与审计插件。每次工具调用,它替你先看一眼:能按你的规则放行的直接放行,需要判断的交给模型,模型也说不准的再问你。每次判定都记进同一本可校验的台账。
「不用模型费直接放行」不是代码里的特权,它就是你规则表里的一行:能停用、能换档、能调序,台账里说得出是哪一行放的。
目录
五分钟上手 · 安装 · 权限档 · 拦截梯 · 配置 · 审计库 · 它到底看什么 · 边界 · 秘密回填 · AI 审查怎么判 · 明确不做 · English · 开发说明
五分钟上手
装完到能干活,四步。后面是使用说明,装完能跑就可以跳过,出问题再回来查。
一、装。 插件市场搜包名 dsh-auto-approval-frame,装完重启 DSH;或命令行 dsh plugin --profile <你的 profile> add dsh-auto-approval-frame。细节见下一节。建议同时装 dsh-gitbash-shell——本插件的解析和测试是按 Git Bash 打磨的,原生命令这条路验证得少,见「安装」。
二、开。 设置 → 插件 → 找到 dsh-auto-approval-frame,确认开关是开的。再回到会话,打开权限菜单选「自动审批」这一档——没选这一档,插件不接管你的任何会话,既不拦也不记。
三、写第一条规则。 设置页的命令表加一行:git status,档位选「允许」。之后在会话里让它看 git 状态,这次调用既不弹窗也不花模型费,台账里写得出是这一行放的。想管住一族命令就写 git;写得更具体的那行优先,git push 压 git。
四、看。 每次拦截、每次人工答复都在设置页的「审计中心」:时间、动作、落在哪一层(规则/快判/深评/人工)、你当时答了什么。想先问「这条命令会被怎么判」,用设置页的「模拟审批」贴进去,它走的是和真判定同一条链,不会对同一件事说两套话。
先说一个坑:模型档位里的 off: null。 如果 llm-pi-ai 的模型条目把 off 写成 null 或干脆不写,宿主发请求时会省略 reasoning_effort;有些服务端把「省略」当成「用我的默认档」,默认档若是最高思考,本来标着「关闭思考」的调用反而会先跑完一大段思维链,审批卡上「AI 解读」从不到一秒变成好几秒,额度小的时候甚至只剩「模型没有返回解读」。本插件默认会替你把这一格补成 none(设置页「AI 审查 → AI 解读跳过思考」),但补的是当前 profile 的用户层配置、而且是模型级的:凡是选「关闭思考」档的调用都会跟着变,你会话选的高思考档不受影响。卸载插件不会把它还原成 null,也不会删除你自己改过的值;想恢复就手工改回,并先把那个开关关掉,否则下次启动又会被补上。
安装
本插件 dsh-auto-approval-frame
插件市场搜包名 dsh-auto-approval-frame,或:
dsh plugin --profile <你的 profile> add dsh-auto-approval-frame
装完重启 DSH。另外还要做两件事——确认插件是启用状态、在会话权限菜单里选「自动审批」档,见上一节第二步。
插件自带命令解析运行库与 bash/PowerShell 文法,不需要本机安装编译器,也不会调用外部解析命令或联网下载组件。
建议同时装 dsh-gitbash-shell
本插件对命令、文件和路径的解析,以及测试,都以 Git Bash(POSIX)语法为主路径。dsh-gitbash-shell 让 agent 在 Windows 上直接用 Git Bash 执行命令,是与本插件配合最完整的组合。插件市场搜 dsh-gitbash-shell,或:
dsh plugin --profile <你的 profile> add dsh-gitbash-shell
装完重启 DSH。
说直白一点:原生 PowerShell/cmd 也在支持范围内(命令表里有专门的原生命令行),但那条路的真实样本少得多,很多写法还没在真机会话里跑过。没暴露不等于没问题——用原生命令时,遇到解析没覆盖的写法、判定偏离预期的概率会更高。两套都能用,但 Git Bash 这条路的修复密度、回归覆盖和稳定性都更好;能装就装。
权限档
插件只接管你在 gatedPresets 里勾选的权限档,默认是「自动审批」(auto-approval)。这个档由插件自带,装完就能在权限菜单选到。
- 如果你的 profile 自己定义了
permission表,它会整行覆盖插件自带的表。这时保留你已有的档,把auto-approval加进去;或者直接在设置页的「受管权限档」里勾选你已有的档。 - 受管档不存在时,设置页会红字报出名字,并给出可复制的 profile 片段;在它修好之前,插件不接管任何会话。
- 前提:宿主审批策略必须是
ask。如果设了DSH_PERMISSION_MODE=danger-full-access,宿主会被钉成never,没有审批请求,插件也不接管。
拦截梯
一次工具调用从上往下走。会给出结论的只有中间四格 L1 规则 → L2 快判 → L3 深评 → L4 人工:谁先给出结论,就按它办——放行就执行这次调用;拒绝就只拦这一次、不执行,会话照常;说不准就交给下一格。四格都没说住,才落到人工。

L0 和 L5 不是关卡。 L0(备料)只把命令拆开、看清要动哪些文件和网络,自己不下结论。L5 只做一件事:跑完命令,命令正常输出的敏感信息换成占位符后,再进入上下文。图上这两格的边框是虚的,就是提醒你它们不参与判定。
梯子外面还有两件事。 一是读别的项目根(同层或向上有 AGENTS.md/.git)而你的话里没点名它——直接问人,模型判放行也不放;你提到的名字能唯一解析到某个目录时,它就把那个目录记成这次会话的根,根内的读直接放行。二是日志审计:它不在梯子上,而是包住整条梯——每一格、每次人工答复、每次配置变更,都写进同一本可校验的库。
每格判什么、能给出哪几种结论,见设置页「模拟审批」的逐步显示;它和真判定走同一条链,不会对同一件事说两套话。
配置
一般不用动,默认值就能用。要改,优先在设置页改;下表是全部键,直接改 profile 时用。
| 键 | 默认 | 含义 |
|---|---|---|
enabled |
true | 总开关。关掉只停判定(拦/问/放),结果脱敏和台账照常。 |
mode |
auto |
off 全交宿主面板/advise AI 只给建议/auto AI 可放行。 |
gatedPresets |
['auto-approval'] |
接管的权限档,默认「自动审批」;设置页只列它和你的自定义档。 |
reviewerSource |
session |
session 跟随当前会话的模型/preset 指定模型。 |
reviewerProvider reviewerModel |
'' |
preset 时填。 |
reviewerMaxTokens |
2048 | 深评输出上限;别低于 512,容易截断。 |
reviewerTimeoutMs |
20000 | 深评超时,默认 20 秒,不重试。 |
evidenceBudget |
16000 | 给模型看的证据总字数上限;超了从低优先级裁。 |
reviewRules |
[] |
你附加给模型的审查规则,只能收窄,不能放宽。 |
commandRules networkRules toolRules |
[] |
每行 {action, match};动作四档 release/ai/ask/deny,从上往下第一条命中即停。 |
fileRules |
[] |
每行 {match, read, write, delete};读/写/删三列各选四档或留空(留空=这行不管这个操作)。 |
sensitiveRules |
[] |
敏感信息检测规则;预设 10 行,可改正则/档位/停用;命中取最严,不看顺序。 |
sensitiveDetect |
true | 检测与脱敏总开关;关掉=工具结果原样进模型。 |
autoRestoreSecrets |
true | 写文件后把占位符换回原值;默认开,但要有回写证。 |
fastJudge |
true | 单段写/删先让快判模型判一句。 |
fastJudgeMaxTokens |
256 | 快判输出上限。 |
fastJudgeTimeoutMs |
3000 | 快判超时,默认 3 秒;超时转深评。 |
retentionDays |
90 | 台账保留天数。 |
maxTotalBytes |
512MB | 台账总大小上限。 |
maxSegmentBytes |
50MB | 单个台账文件上限。 |
debug ⓟ |
false | 补丁专属,设置页只读。 |
ⓟ = 补丁专属。
规则怎么写
- 命令:写「程序名 + 参数前缀」。
git push管住git push origin main,不管git pull;*匹配一个参数;整行只写*就是所有命令;re:开头按正则。 - 文件:写路径。
~是家目录;*只匹配当前一层,**匹配任意层;路径以/结尾表示这个目录及其内容。读/写/删三列分别表态,留空=这行不管这个操作,后面的行仍可管。 - 网络:写主机名或 IP,如
api.example.com、*.example.com、*;不写端口和路径。 - 工具:写工具名通配,如
mcp:*。 - 旧的三键
trustedDirs/dshOpen/neverAutoExtra会在启动时自动迁成文件行,之后不再是配置键。
规则谁先说话
- 所有行——预设行(插件自带)和你的行——在同一张表里,从上往下,第一条给出结论的行算数。档位就是允许/AI/人工/拒绝四档。
- 预设行不再永远压你一头:把你的允许行放在它上面,就能放开它;拖回去就恢复保守。
- 「拒绝」行只要命中就拒,跟先后无关。
- 「AI」行命中就交模型判一次,后面的行不再看。
- 想改某条预设:能直接改那条行;也可以在你的表里用更具体的行盖住它。
升级提示: 旧版的 AI 行是「本层不拦、继续往下走」,现在改成「交模型判一次,后面的行不再看」。插件不会自动改你的行,只在设置页提示一次;有 AI 行的配置请自己确认一遍。
允许行意味着什么
- 允许是真放行:不调模型、不弹面板,但会写一条台账;写不进去就转人工。
- 允许行也是信任声明:命中的路径不再算「工作区外」,删除也不再按「内容不可恢复」处理;
~/.dsh的子树也可以被开口。整棵~/.dsh和含凭据的子树仍会被拒绝开口。 - 敏感面的允许只放这一次调用,结果该遮还是遮。
- 盘根和家目录没有特殊开关:它们就是普通目录。
rm -rf C:\、rm -rf ~都会落「删除·工作区外,问你」;要更严就写一条普通路径行,盘根写成C:\。 - 预设表具体有多少行,以设置页显示为准。
默认档会把什么交给模型
- 改工作区根、改到信任区外、改符号链接目标:先交 AI 判。
- 改
~/.dsh的档位配置、台账、审批记录:仍然问你。 - 客户端产物目录的读:不再无条件放行。
配置写坏了会怎样
- 保存时只要有一行档位非法、正则编译不过,或行里缺
match/preset,整次保存被拒,盘上配置原样不动。 - 读进来时如果有坏行,整份配置按「损坏」处理:继续用你上一份合法配置,不退回默认档。
- 设置页会明确提示「当前生效的是上一份合法配置」,并指出坏在哪几行;坏行不会被悄悄丢掉。
- 留空是合法的:留空=这行不管这个操作,不会被默认值填回。
审计库
台账在 ~/.dsh/auto-approval-frame/audit/,按天分文件,一行一条 JSON,带哈希链,能校验有没有被改过。
state.json:当前写到哪个文件、链尾在哪。manifest.jsonl:每次分段关闭、裁剪、清空都记一笔。2026-10-27.jsonl:当天的记录;同一天写满一个段,就接.1.jsonl。legacy/:旧版单文件台账,升级时原样移进来。
每条记录有时间、类别、事件、结论、落在哪一层,加上这次事件的字段(命令预览、命中的规则、原因、耗时等)。设置页的「审计中心」读的是同一份库,不另存副本;可以查询、导出、清空。默认隐藏启动和配置变更两类簿记,人工答复默认显示;隐藏只影响列表和导出,记录照常入账。
台账默认留 90 天、最多 512MB;到点整段裁掉,裁剪记录留在 manifest.jsonl,链仍能校验。旧版的 decisions.jsonl 会在启动时自动迁移,原文件移到 legacy/。
它到底看什么(隐私边界)
先说要紧的: 开启审查后,这次调用的命令、目标路径、元数据和最近的用户意图,会走宿主的 LLM 通道发给你配置的模型服务。不能让这些内容出机器,就把 mode 设成 off,或不要把 auto-approval 档接给远端模型。
判定时,插件自己不打开文件读内容,也不会主动去找密码;只有密码出现在这次调用的命令或输出里时,才会在台账预览或结果侧被遮住。它看的是这次调用本身:
- 命令和入参:要动哪些文件、哪些网络、用什么程序。台账里的命令预览会先遮敏感信息、再截到 400 字;本机绝对路径会原样记下来,台账按敏感件对待。
- 目标的元数据:存在不存在、是文件还是目录、多大。只查这些,不读正文。
- 工作区里的三条只读 git 信息:仓库根、跟踪文件、工作区状态。
- 工具执行后的结果文本:要扫一遍敏感信息、遮完再交给模型;扫描本身会读这段文本。
它存什么:
- 台账:每次判定一行,含命令预览、路径、结论、时间。默认留 90 天、最多 512MB,到点整段裁掉。
- 敏感值映射表:结果里被遮掉的值,只在本机
~/.dsh/auto-approval-frame/secrets.json,最多 200 条,用来把占位符换回去;不发给模型,也不随台账导出。 - 不存:文件内容、工具结果全文。
它不联网:除了上面那次宿主 LLM 调用,插件本身不向外部发请求;设置页的诊断接口只回环。
边界:这个插件保证什么、不保证什么
这一节是装机前必读。把「它不拦」读成「系统安全」会出事。
- 「允许」只是本插件不拦,不等于宿主一定执行。 本插件放行后,宿主自己的策略和 guard 还可能再问一次或直接拒;台账里两格结论不一致是正常的,记的就是谁说了什么。
- 它在应用层,不是 OS 隔离。
auto-approval这个档本身就是danger-full-access+approval: ask,在它下面命令照样能读写你整个用户目录。插件只能「在它发生之前问你」;宿主把档钉成never的会话它不接管,也就拦不住。要真隔离请用沙箱/容器/只读挂载。 - 遮掉的明文靠占位符,已经出去的值收不回来。 命令进台账前遮的是副本,工具结果回给模型前换成
[[SEC-…]];已经到模型手里的原文、命令参数里的 token(宿主不允许插件改参数)都换不回来。值一旦以明文进了一条命令,就按已经泄露处理。 - 二进制和超长内容不扫,台账会如实写「未扫」。 太大的块不进入检测面,
unscanned计数会记下来。「链能校验」不等于「内容都看过」。 - 敏感判据靠文件名和形状,改名就绕得过。 把
id_rsa改名成key.txt,名字那一档就不再命中。它接得住常见泄露形态,不是内容分类器。 - 你自己加的允许行会盖住预设行。 表和顺序都是给你改的;把允许行拖到「凭据目录」那一行上面,那条下界对这台机器就失效。这是设计,也是你要自己承担的部分。
- 宿主不告诉插件「哪些文件真的写成了」。 自动回写只能按插件自己的写目标推断,再读回文件核对版本;后台命令成功、脚本最后一行成功,都不等于每个中间目标写成。要更硬的保证,需要宿主在执行侧报告写入集合。
- 总开关只关判定,不关遮罩。
enabled: false或mode: off停掉的是「拦/问/放」;结果脱敏和台账照常跑。要「结果原样进模型」,得卸载插件,不是关开关。 - 「用户批准」只认你自己打的字。 工具问答回传的
answers是结果,不是批准(远端工具可以伪造);宿主的「计划批准」目前也不算数。插件没有能力验证「这是不是一次真正的用户决定」,所以只认用户原话。 - 自动回写只能绑到「哪一次调用」,绑不到「执行器确认哪些目标真的落地」。 和第 7 条是同一个缺口:插件能核对「这次调用确实写了这个文件」靠读回文件版本,但宿主不报告实际写入集合,所以不假装补上了。
- 插件遮的是副本,宿主最终组包仍是原件。 结果脱敏改的是交给宿主的那份结果副本;宿主最后组装时还有自己的环节可以往结果里加内容,那部分插件看不见、拦不住,只能在结果侧发现凭据形状时记一笔。要封死最后一道口,需要宿主在执行器层提供最终投影点。
秘密回填:占位符怎么换回原值
结果里的敏感值被遮成 [[SEC-…]] 后,模型写文件时写进去的就是这个占位符。
写之前问你一次
- 文件还没落盘、这次要写进去的内容已经在手里的时候,插件发现里面有本表铸出的占位符,就问一句「要写入『文件名』的内容里有几枚密码占位符,还原吗」,两个选项:还原成真值、保持占位符。
- 选还原:插件当场为「这一次调用+这个文件」签一张证,写入照常执行,落盘那一刻把占位符换成原值。会话、目的路径、期限都由插件自己填,你不用输入任何东西。
- 选保持,或者没人应答:文件里就是占位符,跟以前一样。
- 只处理文件层;命令参数里的占位符换不了(不允许插件改参数)。
edit的old_string比的是磁盘明文,带占位符会 not found,改被遮的行用按行/模式脚本。 - 关掉设置页的「写之前问要不要还原占位符」,就完全不问、也不回填。
凭什么说安全
- 有证才换:证绑的是「会话+这次调用+这个文件+用途」,四样对上才换,对不上就不换,文件保留占位符,台账记一条「跳过回写」。
- 一张证只用一次:两个实例同时抢同一张证,只有一个能换成。
- 删了就是删了:删除和清空会留一条删除标记,旧实例不会把删掉的明文再带回来。
- 换的时候要么整个换成功、要么不动;跳过凭据树、系统树、符号链接、超过 4MB 和读不出的文件。
你会看到什么
- 审计中心会记「写前签了 N 张回填授权」「已回写」或「跳过回写」。
它不保证什么
- 只按「插件认为这次调用写了这个文件」回写;宿主不告诉插件执行器实际写了哪些文件,所以后台命令、脚本中间目标有没有落地,插件只能按写目标和读回版本推断(见边界第 7 条)。
AI 审查怎么判
放行不等于「模型没看出风险」。模型要放行,得逐项回答三个问题:
- 这次动作有必要吗(necessary)
- 用户的话覆盖到这次动作了吗(authorized)
- 保障够吗(safeguarded)
三问里任何一问答「否」,就算模型写了 allow 也不成立,直接改成拒绝,理由分别记成「没必要」「授权没覆盖」「保障不够」。
几条配套规则:
- 证据不完整就不自动放行。 证据被裁过(不是完整证据),即使模型说 allow,也转人工,台账记下丢了哪一段。
- 没给的部分是未知,不是无害。 被裁的正文会附「原文多长、留了多少、丢了多长、首尾各一段」;模型判断依赖没看到的部分时,必须说「判不了」,不许猜。
- 区外不再自动等于未授权。 目标在工作区或已允许目录之外,只说明「范围要算」,不等于「用户没授权」;模型按证据里的原话、目标资源、排除项与撤销记录算覆盖,引文必须逐字出现在证据里。
- 授权读数不再替用户概括。 「目的」「允许的附属操作」两格已撤(A18):它们由词表从原话里取词(「全加上」抽不出写、句尾「你看」抽出读),概括只往窄里错,模型又会把它当成授权边界,于是明确授权被判成未授权。防编授权靠引文核验,不靠程序先概括一遍。
- 一次判定优先。 证据不完整不跑快判;快判放行就结束,只付一次;快判不过才升级深评。
旧模型没给三问的答案时,插件不会额外拒,台账照记;要收紧成「缺三问也不算批准」,需要新增一档配置,本版没做。
明确不做
不做自研倒计时(人工档直接用宿主面板)、斜杠命令、学习/熔断、快判的专门分类器、产物溯源。每次需要判断的调用都会付一次快判或深评——这是为了只保留一套判定权。
宿主不允许插件改写工具参数,所以:对工具输出只做拒绝/允许两问,脱敏只在结果侧发生;模型把 token 写进文件后的修正只在文件层做(可关,默认开但要有证),命令参数里的 token 换不了。
English
An interception and audit frame for DeepSeek Harness. It manages one permission tier (auto-approval by default): every tool call walks preset protections, the ordered rule table, a read-only fact gate, AI fast/deep review, human approval, and result redaction. Every event is written to a hash-chained, day-segmented audit store.
Quick start
Four steps from install to a working frame; everything after this is reference.
- Install. Plugin market →
dsh-auto-approval-frame, then restart DSH; ordsh plugin --profile <profile> add dsh-auto-approval-frame. Also installdsh-gitbash-shell: the parsers and regression suite are built around the Git Bash path. Native PowerShell/cmd is supported, but it has seen far less real-world coverage, so undiscovered edge cases are more likely there. - Enable. Settings → Plugins →
dsh-auto-approval-frame, confirm it is on; then open a session's permission menu and choose Auto approval. Without that tier the frame manages nothing — it neither blocks nor records. - First rule. On the settings page's command table add a row
git statuswith tier allow. It then runs with no prompt and no model call, and the audit line names that row. A row forgitcovers the family; the more specific row wins. - Audit. Every block and every human answer is in the audit centre — time, action, layer (rule / fast review / deep review / human), and what you answered. Simulate runs the same chain as a real decision.
Managed permission tiers
The bundle patch ships auto-approval (danger-full-access sandbox + approval: ask). A profile-level permission row overrides the whole table; if you own one, add auto-approval there or select an existing custom tier in the page.
The page lists only auto-approval plus custom tiers. Read-only / workspace-write / danger-full-access are not listed: read-only has nothing to release, workspace-write's grant is confirm (AI cannot release), and danger-full-access pins approval to never (no prompt). A saved legacy choice stays visible so you can switch it off. When a managed tier is missing, the page reports it with a copyable profile snippet.
What it writes
~/.dsh/auto-approval-frame/audit/: hash-chained day segments, manifest, state, legacy imports.~/.dsh/auto-approval-frame/: the local reversible token vault (secrets are masked before they reach the model).
Language
The settings page follows the host locale (<html lang>): Chinese clients get Chinese, English clients get English. README Chinese is the full reference; this section is the English summary. Audit messages are stored as generated.
Tests
node test/frame.test.mjs && node test/effects.test.mjs && node test/wiring.test.mjs && node test/client.test.mjs
Not affiliated with DeepSeek.
开发说明
这一节是实现契约与回归锁;只使用插件可以跳过。
展开:配置版本语义 · 实现备注 · AI 审查实现 · 效果分类 · 设置页细节 · 24 条不变量配置的版本语义
configVersion带语义纪元前缀2:<12hex>;旧语义下发出的批准不会命中新语义。- 同一份配置在纪元变化前后拿到不同的版本串;读到比自己更新的纪元时,保留上一份合法配置、页面只读提示,不清空你的规则。
- 同一次判定里,
frame接口、审计判定行、证据块的configVersion是同一个串(回归锁test/config-evidence.test.mjs)。 - 预设表行数:文件 63、命令 318、工具 3、检测 10;快照锁
test/_preset-snapshot.mjs。 - 配置损坏:保存被拒记
configDamage.reason = 'save-rejected';读入坏配置(rules-invalid/unparsable)时继续用上一份合法配置;configDamage非空就是页面上的「当前生效的是上一份合法配置」。
实现备注
- 判定链只有一份实现:
lib/frame/ladder.js的walkLadder()。真判定与设置页「模拟审批」都调它,步骤文案由ladderSteps()/ladderVerdict()从同一次 walk 派生;test/wiring.test.mjs有差分锁,逐条比对模拟落点与台账第一条定局行。 - 层名两份表:
audit.js的LAYER_TEXT(node 侧)和client.js的LAYER_LABEL(webview 侧)必须逐字一致,test/client.test.mjs按字面比对。 - 人工答复的观测点:读
session/event的approval/asked→approval/decided,不读approval/request——后者是宿主跨进程转发的 waterfall,宿主自己的应答器先终结,插件监听排在后面进不去。每次调用按callId+actionHash存一条 pending,这一侧只读它,不重新推导(lib/frame/pending.js)。 - 审计校验:
verify()校验 manifest 的 sha256/条数/链尾、逐行 hash、prevHash、sequence 连续性;分段被保留策略裁掉后,靠墓碑继续校验。旧decisions.jsonl在启动时迁移,并记lifecycle.migrate与旧文件 sha256。
AI 审查实现
- 三问在
lib/frame/verdict.js的REVIEW_QUESTIONS;parseVerdict()把任一假改成 block;mayRelease()在mode=auto ∧ grant.state=auto ∧ reviewed/allow之后再判一次。 - 证据裁剪由
lib/frame/evidence.js的projectEvidence()记{field, kept, lost};completeness只有full/partial/too-large;lib/index.js的evidenceComplete只认full。 - 读数在
test/evidence.test.mjs;把clip()的记账关掉,或把三问退回只认no_material_risk,对应用例转红。
效果分类:一条命令被拆成哪些角色
事实层(lib/frame/parse.js/facts.js)把一次调用拆成作用项,每项带一个角色;档位不在这里定,
这里只说「实际会发生什么」。角色一共十种,台账与证据块都按它显示:
| 角色 | 含义 | 例子 |
|---|---|---|
executable |
被执行的那个程序本体(带路径时才记) | /usr/bin/python3 -V |
script-read |
解释器读进来执行的脚本(读,不是写目标) | python -S sync_docs.py --help |
read-source |
被读的文件 | cat a.md、cp a.md x 的源 |
stdin |
输入重定向/here-string 的数据源 | cat < in.txt |
read-manifest |
一张「文件清单」本身 | wc --files0-from=list、tar -T list、xargs -a list |
write-destination |
真正的写目的 | > out、sort -o out、cp a.md dir/ 的 dir/a.md |
directory-container |
当作容器读的目录 | grep -r x .、ls dir |
delete |
删除目标 | rm -f a.md |
network-source / network-destination |
网络面的「取回」/「送出·远端执行」 | curl URL/scp a.txt host:/ |
四条容易算错的形状,现在按真实效果记:
- 解释器 + 脚本:
python -S …/sync_docs.py --help的脚本是script-read。旧形按「像路径」把它当写目标, 于是整条只读命令落进写档、理由还指向~/.dsh。 - 输入重定向:
cat < in.txt的<是stdin(读),不把整段变成写;>/>>/2>仍是写目的。 - cp/mv 到已存在的目录:真正被写的是「目录 + 源文件名」(
cp a.md .⇒<ws>/a.md), 容器目录单列在container,不会再让.本身命中「工作区根本体」。反向:rm -rf <ws>仍是根级下界。 - 清单旗标:
--files0-from/-T/-a的取值按read-manifest读,列出的名字展开成读面;xargs -a list rm的内层是删除程序时,展开项仍按delete记、整段仍走删除档。
读的举证要看得见全貌(completeness)。递归读(grep -r、rg、find)会把读面枚举进目标集,上限
深度 4/访问 800/条数 20;任一触顶记 truncated,读目录或 lstat 失败记 error,两者都不许走
「已证明只读」那行。没有列出任何目标的调用同样不换免检;扫描报错不当「没发现」。
宿主原生读工具(read/grep/glob/ls)省略 path 时,读面是宿主默认 cwd,会把这一格展开成
真实作用项,不再出现「作用项空、照样放行」。sed -n 1p file 这类纯打印也举得出只读证(脚本只认
行号+p/d/n,且不带 -i/-e/-f);换成 python -c open() 则仍交模型——差在事实层,
不在路由层。
方言按归一 basename 分档(Get-ChildItem/findstr.exe 与 ls/grep 同一套判据),
单引号里的 *、;、$ 是字面量,不当通配/分段/展开(未加引号的照旧算);双引号里的 $ 仍会展开,整词环境变量按判定这一刻的取值算死(见不变量 16)。
proven-read 还要过任务资源范围:本项目与已装技能的说明读直放;别的项目的业务/架构/
面板/配置档案即使只读也交模型判必要性(人写的允许行仍可显式开口)。
设置页细节
这一节写给要改页面的人;只用默认设置可以跳过。页面在 lib/client.js,顶部是一条判定链(0 是否接管 → 1 安全规则(预设行+你的有序表;命令面一条命令一行+末行兜底)→ 2 快判 → 3 深评 → 4 人工 → 5 结果脱敏,
标出哪几步可配、哪几步固定,点一格跳到对应卡片),卡片顺序就是执行顺序:接管与模式(开关/模式/受管权限档)、
安全规则(两级页面:默认四行摘要——命令安全/文件安全/网络安全/工具规则,各带一句说明与条数;点一行进明细,‹ 返回 回列表。
明细页自上而下=表头(图标+标题+条数)→ 一条工具行(条件输入框/新规则默认动作/+ 添加)→ 一条说明 → 一张表(用户行与预设行同一个列表,行首「用户/预设」片区分,都可改、都能调序)、
AI 审查(模型/快判/自定义审查规则,超时与预算收进「高级」)、敏感保护(一张卡两行并列:检测规则 走同样两级——摘要行→明细表;敏感信息 记着遮出来的占位符,可看明文/开关回写/清空;总闸 启用检测与脱敏 画在卡头右侧,
不占一行——启用本插件 同形制)、
审计中心(保留策略在「高级」,加查询/导出/清空)。一张卡只有一个容器(.dsa-card),卡内一律 .dsa-item 一种行(字段行/摘要行/规则行/审计条目同形制)。分层靠三档边框+有色行底:卡框 border-l3 → 行框 l2 → 控件 l1,行底用自定一档 --dsa-row(浅色=黑 2.2%/深色=白 4.5%,按宿主主题选择器 body[data-ds-dark-theme] 各给一档,只比卡底深一点点)——宿主的 bg-layer-1/2/3 在浅色主题下全是 #fff,靠它拉层级无效,而 bg-module-platform 又太深;也不许用投影,分层要有梯度而不是靠影子糊。规则行分两层:上层是编号|来源片(用户/预设)|主语与条件|行操作(拖动把手/启停/删除),下层是读/写/删的动作片(片内=操作图标+操作名+彩色圆点+档位)——动作片靠右、略小,五张表同一把尺。行首编号只是序号,敏感面命中取最严、不看先后;每行都有启用开关;删除按钮对用户行直接删、对预设行写 removed 标记(不显示、不再拉高)。文件/网络/工具面的预设行条件列是说明文字(命中由事实层给,改了字也改不了命中范围);命令面的条件列就是命令名(一条命令一行,算法算出的结果只许把档位加严),末行「其他命令」固定末位、没有删除入口与拖动把手;敏感面的条件列才是可改的正则;文件面的预设行与用户行同形制:读/写/删三颗档位片各自表态(只放行 ~/.ssh 的读、~/.aws 仍必人工,就是这个粒度才做得到);路径说明可换行显示全。动作片统一带一枚动作色点(行内与工具条「新规则默认」同一套片子,四档只靠彩点区分:拒绝红/人工黄/AI 靛蓝/允许绿(片子白底,不再整片染色);不带操作名的单动作片统一最小宽度,AI 不会比「人工」窄一截),不嵌原生 <select>(系统弹出层比片子窄一圈、字也更小,点开就风格断档),也不是摊在行里的一排按钮:点片子弹出一个贴在片子正下方的浮层菜单(宽度不小于片子、l3 边框+实底、无投影),选项行 30px 高、按动作着色、当前项打勾,点外面或选中即收起,一屏只开一个。文件行的读/写/删三颗片在第二行右对齐;拖动把手排序,拖到预设行之间时会整表物化一次以持久化顺序。保存/撤销:干净态只是页尾一行,有待保存改动才升格成粘底悬浮条(.dsa-footer.is-dirty),按钮靠右=说明在左 → 撤销 → 保存最右。
样式集中在 settingsCss() 注入的一张 <style>:内联 style 给不了
:hover 与 disabled 两态,而这套页面全靠这两态才看得清;颜色全部走宿主设计令牌,深浅色自动跟随。宿主本来会给没注册自定义页的插件自动生成一张表单(autoGenerate:
"Generate a page if no custom page is registered for this instance",见 @deepseek-ai/dsh-api-remotes
客户端),那张表直接用 schema 键名当标签 ⇒ 没有客户端半时看到的就是英文键名页。注册自定义页后由本文件接管。
写入只经 form.mutate([{op:'set',path:[key],value}]),且只能点名 volatile 字段(=EDITABLE_KEYS);
debug 在卡片上只读,改动走 profile 补丁;gatedPresets 在「接管与模式」卡按宿主 live 权限目录勾选。预设保护与生效的信任/开口/被拒行由
GET /api/auto-approval-frame/frame 从宿主读回,页面不自己复算。
规则行写成一句话:当 命令 匹配 [git push --force] → [拒绝];文件面在同一句里嵌 读/写/删 三颗片(留空=不表态,后面那行仍可管)。
读序=语法序,不需要读者去对列,条件还能就地改。页面顶部的模拟审批(原名「试算」→「模拟执行」→「模拟审批」,名字都由用户定;按钮「模拟一下」,输入框 460px 一行高;GET /api/auto-approval-frame/explain,同源只回环)把一条命令喂给判定链,
逐步回落在哪一关、为什么,决定步之后一律标「不再走」;它不调模型、不执行任何东西,答的是「为什么会这样」而不是「AI 会不会同意」。
会话侧只做两件事:预设文案与 aria 按界面语言改写、给本档补图标(在本机跑通的判据: 图标只加在宿主确实给其他档位画图标的面——权限菜单行与输入框触发器;弹层行与设置选择器只改文案; 纯文本列表不塞孤立图标;原文留档以便语言切换还原;每轮清孤儿图标)。
不劫持宿主点击。 上一版在这里加了「启用前风险确认」:捕获阶段 preventDefault 拦下对「自动审批」行的 点击,事后再对该节点补发一次 click —— 但宿主菜单一关,节点已脱离文档,补发的点击走不到宿主挂在容器上的 委托监听,结果是确认了却切不过去。插件不该假借宿主的交互通道,故这道门整段移除; 高风险档的把关回到预设下界与判定链本身。回归里锁了一条硬判据:点击「自动审批」必须直达宿主。
不变量(回归锁死)
24 条。每条第一行是判据,下面几行是判据怎么落到代码里。任一条被打破,对应的回归用例会红。
表判人工的路径,模型不会被叫;秘密出门的兜底在敏感面。
凭据树、系统关键树、
.git内、DSH_HOME 状态树(未被允许行开口)出厂档都是人工; 要交模型判,自己把那一格改成 AI(改成 AI 后模型真裁决,不再被宽行事后否决)。 工作区根本体(删/搬/销毁这个目录本身)由单条事实闸直接问人;git add ./eslint .这种以.为操作数的写/执行不在此列。 路径行只决定这一格是问人还是交模型;读回来的内容与要写出去的内容, 统一由结果脱敏与已知明文检测兜底(与档位无关)。一张表内按行序先命中即停,表间取最严。
deny作为首条命中时拒(deny无特权,不越过前面已经说话的行);ask问你;ai交模型判一次、后面的行不再表态;release真放行——不调模型、不弹面板。不同作用项/不同表之间才取最严(拒绝 > 人工 > 交 AI > 允许)。
release放行前要留下可追责的账,写不下就不放行(见第 7 条)。ALLOW 只在
grant=auto × mode=auto时有释放力。表把这一档判给 AI 时
grant=auto,模型说 allow 就放;表判人工/拒绝时模型不会被叫。 旧confirm(只给建议)档不再出现在评审路径上——同一路径的其它命中行不会事后改判。allow与reasonCode=no_material_risk必须同时成立。两者矛盾时,一律改写成 block。
快判给出的 block 只表示「升级到深评」,不是终局拒绝。
评审举不出结论时,落点分两档。
四种成因都算
unable:超时、坏 JSON、路由不可用、证据过大。证据过大(
context_too_large)一律回落人工——模型根本没看全,不许静默放行。纯读可以放行:面集合全是读、目标集已确定、没有机械下界理由,走「unable 兜底」,台账记
layer=deep与unableTier,指得出是哪一次评审举不出结论。其余情况(含任何副作用面)仍回落人工。
你答复之后那一侧,不再调用 L0/L1。
判定从
session/event只按approval/asked的 callId 取 pending。pending 按
actionHash绑定,动作漂了不继承判决。账记不下来,就不放行。
这条约束覆盖四个放行出口:表里的「允许」行、只读与内部工具举证行、快判、深评。
本插件放行的每一笔都要有账可查;写不进审计库,降级为人工。
纯读能不能免审,由命令面的一行拍板,不靠证据行。
proven-read已经不是表行:证据仍由事实层举(台账里读得出),但「这条调用能不能免审」只认命令清单里的行。出厂命令面的读族行就是「允许」,所以纯读直接放行、不调模型(行数与范围见第 24 条)。
要收紧,就停用或改档这些行。
命令表一行都没接住的段,仍落末行「其他命令」交 AI 判。
只编辑会话的内部工具(
compress/decompress/acp_status/acp_cache/todo_write)走「会话内部工具」那一行。语气不构成授权证据。
情绪化的句子降级进「非授权背景块」:内容照录,授权标签摘除。
结果侧命中就遮,与档位无关。
任何一档(含允许)先换成确定性 token
[[SEC-<12 hex>]]。档位只决定遮完之后怎么办:问你、放行,还是整段不给模型。
明文只落本机
secrets.json(200 条/30 天),列表默认不出明文;查看明文要单独取一次,并入账。人工答复与工具结果都入账。
approval.answer记宿主那四个结局:已批准(allowed-once)、已拒绝(rejected)、没答(cancelled)、没有应答通道(unavailable),并带上它批的那条动作。tool.result记成功或失败,附结果首段。所以一条 ask 不会在列表里留下没有结局的悬尾。
自动回写只发生在文件层,且只换本表铸出的 token。
写完读回、原子重写;跳过凭据树、系统树、符号链接、超 4 MB 与读不出的文件。
超限、非 UTF-8、读不出内容这三类属「载体装不下占位符」,跳过不记账——否则一次
cp一个编译产物,台账里就留一行像告警的记录。凭据树、符号链接、无授权、目标被改,照旧各记一条。
命令参数里的 token 换不了,宿主契约禁止改写参数。
本机以外的目标,不当作本机路径。
远端主机单独记一栏,也喂给网络面——写一行
deny prod.example就拦得住scp out.txt user@prod.example:/tmp。外传一族各占一行:
git push/curl -T/aws s3 cp/docker push/npm publish/gh release create。脚本正文也算外传面:内联
-c、heredoc、脚本文件里同时出现发送动作和目的主机,记成exfil:script-upload;只连不发的(测延迟、TLS 握手)不算。网络表末尾固定一行「其他外传主机」,只管上行方向,默认人工,可改档、可停用,删不掉也挪不走。
这些事实一律把 grant 压到
confirm:模型看不见机器外面发生了什么,无权单独放行,只能问人。目标提取只认真实操作数——判据是「这条命令要动的东西」,不是「命令行里出现了什么字」。
词首
#之后是注释。<</<<-/<<<的标记与 heredoc 正文整体消失:正文是数据,不是命令。不像路径的词不折成路径:
--line 10、--label 决定·面板、纯符号。以文件为操作对象的程序除外:
rm -f junk里没有点也真是文件,这格一丢,删除下界就没了。反面例子:正文词
token曾被折成…\面板.md\token、命中凭据名,把一条往受管文档追加表格行的命令降成「模型无权放行」。事实层被截断单独成行(
facts-truncated,默认ai=送去判),不再冒充「一次动得太多」。默认档不靠顺序,靠作用面。
预设行的文件面带
read/write/delete三列默认值,''表示对该操作不表态,不再一律回落成人工。「读类不再逐次问人」是在表上解的,
walkLadder的步骤次序一步没动——把只读门前移,会让cat ~/.ssh/id_ed25519静默放行(实测三条反例)。客户端状态族(
~/.dsh・~/.workbuddy・~/.qoder・~/.copilot)的读豁免只有两条行、四道闸口:目录属于该族、不是profiles/bin、整条路径没有任何凭据形状的段、不是目录本体下直接摆的文件。skills/docs子树的读默认放行,其余子树的读默认交模型判。~/.gnupg/skills这类「凭据目录里恰好叫 skills」不放——按目录名放行就是把自己骗过去。.workbuddy/.qoder/.copilot三行按家目录直接子段匹配(homeOnly):客户端还会在每个会话工作区注入一份同名目录,那不是~/.workbuddy,不该拿它当理由文案。这三行的读默认交模型判,写与删仍人工。
~/.dsh(profiles是宿主配置)与~/.ssh的读,不随这次降档。只读证里「哪个词是路径」,按位判。
位只在
positions.js一张表里定义:124 只读族程序 × 八位(read/write/exec/take/key/pattern/nopath/nav),每行带evidence。facts.js只查表,段上落一列wordRoles(flag/path/nopath)。write位的取值只进写面,不再记一份读。exec位的后随程序按同一张表再查,内层是读族则本段仍算读;{}/\;是占位符,不是路径。表外程序不产读证,目标面保持保守:表外程序与表外旗标的词按
path。未引用的通配在当前目录有界展开(≤20 个)后名字进目标;判读与程序名无关——展开成功(含 0 命中)就是目标集确定,
head *.md与cat *.md同判。引号里的
*是字面字符,0 命中是空集,两者都仍是读(只是目标为空),不铸假目标。只有「列不出/触顶/不支持」才按「参数含通配」否证。
没有路径参数的读(
grep TODO读 stdin)仍算「读面为空」,不换已证明只读。命令替换(
$(/反引号)仍扫全部词,不按位置放宽。整词
$名/${名}只在「读族 + 路径位」、且整条命令没有运行时改名写法时,按判定这一刻的环境值算死。运行时改名写法包括
X=…、${X:=…}、read X、eval、source、for X in …、select X in …、算术((。未设或空值当空串(读、目标为空);非空值必须是真实存在的路径(只
lstat、不读正文)。argv 仍留原文,环境变量的值不进证据;目标集按「段+路径+动作」去重。
读再分两面:
name(只回名字、计数、元数据,正文不进会话)与body(回正文片段,走敏感面严审)。positions.js每只程序带face:grep -l/-L/-c/-q切 name,grep -n/-o切 body。组合按三类判:
-exec与xargs把内层程序按同一张表再查、内层的面决定本段面;管道各段取最严,且流不洗面(上游只回名字则整条 name,上游读正文则整条 body);重定向是段与文件绑定,不参与组合。facts 产出
readFace一列。git config后面那个点分词是配置键,不是文件。判定要两条同时成立:子命令在表里登记过、点分词形状符合。
形状是
段.名,不以点开头的点分词。取值位、旗标取值位、表外程序、非
config子命令,一律照旧按path。清假目标的边界,是不把真目标一起清掉:
--file <路径>/-C <目录>的取值照旧进目标集。git config --get user.name这类已放行的读,不许因为键位消失而改弹人。「有没有保障」这一问,只在算法量得出「动不掉任何已有东西」时才代填。
量得出的只有两件事:
proven-read(整段已证只读)、target-absent(全部目标都是write面,落点在workspace/trusted/dsh-open之一,且kind全是absent)。以下一律不代填:删除、改名、销毁、提权、历史改写、执行、读面掺进来、落点读不出、动态目标。
「已被 git 跟踪且干净」「有第二份副本」两格还没测得出,也不代填。
代填只发生在模型已判 allow 那格:它判 block 时,第三问的答否是它拒的一部分,抹掉会把台账里的拒因换掉(实测那一格
ask→blocked,方向是往严)。台账按格署名
checksBy(ai或algorithm:<事实>);safeguardedBy的取值不变,两把锁判据读它。没有可对证依据的放行,不成立。
模型要放行,得交出两样机器验得了的东西:
basis.rule逐字等于本次证据里某一行的行名,basis.quote逐字出现在它读到的那份证据里。引你的原话不设下限——你回「可以」两个字就够当授权;引技术碎片不短于 12 字。
引用要照展示形态抄:证据是 JSON 文本,自己解开转义就不再逐字相同。
验不出就落「问人」,不改判成 block——那是模型的话,框架不替它改结论。代价是多问一次人;拿没见过的话作保才是事故。
老调用方不传
basis,维持原判据。本机以外的破坏族是硬闸行,不是模型的话。
覆盖云控制面、IaC、集群、库表语句、容器产物、账号、计划任务的销毁动作,共八行(
DESTROY_ROWS,出厂ask)。认法按「程序名+子命令词/旗标/语句形状」,不按
opClass认——aws s3 rm、terraform destroy在事实层没有可lstat的目标,只看 opClass 会整族漏掉。判定链在这一步就把人抬出来,走不到模型(实测这些形状
review=0),所以 AI 的 allow 与任何上层代放都碰不到它。要放宽,只能由操作者自己改这一行。
代价:命中靠程序名白名单,白名单外的同类工具(如自研 CLI)不在这一族。
边界:读类与本机文件面一格未动(实测对照 15 格逐格同)。
人工放行只按「类」沿用,且同类沿用有帽。
第三把键由五样组成:会话、工具、本次真命中的规则行、面集合(读/写/删)、目标根类别(
classkey.js)。它不跨会话、不跨配置版本,也不活过 TTL。
以下一律不建键、不回放:删除/改名/销毁/提权/历史改写/执行/未归类工具;
elevated:/destroy:/exfil:/credential-/remote-任一理由;进程族与装包族;区外目标;跨项目读;动态目标;无目标事实;以及unrecoverable-content/batch-too-large/facts-truncated/opaque-command/symlink-operand等逐条排除项。跳过时记下原因(
classOf回原因,落进approval.answer行的classReceipt.skip)。同一类键沿用到
CLASS_REUSE_CAP(8 次)后,这一格重新问人,弹框写明「已沿用 N 次」和最初批准的时刻——等于重复调用升级。每次重新批准把计数归零。
实测对照:无批准时 17 格与改前逐格同,类键没新增任何放行;批准后只有「区内 git 写子命令」由
ask→allow,排除格零回放。目标集为空时,没有退化键。
targetDigestOf([])对所有无路径命令算出同一个值。旧形制下,一次
allowed-once就让git push/systemctl restart/npm install/aws ec2 terminate-instances/mysql -e "DROP TABLE"互相顶替(改前实测 6 格ask→allow)。现在空摘要不构成退化键,这些命令各问各的。
宿主给了
actionHash时,按动作键照旧沿用,与目标集空不空无关。AI 层的提示词规则先行。
顺序是:先列命中的规则行(原文逐字,多行全列、取最严),再给用户原话、卡、证据、硬底线,最后判「这次动作在不在规则允许的范围内」。
AI 只回
card + verdict一个 JSON。reasonCode四个出口:no_material_risk(放行)、not_necessary(缩范围回执行端)、authorization_not_covering(要授权回执行端)、safeguards_insufficient(兜不住也回执行端)。执行端被拒收到的整串:
AI 自动拒绝(中文码/原码):评审说:<评审 summary|narrowerScope>|建议:向用户要完整路径和动作。前缀声明这是程序自动拦的(不是用户在拒);中段是评审原文——理由码只到「哪一问没通过」,说不出这一条缺什么,所以原文必留;末段只在authorization_not_covering出现,写死下一步。原文在前、建议在后;建议定长,额度 480 先扣它,截只截原文末尾。不点名路径:facts.targets(role 混装,读源与写目的同列)与用户原话里正则抽的资源(只认路径形状)都是推断,推断不准就不写成断言。unable只留证据不足,仍问人。三问由结论反推:
allow对应三真;各 block 把对应那问答 false。授权来源只认这一条口径:算法路由到 AI、allow、依据有效、grant auto、算得稳(非不可逆、无动态目标、无运行时展开、非解释器族)。台账记
safeguardedBy=authorization/safeguardFact=user-words+rule-row。任务卡由评审端抽(可做/在哪/不可做/单独授权,每项带用户原话逐字引文,不短于 12 字)。
插件按会话存,下次带上;引文核不过的项丢弃并计数。卡只收窄,不放宽。
命令面一条命令一行,行上只有四件:
command/action/enabled/note。verbs/flags/statements/pairs/history/ops/execFamily/execDynamic/scope这些条件与范围字段,一个都不进表。算法根据这次调用算出的结果只影响判定,不再冒充表行——原 11 条结果行整类拿掉。
proven-read/unproven-read/interpreter/inline/piped/process/package/opaque-command落末行兜底交 AI。来源不可举证的内联与管道、框架解释不了的删除参数,由事实层内部下界保
ask,不进表、不可配。同一段命中多行时取最长命令名(具体压泛化:
git push压git,docker system prune压docker)。多段命令各自命中,合成取最严。
末行「其他命令」固定最后:可改档、可停用,不可删、不可上移。
前面所有命令一个都没命中时,这次调用交 AI 判,绝不空放。
出厂命令面有 106 行
release,全是读族(cat/ls/git status/grep…)。它们带到会写的参数时(
sort -o、sed -i),写仍由文件面按落点判,删除由事实层下界问人。文件面、工具面、网络面的行与档,一格未动。
No comments yet. Be the first to write one.