npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@suagr_xl/dsh-safety

v0.2.1

Published

A safety harness plugin for DeepSeek Harness (DSH): protected-path enforcement, trash-based safe_delete with undo, last-known-good composition snapshots, pre-restart composition checks, and an audit journal.

Readme

dsh-safety

English | 中文

是什么 · 功能 · 审批流程 · 安装 · 快速上手 · CLI · 工具 · 配置 · 原理 · 目录结构 · 测试 · 故障排查 · 安全 · 设计 · 常见问题 · 已知限制

是什么

DeepSeek Harness(DSH)的文件系统安全护栏。它在工具执行边界强制一套三级文件策略,并且关键的是——让模型在删除/改写任何重要文件前先向人提问

  • 破坏性调用在执行之前被拒绝,并附教育式消息:目标是什么、为什么重要、正确的替代路径是什么;
  • 每次删除都走可恢复的回收站;
  • 插件组合可快照、可事务化回滚;
  • 重启前对组合做校验;
  • 敏感删除/改写需要一次性、限时的用户审批——模型永远无法自我授权。

零运行时依赖。既可安装为标准的 DSH profile bundle,也附带独立 CLI——DSH 本身无法启动时,恢复层与审批层依然可用。

背景——护栏规则源于一次真实生产事故:脚本因 PowerShell $HOME 是只读变量而静默解析错路径,Remove-Item -Recurse -Force 删除了整个引擎运行根目录。该目录能恢复,仅因为它属于可再生成的生成内容;手写文件则会永久丢失。插件将这次事故的教训实现为强制机制,而非文档说明。

功能

  • 执行前守卫ctx.tools.guard):在工具真正运行之前拒绝破坏性调用。
    • 递归删除目录rm -r/-rfRemove-Item -Recurserd /srmdirshutil.rmtreefs.rm recursiverequire('fs').rmSync…)默认一律拒绝,强制走 safe_delete(回收站、可撤销);cooperative 模式下人类可批准放行一次自由路径的递归 shell 删除。
    • write/edit/str_replace_editorprotected 区(profile 的 package.jsoncordis.patch.ymlcordis.yml、lockfile、node_modules、部署安装目录、home 级补丁/设置)→ 拒绝,除非用户已批准。
    • 删除命中 confirm 区(整个 OS 用户主目录、插件源码、agent-preset)→ 拒绝并需已获批的审批。
    • run_code 代码体同样被扫描——任意代码执行不能靠"绕过工具边界"把对受保护区的 fs.rmSync/shutil.rmtree 藏起来。
    • 变量引用删除也能拦——Remove-Item "$env:USERPROFILE\.dsh\…" 这种展开后才是真实路径的命令,会把引用+尾段与保护标记比对并拒绝。
  • 教育式、反绕过的拒绝——被拦时返回:为什么拦、目标是什么、后果是什么、正路是什么;systemPrompt 明确要求模型"被拦就停、不许换姿势绕过、直接问用户";同一目标反复被拦会升级为明确的 STOP 警告。
  • 用户审批门禁——safety_ask 发起带因果的结构化请求(是什么/为什么/后果/替代方案);人类通过 dsh-safety allow <id>(或 dsh-safety delete --force)批准;审批一次性 + 限时 + 全审计 + 跨进程原子锁串行化(web + headless 不会并发丢更新)。模型永远无法自我批准——force:true 单独不算数。每个请求还携带系统按真实路径分类计算的后果systemNote),与模型自述分开呈现——你批准时看到的是系统背书的判定,而非模型不可核验的故事。
  • safe_delete —— 唯一合法的删除通道。删除=移动进回收站(safety_undo 可还原);preview:true 先看再删;拒绝文件系统根和自身状态目录;每次删除都进审计日志。
  • 组合快照 —— safety_snapshot 把整套插件组合(每个 profile 的 manifest/补丁/lockfile、插件 package.json+cordis.patch.yml、agent-preset)带 SHA-256 存起来;safety_restore 事务化回滚到 last-known-good(现行文件先自动备份,失败的整体回滚不会把组合留成半恢复态)。默认排除含凭据的文件。
  • 重启前体检 —— safety_check 检查 UTF-8、乱码检测(错误编码往返,就是"DSH 打不开"的经典原因)、JSON 可解析、跨补丁层重复插件行 id("一行只能在一个层"规则)。
  • 审计日志 —— 拦截/审批/删除/快照/回滚全部留痕,safety_journal / safety_status 可查。
  • 独立 CLI —— dsh-safety 不依赖 DSH:在你自己终端就能 policy/delete/undo/snapshot/restore/check/approve,DSH 打不开时也能用。

审批流程

审批系统的全部意义在于:模型永远不能批准自己的破坏性调用。模型能设置的标志(force:true)不是确认——只有人类动作(CLI)才是。

模型对 confirm/protected 区发起删除/改写
        │
        ▼
守卫拦截(教育式消息:是什么 / 为什么 / 后果 / 正路)
        │
        ▼
模型调用 safety_ask { path, kind, what, why, consequence, alternative }
        │   → 生成请求(系统同时按真实路径分类计算权威后果)、返回 id、写入审计
        ▼
模型告诉用户:"请批准:dsh-safety allow <id>"
        │
        ▼
用户执行  dsh-safety allow <id>     (或:dsh-safety delete --force)
        │   → 授予一次性、限时的审批(默认 5 分钟)
        ▼
模型重试原调用 → 守卫消费该审批并放行
        │   (审批已用掉;下一次同目标调用会再次被拦)
        ▼
全程审计:谁请求、谁批准、何时、何时被消费

实际细节:

  • 请求:调用被拦时,拒绝消息会提示模型用 safety_ask 并附上因果(what/why/consequence/alternative)。请求同时携带系统计算的 systemNote——dsh-safety approvals[system] 判定与 (model: …) 自述分开呈现,让你基于系统分类做知情决策,而非模型的单方陈述。
  • 批准dsh-safety allow <id> 批准模型创建的请求;dsh-safety allow --path <p> --kind delete|write [--recursive] 直接创建并批准一个(你就是人类);dsh-safety delete --force 在 confirm/protected 路径上也会先授予所需审批再移入回收站。
  • 一次性:审批被第一个匹配调用消费(kind 与 target 精确匹配;递归审批按 flag 精确匹配、target 可为空)。用后即失效。
  • 限时:已批准的请求在 approvalTtlMs(默认 5 分钟)后过期,需重新批准。
  • strict 与 cooperativemode: strict(默认)下递归 shell 删除永不可批准——删目录树的唯一方式就是 safe_delete(回收站、可撤销);mode: cooperative 下人类可用通用递归审批(dsh-safety allow --path … --recursive)放行一次自由路径的递归删除。
  • 防循环:模型对同一目标反复重试时,守卫会升级并明确要求它停止、去问用户。

安装

系统要求:已装好 DeepSeek Harness(dsh web 能启动)。npm 安装无额外要求;从仓库安装需要 Node.js >= 22 与 pnpm。

从 npm 安装(推荐)

dsh plugin --profile web add @suagr_xl/dsh-safety   # 从官方 npm registry 安装 / install from the official npm registry

dsh plugin 会跑 pnpm,并因本包声明了 dsh.bundle 自动把它加进 dsh.profile.bundles。装完重启 dsh web,守卫即生效、safety_* 工具可用。

从仓库安装(开发调试)

git clone https://github.com/sugarxl/dsh-safety.git   # 克隆仓库 / clone the repo
cd dsh-safety                                         # 进入目录 / enter the directory
dsh plugin --profile web add link:$(pwd)              # 把仓库软链进 profile / symlink the repo into the profile

link: 是软链(改 lib/ 重启即生效),file: 则是复制快照。dsh plugin 会自动 reconcile 进 bundles。注意:profile 目录不是 pnpm workspace,workspace:* 依赖会回退到 npm 仓库——本插件完全没有运行时依赖(import 只有 Node 内置 + 自己的 safety-core.mjs/state.mjs/audit.mjs),所以裸 link: 安装不需要它自己的 node_modules,也不存在回退问题。

官方安装布局

两种方式都走官方 dsh plugin 机制,装完无需任何手工配置:

$DSH_HOME/profiles/<name>/package.json                # 新增依赖 + dsh.profile.bundles / dependency + dsh.profile.bundles
$DSH_HOME/profiles/<name>/node_modules/@suagr_xl/dsh-safety/    # 安装的包本体 / installed package

bundle 层在启动时从包内的 cordis.patch.yml 读取。dsh-safety 这个行 id 只能出现在这一个层(包内文件)——不要再写进 profile 或 home 的 cordis.patch.yml

验证与卸载

dsh --profile web --dump-config | grep -i dsh-safety   # 确认行出现 / row present
dsh-safety check                                        # 重启前体检 / pre-restart gate
# 重启 dsh web / restart dsh web

# 卸载:/ uninstall:
dsh plugin --profile web remove @suagr_xl/dsh-safety
# 重启 dsh web / restart dsh web

独立 CLI(不装插件也能用)

npm link   # 或直接: node bin/dsh-safety.mjs ... / or directly: node bin/dsh-safety.mjs ...
dsh-safety status

CLI 与插件读写同一个 $DSH_HOME/.dsh-safety 状态目录,DSH 挂了也能 approve/undo/restore。

安装排障

  • 装了也重启了,但没生效:要重启整个 dsh web 进程,刷新页面不够;用 dsh --profile web --dump-config 确认行已挂载。
  • ERR_PNPM_IGNORED_BUILDS:pnpm 拒绝依赖的构建脚本,把提示的包加进 profile 的 pnpm-workspace.yaml allowBuilds 后重跑。
  • pnpm 发布年龄门禁装到旧版:pnpm 11 的 minimumReleaseAge 会在发布后约 10 天内静默装旧版;在 profile 的 pnpm-workspace.yamlminimumReleaseAgeExclude: ['@suagr_xl/dsh-safety'],再执行 dsh plugin --profile web update @suagr_xl/dsh-safety 升级。

快速上手

# 1. 查看当前策略分区
dsh-safety policy

# 2. 修改任何组合文件之前,先快照
dsh-safety snapshot before-edit

# 3. 通过安全通道删除(先预览,再执行)
dsh-safety delete path/to/file --preview      # free 路径——直接可用
dsh-safety delete path/to/file                # 移入回收站(可撤销)
dsh-safety delete path/to/important --force   # confirm/protected 区:
                                              #   CLI 的 --force 就是人类审批

# 4. 恢复误删
dsh-safety trash
dsh-safety undo <trash-id>

# 5. 启动失败时:先校验,再回滚
dsh-safety check
dsh-safety status          # 查看快照 + 待批准请求
dsh-safety restore <snapshot-id> --confirm

# 6. 批准模型创建的审批请求(模型通过 safety_ask 提问后)
dsh-safety approvals
dsh-safety allow <request-id>

CLI 参考

dsh-safety status                  状态:回收站/快照/审批/日志
dsh-safety delete <path> [--force] [--preview]
dsh-safety trash [--limit N]
dsh-safety undo <id>
dsh-safety snapshot [label] [--exclude a,b]
dsh-safety restore <id> --confirm
dsh-safety check                   失败时 exit 1(适合 CI)
dsh-safety journal [n]
dsh-safety policy                  当前策略分区
dsh-safety approvals               列出待批准/已批准请求
dsh-safety allow <id>              批准模型创建的请求
dsh-safety allow --path <p> [--kind delete|write] [--recursive]   直接创建并批准一个
dsh-safety revoke <id>             撤销请求
dsh-safety help

--home <path> 可覆盖状态根(默认 $DSH_HOME~/.dsh)。

插件配置的根目录在 cordis 补丁层里,独立 CLI 读不到——delete/policy 因此接受同样的覆盖参数,以便与运行中的守卫对齐:

--write-root <path>      追加 protected(禁写/改/删)根目录
--confirm-root <path>    追加 confirm-delete(仅回收站)根目录
--no-home-confirm        不把整个 OS 主目录设为 confirm 区
--keep-trash=N / --keep-snapshots=N   删除/快照后的保留上限

CLI 是审批流的人侧:dsh-safety delete --forcedsh-safety allow真实的用户授权(写入状态);模型永远无法自我批准。

模型侧工具(以插件方式安装后)

| 工具 | 作用 | |---|---| | safe_delete | 回收站式删除(preview / 用户批准 / 可撤销)。force:true 不是用户批准——删除前必须已有获批的审批请求 | | safety_ask | 带因果(是什么/为什么/后果/替代方案)地向用户请求审批;用户通过 dsh-safety allow <id> 批准 | | safety_trash / safety_undo | 列回收站 / 还原条目 | | safety_snapshot / safety_restore | 快照组合 / 回滚(需 confirm:true) | | safety_check | 重启前校验(UTF-8 / 乱码 / JSON / 重复 id) | | safety_journal / safety_status | 审计日志 / 状态(含待批准请求) |

配置

在补丁层覆盖插件行配置(例如 profile 的 cordis.patch.yml):

- id: dsh-safety
  config:
    blockWriteRoots: ["C:\\extra\\protected"]
    confirmDeleteRoots: ["D:\\data"]
    snapshotExclude: ["settings.yaml", ".credentials.yaml"]
    blockWrites: true
    blockShellDestructive: true
    audit: true
    keepTrash: 200
    keepSnapshots: 10
    mode: strict            # strict | cooperative
    approvalTtlMs: 300000   # 已批准请求的有效期(默认 5 分钟)

| 字段 | 默认 | 含义 | |---|---|---| | blockWriteRoots | profile manifest/补丁/lockfile/node_modules、安装目录、home 补丁/设置 | 禁写/改/删 | | confirmDeleteRoots | $HOMEprofiles/*.agent-presets | 禁删(需已获批的审批,仍只进回收站) | | snapshotExclude | ["settings.yaml", ".credentials.yaml"] | 永不复制进快照的文件 | | blockWrites | true | 开/关写保护守卫 | | blockShellDestructive | true | 开/关 shell 删除守卫 | | audit | true | 记录破坏性工具调用 | | keepTrash / keepSnapshots | 200 / 10 | 保留上限 | | mode | strict | strict:递归 shell 删除永不可批准;cooperative:人类可通过审批流授权 | | approvalTtlMs | 300000 | 已批准请求的有效期,过期需重新批准 |

原理

三级策略:

| 级别 | 允许 | 禁止 | 默认覆盖 | |---|---|---|---| | protected | 读 | 写 / 改 / 删(除非已获用户审批) | profile 的 package.json/cordis.patch.yml/cordis.yml/lockfile/node_modules、安装目录、home 补丁与设置 | | confirm | 读、编辑 | 删(需已获批的审批,仍只进回收站) | 整个 $HOME、插件源码、agent-preset | | free | 读写删 | 递归删(cooperative 模式下可批准) | 普通工作区文件 |

守卫对每次工具调用的判定链:有破坏性动词?→ 是不是递归删除?→ 显式路径是否命中 protected/confirm?→ 变量引用片段($env:X\…%X%\…${X}/…)是否展开进受保护区?→ 命令文本是否命中保护标记(~/相对路径形式)?→ run_code 代码体走同一条链 → 存在匹配的已获批审批则放行一次,否则拒绝。

拒绝是教育式的:说明目标是什么、描述它、解释后果(如"改写会让 DSH 启动失败")与正路(safe_delete / safety_ask),并提示模型不要绕过;同一目标反复被拦会升级为明确的 STOP。拒绝会写审计日志并作为错误返回给模型(绝不会导致进程崩溃)。

第二层:挂 fs/write-intent / fs/edit-intent 瀑布,任何途径写 protected 路径都抛 FS_DENIED

buildPolicy 位于 safety-core.mjs,插件守卫和独立 CLI 共用同一份策略,两套表面永远不会漂移。restoreSnapshot 是事务化的:先备份现行文件、再从快照复制回去,任一阶段失败就整体回滚——失败的恢复永远不会把组合留成半恢复状态。审批状态存放于 $DSH_HOME/.dsh-safety/state.json,守卫、safe_delete 与 CLI 共用。

目录结构

dsh-safety/
├── bin/
│   └── dsh-safety.mjs        # 独立 CLI(零依赖)
├── lib/
│   ├── safety-core.mjs       # 纯逻辑:策略/守卫/回收站/快照/校验
│   ├── index.js              # host 半区:工具、guard、fs 钩子
│   ├── state.mjs             # 持久化状态(审批/守卫计数)——已接入 index.js
│   ├── audit.mjs             # JSONL 审计日志 + 阈值告警——已接入 index.js
│   ├── policy.mjs            # 策略细化工具(符号链接/挂载检测,已导出)
│   └── snapshot-store.mjs    # 增量快照工具(baseline/delta,已导出)
├── test/
│   ├── safety.test.mjs       # 单测:核心守卫/回收站/快照/校验
│   ├── state.test.mjs        # 状态持久化 + 审批生命周期
│   ├── audit.test.mjs        # 审计日志 + 告警
│   ├── policy.test.mjs       # 策略细化
│   ├── snapshot-store.test.mjs # 增量快照
│   └── harness.mjs           # 集成检查(干净检出,零依赖)
├── cordis.patch.yml          # bundle 补丁(插入 dsh-safety 行)
├── package.json              # dsh.bundle + bin
├── install.ps1 / recover.ps1 # 本地便捷脚本(快照→安装→校验→回滚)
├── README.md / README.zh.md  # 文档(中英双语,官方配对)
└── LICENSE / NOTICE / SECURITY.md

测试

npm test                        # 全部单测(核心 + state/audit/policy/snapshot-store)
node test/harness.mjs           # 集成检查,干净检出(无需 @deepseek-ai)
npm run check                   # 每个 lib/bin 模块的语法检查

当前规模:68 个单测 + 集成 harness,Windows 与 Linux CI(Node 22/24)全绿。

故障排查

  • 改完插件后 DSH 打不开:跑 dsh-safety check 找乱码/JSON/重复 id;dsh --profile web --dump-default-config 看不带用户层的 bundle 层;dsh-safety restore <id> --confirm 回滚快照。
  • 守卫拦了合法操作:守卫从不拦读和插件源码编辑;它拦的是 $HOME/插件/配置区的删除并要求人类审批。用 safe_delete(可撤销)代替裸 rm;confirm/protected 路径上让模型先 safety_ask,你用 dsh-safety allow <id> 批准。
  • 需要删除受保护路径:CLI 侧 dsh-safety delete <path> --force——CLI 用户就是人类,--force 即真实审批,仍然只进回收站、永不真正删除;模型侧必须有已获批的审批(光 force:true 不够)。
  • 模型被拦后反复换姿势绕过:这正是守卫要阻止的。明确告诉它调用 safety_ask 并等待你的批准,或直接拒绝该请求。

安全

完整威胁模型见 SECURITY.md。要点:守卫拦截的是模型工具调用,不是你在自己终端敲的命令;run_code 扫描是文本级的,动态/混淆代码可能绕过;state.json 里的审批记录可被同进程插件篡改。它是安全网,不是沙箱——真正的隔离请配好 DSH 自带的沙箱/审批,用本插件补 DSH 缺失的恢复层 + 问人层

License

MIT。集成模式参考 DeepSeek Harness(MIT),见 NOTICE