sugarxl / dsh-safety

已收录

Safety harness plugin for DeepSeek Harness (DSH): execution-time guard, trash-based safe_delete, composition snapshots & rollback, pre-restart validation. Standalone CLI, zero dependencies.

main其他 查看源代码

安装

npm link # or: node bin/dsh-safety.mjs ...

此命令根据 GitHub 仓库地址生成。运行前请检查上游 README 与源代码;需要可复现安装时,请固定 release 或 commit。

README

维护者编写的文档快照。

在 GitHub 查看 ↗
提交版本 341114c同步于 2026年8月18日

dsh-safety

English | 中文

license   dependencies   node   npm version   npm downloads   github release   stars   last commit   ci

DeepSeek Harness 文件系统安全护栏:拦截破坏性操作 · 删除可恢复 · 组合可回滚 · 启动前校验
execution-time guard · safe_delete · composition snapshots · pre-restart check · standalone CLI

What

A filesystem safety harness for DeepSeek Harness (DSH). It enforces a three-tier file policy at the tool-execution boundary: destructive agent calls are denied before they run, every delete is routed through a recoverable trash, the plugin composition can be snapshotted and rolled back, and the composition is validated before a restart.

The package has zero runtime dependencies. It installs as a standard DSH profile bundle and also ships a standalone CLI, so the recovery layer remains usable when DSH itself will not start.

Background — the guard rules are derived from a real production incident: a script silently resolved the wrong path (PowerShell's $HOME is read-only) and Remove-Item -Recurse -Force deleted an entire engine runtime root. The directory was recoverable only because it was generated content; hand-authored files would have been lost permanently. The plugin turns the lessons of that incident into enforced mechanisms rather than documentation.

Features

  • Execution-time guard (ctx.tools.guard): denies destructive tool calls before they run.
    • Recursive directory deletes are blocked everywhere (rm -r/-rf, Remove-Item -Recurse, rd /s, rmdir, shutil.rmtree, fs.rm recursive, require('fs').rmSync…) — no matter which path, routed to safe_delete.
    • write/edit/str_replace_editor on protected paths (profile package.json, cordis.patch.yml, cordis.yml, lockfiles, node_modules, the deployment install dir, home patch/settings) are denied.
    • Deletes on confirm zones (the whole OS home dir, plugin sources, agent presets) are denied and routed to safe_delete.
    • run_code bodies are scanned too — arbitrary code execution cannot hide an fs.rmSync/shutil.rmtree on a protected zone behind a tool call boundary.
    • Variable-reference deletes are caughtRemove-Item "$env:USERPROFILE\.dsh\…" whose literal path only exists after expansion is denied (the reference + tail fragment is matched against protected markers).
  • safe_delete — the only sanctioned delete channel. Moves to a trash directory (recoverable via safety_undo), preview:true shows what would be removed first, refuses filesystem roots and its own state dir, and journals every delete.
  • Composition snapshotssafety_snapshot saves the whole plugin composition (per-profile manifests, patches, lockfiles, plugin package.json + cordis.patch.yml, agent presets) with SHA-256 hashes; safety_restore rolls back to a last-known-good state (current files are backed up first). Credential-bearing files are excluded by default.
  • Pre-restart checksafety_check validates UTF-8, detects mojibake (wrong-encoding round-trips, the classic "DSH won't open" cause), JSON parse errors, and duplicate plugin row ids across patch layers (the "one row, one layer" rule).
  • Audit journal + web panel — every block/delete/snapshot/restore is journaled; a "Safety Center" settings section shows trash, snapshots, journal, and one-click restore/rollback.
  • Standalone CLIdsh-safety works without DSH: delete/undo/snapshot/ restore/check from your own terminal, even when DSH won't boot.

Install

System requirements: a working DeepSeek Harness (dsh web boots). npm install has no extra requirements; installing from the repository needs Node.js >= 22 and pnpm.

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

dsh plugin runs pnpm and reconciles dsh.profile.bundles automatically because this package declares dsh.bundle. Restart dsh web — the guard is then active and the safety_* tools appear.

From the repository (development)

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)              # symlink the repo into the profile / 把仓库软链进 profile

The link: protocol symlinks the repo (changes to lib/ apply after a restart), unlike file: which copies a snapshot. dsh plugin reconciles the bundle automatically. Note: the profile directory is not a pnpm workspace, so any workspace:* deps would fall back to the npm registry — this plugin has zero runtime dependencies at all (its imports are only Node builtins + its own safety-core.mjs), so a bare link: install works with no node_modules of its own and no fallback is needed.

Where it lands (official layout)

Both installs go through the official dsh plugin mechanism — nothing else to configure:

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

The bundle layer is read at boot from the package's own cordis.patch.yml. The dsh-safety row id appears in exactly one layer (that file); never add it to the profile or home cordis.patch.yml.

Verify & uninstall

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

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

Install troubleshooting

  • Installed, restarted, but nothing changed: restart the whole dsh web process — a page refresh is not enough. Confirm the row is mounted with dsh --profile web --dump-config.
  • ERR_PNPM_IGNORED_BUILDS: pnpm blocks dependency build scripts; add the listed packages to pnpm-workspace.yaml allowBuilds and re-run.
  • pnpm release-age gate installs an old version: pnpm 11's minimumReleaseAge can silently pick an older publish within ~10 days; add minimumReleaseAgeExclude: ['@suagr_xl/dsh-safety'] to the profile's pnpm-workspace.yaml and run dsh plugin --profile web update @suagr_xl/dsh-safety.

Standalone CLI (no plugin install needed)

npm link   # or: node bin/dsh-safety.mjs ...
dsh-safety status

The CLI reads the same $DSH_HOME/.dsh-safety state the plugin uses, so you can undo/restore from your terminal even if DSH is down.

Quick start

# 1. Inspect the effective policy zones
dsh-safety policy

# 2. Snapshot before editing any composition file
dsh-safety snapshot before-edit

# 3. Delete through the safe channel (preview first, then execute)
dsh-safety delete path/to/file --preview
dsh-safety delete path/to/file

# 4. Recover a delete
dsh-safety trash
dsh-safety undo <trash-id>

# 5. Boot failure: validate, then roll back
dsh-safety check
dsh-safety status          # list snapshots
dsh-safety restore <snapshot-id> --confirm

CLI reference

dsh-safety status                  state: trash, snapshots, journal
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 on failure (CI-friendly)
dsh-safety journal [n]
dsh-safety policy                  effective policy zones
dsh-safety help

--home <path> overrides the state root ($DSH_HOME or ~/.dsh by default).

Model-facing tools (when installed as a plugin)

ToolPurpose
safe_deletetrash-based delete (preview / force / undoable)
safety_trash / safety_undolist trash / restore an item
safety_snapshot / safety_restoresnapshot composition / rollback (confirm:true)
safety_checkpre-restart validation (UTF-8 / mojibake / JSON / duplicate ids)
safety_journal / safety_statusaudit log / state

Configuration

Configure via the bundle row in a patch layer (e.g. the profile's 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
FieldDefaultMeaning
blockWriteRootsprofile manifests/patches/lockfiles/node_modules, install dir, home patch/settingsno write/edit/delete
confirmDeleteRoots$HOME, profiles/*, .agent-presetsno delete without force (still trash-only)
snapshotExclude["settings.yaml", ".credentials.yaml"]files never copied into snapshots
blockWritestrueenable the write/edit guard
blockShellDestructivetrueenable the shell-delete guard
audittruejournal destructive tool calls
keepTrash / keepSnapshots200 / 10retention limits

How it works

Three-tier policy:

TierAllowedDeniedDefault coverage
protectedreadwrite / edit / deleteprofile package.json/cordis.patch.yml/cordis.yml/lockfiles/node_modules, install dir, home patch & settings
confirmread, editdelete (needs safe_delete --force, still trash-only)entire $HOME, plugin sources, agent presets
freeread/write/deleterecursive deleteregular workspace files

The guard decision chain, per tool call: destructive verb? → is it a recursive delete? → does an explicit path hit a protected/confirm zone? → does a variable-reference fragment ($env:X\…, %X%\…, ${X}/…) expand into a protected zone? → does the command text hit a protected marker (~/relative forms)? → run_code code bodies go through the same chain → recursive deletes are denied everywhere as a final rule. Denials are journaled and returned to the model as errors (never a crash).

A second layer hooks the fs/write-intent / fs/edit-intent waterfalls and throws FS_DENIED on protected paths regardless of which tool writes.

buildPolicy lives in safety-core.mjs and is shared by the plugin guard and the standalone CLI, so the two surfaces can never drift apart. restoreSnapshot is transactional: it backs up live files first, then copies snapshot files back, and rolls the whole thing back if either phase fails — a failed rollback never leaves the composition half-restored.

Structure

dsh-safety/
├── bin/
│   └── dsh-safety.mjs        # standalone CLI (zero deps)
├── lib/
│   ├── safety-core.mjs       # pure logic: policy/guard/trash/snapshot/check
│   ├── index.js              # host half: tools, guard, fs hooks, web route
│   └── client.js             # browser half: "Safety Center" settings panel
├── test/
│   ├── safety.test.mjs       # 20 unit tests (zero deps)
│   └── harness.mjs           # 38 integration checks (clean checkout, zero deps)
├── cordis.patch.yml          # bundle patch (inserts the dsh-safety row)
├── package.json              # dsh.bundle + dsh.client + bin
├── install.ps1 / recover.ps1 # local convenience scripts (snapshot→install→verify→rollback)
├── README.md / README.zh.md  # docs (bilingual, officially paired)
└── LICENSE / NOTICE / SECURITY.md

Testing

node --test test/safety.test.mjs   # 20 unit tests, zero dependencies
node test/harness.mjs              # 38 integration checks, clean checkout (no @deepseek-ai needed)
npm run check                      # syntax checks

Troubleshooting

  • DSH won't boot after a plugin change: run dsh-safety check to find mojibake / JSON / duplicate-id problems; dsh --profile web --dump-default-config to see the bundle layer without the user layer; dsh-safety restore <id> --confirm to roll back a snapshot.
  • The guard blocks something legitimate: the guard never blocks reads or edits of plugin sources; it blocks deletes on $HOME/plugin/config zones — use safe_delete (undoable) instead of raw rm.
  • A protected path needs to be deleted: safe_delete with force:true (or dsh-safety delete --force) — the item still goes to trash, never permanent.

Security

See SECURITY.md. In short: the guard intercepts model tool calls, not commands you run in your own terminal; safety_check is a line-level scanner, not a full YAML parser. It is a safety net, not a sandbox — configure DSH's own sandbox/approval for real containment, and use this plugin for the recovery layer DSH lacks.

License

MIT. Integration patterns modeled after DeepSeek Harness (MIT); see NOTICE.

项目文件与信号

以下项目是目录快照中检测到的公开仓库信号。

测试已检测
安全策略已检测
贡献指南已检测
文档已检测

仓库信息

开发语言
JavaScript
许可证
MIT
最新发布
v0.1.1
最后更新
2026年8月18日 11:08

谨慎安装

请检查源代码、权限、生命周期脚本、依赖与网络访问;不受信任的插件应先在隔离环境中测试。