Back to home

yyyyolo7a79-sketch

dsh-auto-and-safety

DSH auto-and-safety permission preset: full file access but no delete without user approval

Stars
2
Language
JavaScript
Created
Aug 15, 2026
Updated
Aug 15, 2026

Introduction

dsh-auto-and-safety 权限插件

为 DeepSeek Harness (DSH) 新增一个 auto-and-safety 权限预设:继承 danger-full-access(全文件权限)的能力,但禁止一切删除文件的行为;每次任务完成后,agent 必须向用户询问是否删除任务过程中产生的临时(tmp)/ 冗余文件,指明文件是什么、文件位置,经用户明确批准后才可删除


目录


1. 需求背景

用户原始需求(原话):

在你的权限 read only / workspace write / all accuse 中再加一条权限 auto-and-safety: 内容为继承 all accuse 的权限,但是禁止一切删除文件的行为,每次任务完成后向用户询问是否删除任务过程中产生的 tmp、冗余文件,并指明文件是什么以及文件位置在用户批准后才可以删除

即:希望 agent 拥有"全权限"干活能力的同时,把"删除文件"这一不可逆高危操作锁死为人批后放行,防止 agent 自行清理/误删。


2. 总体设计

实现分两层,缺一不可:

载体作用
权限预设层cordis.patch.ymlpermission 行的 presetsauto-and-safety 成为可切换的权限预设(GUI 选择器 + /permission 命令可见可选)
行为强制层本地插件 plugins/auto-and-safety/lib/index.js① 向系统提示注入安全规范;② 在 tools/pre-execute 工具执行门上硬拦截删除命令

设计要点:

  • 预设 = 两个旋钮的组合:DSH 的权限预设表每项只有 sandbox(文件沙箱模式)和 approval(审批策略)两个字段。auto-and-safety = sandbox: danger-full-access(继承 all access)+ approval: ask(保留审批通道)。
  • "禁止删除"无法用旋钮表达:DSH 沙箱模式枚举只有 read-only / workspace-write / danger-full-access,没有"禁止删除"模式。因此"禁止删除 + 批准后放行"必须落在工具执行前的拦截钩子上。
  • 官方 fs 工具本身没有删除能力:DSH 的 fs 工具只有 read / write / edit / read_image,删除文件只能通过 shell(pwsh / bash)命令或 .NET 调用。所以拦截点聚焦 shell 命令即可覆盖实际删除路径。

3. DSH 权限体系原理(前置知识)

以下基于 @deepseek-ai/dsh rc.6 源码,整合时如版本变化请以实际源码为准。

3.1 三个硬编码枚举

概念定义位置取值
沙箱模式 SandboxMode@deepseek-ai/dsh-sandbox-policySANDBOX_MODESread-only / workspace-write / danger-full-access
审批策略 ApprovalPolicy@deepseek-ai/dsh-user-approvalAPPROVAL_POLICIESask / never
沙箱升级阶梯@deepseek-ai/dsh-sandboxWIDER_MODESread-only → workspace-write → danger-full-access(只升不降)

3.2 权限预设表(permission presets)

  • 插件:@deepseek-ai/dsh-permission-presets(行 id:permission,位于 dsh-base bundle)。
  • 配置:presets 是一个 dict,每项 { sandbox, approval, name?, description? }
  • 切换机制:切换预设会向会话日志追加 permission/preset 事件,并分别写 sandbox/modeapproval/policy 事件;会话通过日志回放(fold)恢复当前预设,因此重启不丢状态。
  • 派生逻辑 derive():按"当前折叠的 sandbox + approval 值"匹配表项;匹配不到显示为 custom
  • custom 是保留名,不能用作家表项名。
  • 默认预设推断:无显式 defaultPreset 时取组合默认值(workspace-write + ask)能匹配到的表项。

3.3 组合补丁机制(patch)

  • DSH 的 profile 组合 = 多个 bundle 的 cordis.patch.yml 按顺序叠加 + 用户 patch 层(<DSH_HOME>/profiles/<profile>/cordis.patch.yml)。
  • patch 条目语法(顶层 YAML 数组):
    • - id: <行id> + 其余字段 → 整行覆盖目标行的对应字段(config 整体替换,不是合并);
    • - insert: [...] → 插入新行(顶层或指定组);
    • - id: <行id> + disabled: true → 禁用。
  • dsh-basepermission 行的默认 presets 只有三个:read-only / workspace-write / danger-full-access
  • 用户 patch 层被 HMR 监听,修改后热重载,无需重启 web。

3.4 工具执行前拦截门(tools/pre-execute waterfall)

  • 每个工具调用执行前,工具注册表跑一个 waterfall:ctx.waterfall(carrier, 'tools/pre-execute', exec, () => ({ kind: 'allow' }))
  • 任何插件可用 ctx.on('tools/pre-execute', (exec, next) => ...) 注册监听;返回 { kind: 'deny', reason } 即拒绝本次调用,工具结果会显示 Error: <reason>;返回 next() 放行。
  • exec 结构:{ name, arguments, agent, callId, signal, ... }exec.agent.session 是调用方会话,exec.arguments.command 是 shell 命令文本。
  • 语义:任一监听可 deny,且没有监听可以强制放行已被 deny 的调用。

3.5 系统提示动态注入(systemPrompt context)

  • ctx.systemPrompt.context({ name, order, text: (context) => string }) 注册动态提示段,text 每次构建提示时调用,可读 context.agent.session 按会话状态决定输出。
  • 参考:dsh-sandbox-policy 用 order 110 注入 "Current DSH file policy: ...";本插件用 order 111 注入安全规范。

4. 实现细节

4.1 预设表配置(cordis.patch.yml 片段)

- id: permission
  config:
    presets:
      read-only:
        sandbox: read-only
        approval: ask
      workspace-write:
        sandbox: workspace-write
        approval: ask
      danger-full-access:
        sandbox: danger-full-access
        approval: never
      auto-and-safety:
        sandbox: danger-full-access
        approval: ask
        name: auto-and-safety
        description: 继承 danger-full-access 的全部文件权限,但禁止一切删除文件的行为;任务完成后向用户询问是否删除任务产生的临时/冗余文件(指明文件与位置),批准后才可删除
- insert:
    - id: auto-and-safety
      name: ./plugins/auto-and-safety/lib/index.js

⚠️ 因为是整行替换,必须写全全部四个预设,漏掉任何一个都会使它在权限选择器中消失。

4.2 插件逻辑(plugins/auto-and-safety/lib/index.js)

纯 ES Module,无第三方依赖(不 import 任何包),导出 name / inject / apply,符合 Cordis 本地插件格式。

export const name = 'auto-and-safety';
export const inject = ['systemPrompt', 'permissionPresets'];

① 删除命令识别正则DELETE_CMD_RE,大小写不敏感,安全优先宁可误拦):

目标匹配
PowerShell cmdlet 与别名remove-item ri rm del erase rmdir rd empty-recyclebin
cmd 命令del erase rmdir rd
bash 命令rm rmdir unlink shred
.NET 静态调用[IO.File]::Delete(...)::delete\s*\(

rd 单独要求后随路径/开关(rd "路径" / rd /s ...),避免把参数里的目录名误判为删除命令。

② 批准放行判定GRANT_RE + QUESTION_RE):

  • 扫描会话事件(倒序),找最后一条 user/message 事件且 source.kind === 'user'(排除工具注入、系统消息);
  • 其文本命中批准短语 → 放行;命中疑问句([吗么呢??]\s*$)→ 视为询问而非批准;
  • 批准短语覆盖:"批准/同意/允许/确认(的|了)删除/删掉/移除/清理"、"请删除/请删掉"、"可以删除(了|吧)"、"删除吧/删掉吧/清理掉"等。

③ 拦截器tools/pre-executeprepend: true):

ctx.on('tools/pre-execute', (exec, next) => {
  if (exec?.name !== 'pwsh' && exec?.name !== 'bash') return next();
  const session = exec.agent?.session;
  if (!session || currentPreset(ctx, session) !== 'auto-and-safety') return next();
  const command = exec.arguments?.command ?? '';
  if (!DELETE_CMD_RE.test(command)) return next();
  if (hasUserGrant(session)) return next();
  return { kind: 'deny', reason: DENY_REASON };   // 拒绝,附流程指引
}, { prepend: true });

判定链:仅当 ①工具是 shell、②会话预设为 auto-and-safety、③命令命中删除模式、④最近用户消息无批准 → 拒绝。任何一步不满足都放行,因此不影响其他预设与其他工具。

④ 系统提示注入systemPrompt.context,order 111):

仅当会话预设为 auto-and-safety 时输出安全规范:禁止一切删除;任务完成后必须用 ask_user_question 列出文件(名称 + 完整路径 + 用途)询问;批准后才可删;无临时文件时明确说明。


5. 安装步骤

无论哪种安装方式,都必须手动合并 cordis.patch.yml 中的两个补丁条目(① - id: permission 预设表、② - insert: 挂载条目)——dsh 的 patch 是用户 profile 的配置层,安装命令不会代你改配置。合并时注意 permission 条目必须写全四个预设(整行替换语义,漏写会从权限选择器消失)。

5.1 快速安装(复制 GitHub 链接即可)

方式 A:git clone 到 plugins 目录(推荐,与 dsh-session-search 同款方式,挂载用本地路径)

# ① 克隆仓库到 profile 的 plugins 目录(以 web profile 为例)
git clone https://github.com/yyyyolo7a79-sketch/dsh-auto-and-safety.git <DSH_HOME>/profiles/web/plugins/dsh-auto-and-safety

# ② 把仓库根目录 cordis.patch.yml 的两个条目并入你的 patch 层:
#    <DSH_HOME>/profiles/web/cordis.patch.yml。
#    其中 - insert: 的挂载路径要写完整(克隆后仓库根在 plugins/dsh-auto-and-safety/,
#    插件本体在其内部的 plugins/auto-and-safety/lib/index.js):
#    - insert:
#        - id: auto-and-safety
#          name: ./plugins/dsh-auto-and-safety/plugins/auto-and-safety/lib/index.js

# ③ 验证生效(配置走 HMR 热重载,通常无需重启;若权限选择器无新选项则重启 web)
dsh --profile web --dump-config | grep auto-and-safety

方式 B:dsh plugin 命令安装(装进 profile 的 node_modules,pnpm 方式)

# ① 安装插件包(等价于在该 profile 目录执行 pnpm add;#main 可换成实际默认分支)
dsh plugin --profile web add github:yyyyolo7a79-sketch/dsh-auto-and-safety#main

# ② 同样把 cordis.patch.yml 两个条目并入,但 insert 的 name 改用包名(插件在 node_modules 里):
#    - insert:
#        - id: auto-and-safety
#          name: dsh-auto-and-safety

安装完成后,在会话中执行 /permission auto-and-safety(或在 GUI 会话权限选择器中选择)即可启用。

5.2 手动安装

  1. 放插件源码:把 plugins/auto-and-safety/ 整个目录复制到 <DSH_HOME>/profiles/<profile名>/plugins/ 下(与 dsh-session-search 等本地插件同级)。
    • 例如:C:\Users\PC\.dsh\profiles\web\plugins\auto-and-safety\lib\index.js
  2. 改配置:把 cordis.patch.yml 中的两个条目(- id: permission- insert:)并入 <DSH_HOME>/profiles/<profile名>/cordis.patch.yml
    • 若你的 patch 层已有 - id: permission 条目,用新 presets 覆盖即可(注意写全四个预设)。
  3. 生效
    • 配置走 HMR 热重载,通常无需重启;若 GUI 权限选择器未出现新选项,重启 web。
    • 验证组合:dsh --profile web --dump-config,应能看到 auto-and-safety 预设与插件行。
  4. 启用:在会话中执行 /permission auto-and-safety,或在 GUI 会话权限选择器中选择 auto-and-safety

6. 使用流程(与演示截图对应)

步骤动作截图
1权限栏出现新的 auto-and-safety 预设docs/权限栏新增.png
2任务完成后,agent 用 ask_user_question 列出临时文件(名称 + 路径 + 用途)询问docs/拦截演示1.png
3选择框选择 ≠ 批准,删除命令被硬拦截,要求对话内文字明确批准docs/拦截演示2.png
4用户对话回复"批准删除/可以删除/删除吧"等批准短语docs/拦截记录/session.jsonl
5agent 重试删除命令 → 拦截器检测到最近用户消息含批准短语 → 放行,删除成功docs/批准后成功删除.png

演示截图

① 权限选择器:新增的 auto-and-safety 预设(当前已选中)

权限栏新增

② 任务完成后,agent 列出临时文件询问用户是否清理(ask_user_question)

拦截演示1

③ 选择框选择不算批准,删除命令被硬拦截,要求对话内文字明确批准

拦截演示2

④ 用户在对话中回复"批准删除"后,删除执行成功

批准后成功删除


7. 实现效果

agent 视角(会话内)

  • 系统提示出现 auto-and-safety 专属规则段(order 111),agent 从第一轮起就知道:

    • 禁止一切删除文件/目录的行为;
    • 任务完成后必须主动列出 tmp/冗余文件(名称 + 完整路径 + 用途)并询问;
    • 只有用户明确批准后才可删除;没产生临时文件时明确说明"无临时文件需要清理"。
  • 删除命令(shell / .NET)在工具执行门被硬拦截,返回:

    Error: auto-and-safety 权限预设禁止直接删除文件/目录:此命令包含删除操作且本会话尚未获得用户批准,已拦截。正确流程:先用 ask_user_question 向用户列出要删除的每个文件(名称 + 完整路径 + 用途/是什么),征得用户批准;用户回复明确的批准(如"批准删除""同意删除""可以删除""删除吧")后,再重试删除该文件的命令。不得用脚本、编码或其他命令变体规避拦截。
    
  • 拦截提示会引导 agent 走"询问 → 批准 → 重试"闭环,而不是绕过。

用户视角

  • 权限选择器//permission 多一个 auto-and-safety 选项,切换即时生效;
  • 每次任务完成后收到明确的清理询问(文件是什么、在哪、要不要删);
  • 删除永远发生在自己点头之后。

8. 行为示例(拦截记录)

以下为真实会话回放(docs/拦截记录/session.jsonl,DSH 会话日志)的完整「拦截 → 询问 → 批准 → 放行」闭环。

场景:搭建 deepseek + dsh 工作流时,agent 批量安装 12 个目标插件(最终装 8 个插件 + 5 个技能),任务完成后产生约 20MB 临时文件(readme_dsh_plugins.mdplugin-install\ 研究目录等),触发 auto-and-safety 清理流程。

阶段事件要点
任务执行安装插件与技能,全程未触碰 .agents / .claude 目录产生临时文件:1 个 94KB README + 安装研究目录(12 份文档 + 10 个 git 克隆)
① 拦截agent 尝试直接清理临时文件删除命令在 tools/pre-execute 被硬拦截,返回 Error 并提示正确流程
② 询问agent 改用 ask_user_question 列出待删文件(名称 + 完整路径 + 用途)选择框点击不算批准——工具层未收到批准标记
③ 引导agent 说明规则,请用户在对话内文字明确批准"我不会绕开这个保护,请你直接回复确认"
④ 批准用户回复「批准删除」命中批准短语(GRANT_RE),授权生效
⑤ 放行agent 重试删除命令拦截器检测到最近用户消息含批准短语 → 放行
⑥ 完成删除成功并验证仅删除批准的两个对象,工作区其余文件未动

完整对话回放见 docs/拦截记录/session.jsonl(JSONL 格式,可用 DSH 会话查看器打开)。


9. 已知限制与边界

  1. shell 命令拦截无法穷举所有删除形态:正则覆盖常见删除命令(Remove-Item/rm/del/erase/rmdir/rd/unlink/shred/Empty-RecycleBin/.NET ::Delete),但理论上仍存在变体(脚本文件内部删除、编码混淆、外部程序调用等)。设计上"宁可误拦、不可漏放",且 prompt 规范 + 拦截双重约束,实际已覆盖 99% 场景。
  2. 一次批准放行窗口:拦截器以"最近一条用户消息是否含批准短语"为准,用户批准后到下一次用户消息之前的所有删除命令都会被放行。因此应引导 agent 一次性列出全部待删文件问清,避免逐文件多次询问。
  3. 子代理(subagent)会话:子代理会话默认走默认预设(workspace-write),拦截不直接作用于子代理的 shell 调用;但父会话的 prompt 规范会传导,行为上仍受约束。
  4. 审批策略旋钮:预设的 approval: ask 使会话审批策略为 ask;在 danger-full-access 下沙箱无升级需求,ask 主要保留审批通道与提示语义,不产生额外弹窗。
  5. HMR 依赖:配置热重载依赖 web 的 HMR 服务;极端情况下(插件文件损坏、配置 YAML 语法错误)会导致 loader 报错,此时重启 web 并按报错修复。

10. 卸载方法

  1. cordis.patch.yml 删除 - id: auto-and-safetyinsert 条目(可保留 permission 行的 presets,去掉 auto-and-safety 一项即可恢复三预设)。
  2. 删除 plugins/auto-and-safety/ 目录。
  3. HMR 自动生效或重启 web。

11. 给 dsv4 / 后续整合者的说明

  • 文件清单
    • plugins/auto-and-safety/lib/index.js — 插件本体(唯一代码文件,零依赖)
    • cordis.patch.yml — 配置补丁(预设表 + 插件挂载)
    • README.md — 本文档
    • docs/需求.md(原始需求)、4 张演示截图、拦截记录/session.jsonl(演示会话回放)
  • 整合最小改动:复制插件目录 + 合并两个 patch 条目,改改 description 文案即可。
  • 版本兼容:基于 @deepseek-ai/dsh 0.1.0-rc.6。升级 DSH 后若权限体系接口变化(SANDBOX_MODES / APPROVAL_POLICIES / tools/pre-execute / permissionPresets.current()),按新源码调整。
  • 可扩展方向
    • 把"批准短语"做成插件 Config(z.object({...})),允许用户自定义放行词表;
    • 增加"删除前自动询问"(不 deny 而是转 ask_user_question)模式;
    • 将拦截扩展到 run_code 语言派发(如 Python os.remove)等更多删除形态。