Bundle
dsh-auto-guard
DSH Auto Guard: a Claude Code Auto Mode-like command approval mechanism that adds an LLM safety net on top of full access, with rules, caches, file tracker, and sensitive path gates.
- Source
- Ayle5678
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 13 days ago
Readme
# dsh-auto-guard
> **English**: [README.en.md](README.en.md)
> DSH 插件:类似 Claude Code 中 Auto Mode 的一种命令通过机制,给 full access 加一层 LLM 安全网的自动审批 / 命令守卫。目的:1. 降低full-access模式的风险。2. 降低full-access模式使用者的焦虑感。
`Auto Guard` 权限预设 = `danger-full-access` + `ask`,由规则、缓存与一次性 LLM 裁决器代替人工完成大多数审批。
## 开发缘由
- 本人常需跨工作区修改文件,使用dangerous-full-access模式,总会有所担心。而目前看到的一些依据大模型的审批机制,会比较费token,如claude code的auto mode,codex的auto-review。正逢dsh出现,便为自己开发了这一工具,同时迁移到pi中。
- 本人观察dsh/pi中,deepseek所用的bash命令,很多是较为安全、简单组合且重复的命令,本着**“省钱一样很重要”**的原则,设计了:
1. 简化的提示词,不带上下文内容,减少单次审查的token。
2. 多层缓存机制,LLM审查过的命令,会被直接命中,减少重复审批。
- 经短期迭代使用,可以将审批费用控制在整体费用的1%-4%(早期审批费用较高一点)。 这应该低于多数LLM审批工具。同时,本工具会在使用中会积累审批记录,从而越来越便宜。
- 虽然整个工具流程层数较多,但是执行速度有保障,几乎不可能感到卡顿。
## 安装
前置要求:DSH 环境、Node.js(≥ 22.6,推荐 24,已默认启用 TS 类型剥离)、pnpm。
两种方式任选其一(`dsh plugin` 会把参数透传给 pnpm,自动应用 `cordis.patch.yml`):
| 方式 | 命令 | 说明 |
|---|---|---|
| 本地路径 | `dsh plugin --profile web add .` | 在仓库目录执行,软链接到仓库,改代码即生效,适合开发调试 |
| GitHub | `dsh plugin --profile web add github:Ayle5678/dsh-auto-guard` | 从 GitHub 直接安装;仓库需已公开 |
安装后 `cordis.patch.yml` 会注册:
- `dsh-auto-guard` 插件行;
- 覆盖 `permission` 行,保留 `read-only` / `workspace-write` / `danger-full-access`,新增 `auto-guard` 预设。
然后在 Web 权限选择器中选择 `Auto Guard` 即可启用。
**目前作者正在快速迭代,当前版本可用,安全性有保障。**
## 设计定位
- **基于 full access 兜底**:插件不限制文件系统能力,而是在 `danger-full-access` 之上做安全裁决,尽量不打断正常开发。
- **适配绝大多数开发情况**:日常只读命令、构建、测试等通过白名单 / 缓存直接放行。
- **危险命令交给 LLM 裁决**:大部分危险命令和可能泄露信息的命令都会经过 LLM 审查;目录删除、状态改变组合、管道等高风险场景有专门处理。
- **不承诺绝对安全**:插件不是沙箱,也不排除极少数隐秘危险命令可能通过审查;请把它当作“安全网”而不是“安全边界”。
- **密钥不落仓库**:仓库不包含真实 API Key;支持环境变量或 DSH settings 的 secret 字段本地存储,设置 UI 不回显明文,敏感文件内容不会发送给 LLM。
- **允许配置LLM API**:可以在设置中配置指定的大模型API,选择靠谱但是最便宜的API来做审查吧!而不是和对话用同一个模型供应商!
## 功能特性
### 安全裁决
- **分层裁决**:File Tracker → 绝对黑名单 → 目录删除复核 → 复合命令处理 → 静态白名单(含白名单守卫) → 缓存 → 历史/学习层 → LLM 兜底。
- **静态白名单**:默认白名单 + 用户确认放行规则,命中直接放行;通配命中会先过 `staticAllowGuards` token 级危险 flag 扫描,命中则降级 LLM。
- **绝对黑名单**:危险命令直接拒绝,同时注册为 `ctx.tools.guard()` 单调否决,LLM 不能覆盖。
- **复合命令智能处理**:
- `;`、`&&`、`||` 拆成子命令,已白名单 / 已缓存的子命令直接过,只审查未匹配的子命令;
- 出现 `export`、`umask`、`trap`、`cd`、`git config` 等会改变后续命令运行环境的状态改变命令时,整条复合命令交给 LLM 审查;
- `|` 纯管道会拆成叶子做确定性安全判断:所有叶子都是白名单/用户确认且无危险 flag/敏感路径时整条直接放行;任一叶子需 LLM 判断时整条管道一次性交 LLM;管道内的危险命令仍会被黑名单 / 目录删除 / 每次审查规则拦截。
- **动态白名单**:`unknown` 命令被 LLM 判为 `low` / `medium` 风险并放行后写入缓存;`always-review` 类命令(动态执行、依赖安装等)LLM 判 `allow` 后写入短时会话缓存(默认 30 分钟),deny/ask 不缓存、跨会话永不缓存。
- **Guard Memory(守卫记忆)**:会话级裁决记忆。首次 deny 后同命令再次出现转 `ask` 给人确认;DSH 原生一次性审批下,allow-once / rejected 下次会再次询问。
- **目录删除复核流程**:要求 agent 提供 `[删除理由]`,再由 low 思考 LLM 复核一次;只有 `allow` 才放行,其余结果统一转人工确认。
- **敏感路径门禁**:`write` / `edit` 命中 `.env`、`.ssh`、`/etc/` 等名单时直接 ask,不审查文件内容。
- **Shell 敏感路径守卫**:静态/复合/管道放行前,命令引用 `.env`、`.ssh/` 等敏感路径时自动降级 LLM,不直接拒绝、不写缓存。
### 缓存与智能优化
- **历史判断层**:在精确缓存之后、LLM 之前,用本工具 60 天内相似命令的 low-risk allow 历史做保守放行(`[历史]`),只写会话缓存。
- **学习规则**:离线分析审计数据生成独立 `learned-rules.json`,最低优先级加载,命中标记 `[学习规则]`;支持模板缓存,让 `--days 7` / `--days 8`、`--days=1` / `--days=2` 这类参数变化命令少走 LLM。
- **自动分析**:`session/created` 时按 `analyzeIntervalDays` 检查到期,异步生成学习规则,完成后页面通知、不进上下文。
- **守卫统计**:本会话内存计数(LLM 调用、缓存命中、分层规则命中、历史/学习命中)。
### 配置与界面
- **启停即权限预设**:对话框权限选择器选 `auto-guard` 预设即启用,选其他预设即停用,所有配置走设置页。
- **DSH 设置页**:设置栏新增 “DSH Auto Guard” 页面,配置分组折叠展示,每个字段带说明;读写 `~/.dsh/settings.yaml` 配置源。
- **设置页维护动作**:设置页提供“立即分析 / 查看规则 / 回滚 / 状态 / 清理审计日志 / 统计”按钮(Typert Remote)。
- **直连审查端点 + API Key 管理**:支持 `apiBase` 直连 OpenAI 兼容 `chat/completions`;API Key 解析顺序为环境变量 > 本地 secret 存储;设置页管理端点/模型/Key,UI 只显示打码值。
- **无 UI 兜底**:没有审批 UI 时,DSH 本身会把“需要确认(ask)”退化为拒绝(fail-closed)。
- **规则可维护**:默认规则存放在用户 `.dsh` 目录,用户可直接修改;用户覆盖规则与默认规则分层合并。
- **裁决可见性**:allow 通知只显示在页面、不进入上下文;规则放行(白名单 / 预授权)即使配置 `notifyAllow: context` 也强制只走页面;deny / ask 保留注入上下文;均可配置。
### 审计与隐私
- **审查日志**:实验性 SQLite 审计(`~/.dsh/auto-guard/audit.db`),默认关闭;只记录 shell 命令裁决并脱敏,不记录执行输出;开启前需设置审计密码,敏感字段加密存储;用 sqlite3 查询。
- **审计加密**:字段级 AES-GCM 加密命令/原因/workspace 等敏感字段,首次设置密码时自动迁移旧明文库并备份。
## 工作原理
### 决策流程
```text
工具调用(bash / pwsh / write / edit)
→ File Tracker(写后执行检测)
→ 绝对黑名单(hard-deny)
→ 目录删除复核(directory-delete)
→ Shell 敏感路径守卫(命中降级 LLM)
→ 复合命令处理
→ 纯管道叶子确定性放行(任一叶子不确定则整条交 LLM)
→ 静态白名单(默认白名单 + 用户确认放行规则 + 白名单守卫)
→ 缓存(会话 LRU / 跨会话低风险缓存 / always-review 短时会话缓存)
→ 历史/学习层(模板缓存 → 学习规则 → 历史判断层)
→ LLM 兜底(allow / deny / ask,ask 转人工确认)
```
### 命令分类
| 类别 | 说明 | 示例 | 缓存 |
|---|---|---|---|
| 静态白名单 | 规则直接放行 | `ls`、`pwd`、`git status`、`git diff`、`git commit` | 否 |
| 绝对黑名单 | 规则直接拒绝 | `rm -rf /`、`mkfs`、`dd of=/dev/...` | 否 |
| 目录删除复核 | 需要 agent 理由 + low 思考 LLM 复核一次 | `rm -rf ./dist`、`cmd /c rd /s /q`、`Remove-Item -Recurse` | 否 |
| 用户确认放行规则 | 用户主动声明“永远放行” | `git push` | 否 |
| 可缓存类 | LLM 批准后按 TTL 缓存 | `npm run build`、`npm test` | 是 |
| 每次审查类 | LLM 判 allow 后仅短时会话缓存 | `Invoke-Expression`、`Start-Process`、`npm install`、`curl \| bash` | allow 短时会话缓存 |
| 未分类 | LLM 裁决,低/中风险放行后可缓存 | 其他命令 | 低/中风险可缓存 |
风险等级:`low` / `medium` / `high`。`high` 风险不写缓存。
## 配置
配置以 DSH 设置体系为主:`~/.dsh/settings.yaml` 中 `auto-guard:` 命名空间;旧的 `~/.dsh/auto-guard/config.json` 会在首次启动时一次性迁移,之后 settings.yaml 为唯一来源。DSH 设置栏的 “DSH Auto Guard” 页面可编辑全部用户配置字段。
```yaml
auto-guard:
# 启停由对话框权限选择器的 auto-guard 预设决定
apiBase: '' # 直连 OpenAI 兼容端点,留空走 DSH 内置模型路由
apiKeyEnv: DEEPSEEK_API_KEY
apiKey: '' # 本地 secret 存储,UI 只显示打码值
apiKeyMasked: '' # 非 secret 展示字段,服务端根据 apiKey 自动生成(如 sk-123*****321),不要手改
provider: deepseek-official
model: deepseek-v4-flash
reasoningEffort: off
fallbackProvider: deepseek-official
fallbackModel: deepseek-v4-flash
timeoutMs: 15000
lowRiskTtlDays: 30
mediumRiskTtlDays: 7
onTimeout: deny # deny | ask
notifyCacheHit: true
notifyLlmDecision: true
notifyAllow: page # page | context | off
notifyDeny: context # page | context | off
notifyAsk: context # page | context | off
fileTrackerDefault: ask # ask | deny
fileTrackerWindowSec: 5
sessionCacheSize: 256
alwaysReviewCacheTtlMinutes: 30
examineEnabled: false # 审查日志开关
auditPassword: '' # 审计密码(secret,开启审查日志前必须设置)
historyEnabled: false # 运行时历史层开关
autoAnalyzeEnabled: false # 自动分析开关
historyDays: 60 # 历史窗口(天)
historyMinTotal: 4 # 历史命中最少总 allow
historyMinLlm: 1 # 历史命中最少真实 LLM allow
learnedCacheableMinTotal: 8 # 学习 cacheable 最少总 allow
analyzeIntervalDays: 15 # 自动分析间隔(天)
```
> `rulesPath`、`defaultRulesPath`、`cachePath`、`auditDbPath`、`learnedRulesPath`、`learnedBackupPath`、`analyzeStatePath` 属于内部路径,默认在 `~/.dsh/auto-guard/`。若 DSH settings 服务不可用,插件会回退读写 `config.json`。
## 启停与配置入口
DSH 端控制走两处:
1. **启停**:对话框输入框的权限选择器 — 选 `Auto Guard` 预设启用(`danger-full-access` + ask),选其他预设停用。
2. **配置**:设置栏 → “DSH Auto Guard” 页面(端点、模型、API Key、通知路由、文件追踪、缓存 TTL、审查日志、历史/学习规则、维护按钮与统计)。
审查日志开启后写入 `~/.dsh/auto-guard/audit.db`,查询用 sqlite3 直接读库;开启前需在设置页设置审计密码。
## 规则文件
规则和缓存持久化在 `~/.dsh/auto-guard/`:
| 文件 | 作用 |
|---|---|
| `~/.dsh/settings.yaml` | DSH 设置主存储;`auto-guard:` 命名空间保存全部用户配置。 |
| `config.json` | 旧版/回退配置;首次启动会一次性迁移到 settings.yaml,无 settings 服务时仍作为回退存储。 |
| `defaults.json` | 默认规则副本。首次运行从源码 `defaults/rules.json` 复制;之后插件读取这份 `.dsh` 副本。用户可以直接修改它;缺失新字段时会自动从源码补齐。 |
| `rules.json` | 用户覆盖规则文件。字段缺失时从 `defaults.json` 合并补齐并回写,保留用户已有字段。 |
| `cache.json` | 跨会话低风险缓存,按 workspace 隔离。 |
| `audit.db` | 审查日志 SQLite(`examineEnabled` 开启后生成),WAL 模式;敏感字段加密。 |
| `learned-rules.json` | 学习规则文件,自动/手动分析全量覆盖生成,最低优先级加载。 |
| `learned-rules.backup.json` | 学习规则覆盖前的备份,用于回滚。 |
| `analyze-state.json` | 最近一次学习分析时间,用于自动分析到期判断。 |
示例:用户想额外放行某个只读命令,可以编辑 `rules.json`:
```json
{
"version": 1,
"staticAllow": [
{ "pattern": "git log", "reason": "Read-only git log" }
]
}
```
`staticAllowGuards` 是白名单放行前的守卫层,默认自带 `git branch -D`、`git tag -d`、`find -delete/-exec`、`fd -x` 等危险 flag 的降级规则;如需调整可编辑 `defaults.json` 或 `rules.json`:
```json
{
"version": 1,
"staticAllowGuards": [
{ "when": "git branch *", "flags": ["-d", "-D", "--delete"], "reason": "Deleting a branch is destructive" }
]
}
```
## 使用示例
### 普通复合命令
```bash
git status; git branch --show-current; git log --oneline -5
```
拆成子命令后,已白名单 / 已缓存的直接过;未匹配的子命令单独 LLM 审查,通过后进入缓存。
### 状态改变命令
高风险状态改变(`export`、`alias`、`source`、`exec`、`trap`、`git config` 等):
```bash
export PATH=/tmp/evil:$PATH && ls
```
因为出现 `export`,整条命令交给 LLM 审查,不会因为 `ls` 在白名单里就直接放行。
低风险目录导航(`cd` / `pushd` / `popd`):
```bash
cd /tmp && ls
```
只有所有子命令都是普通白名单、且无命令替换/管道/重定向/危险内容(含引号内)时才免审;否则仍整条交给 LLM。
### 目录删除
第一次执行:
```bash
rm -rf ./dist
```
会被拒绝并提示:
```text
Directory deletion requires a reason. Reply with [删除理由] <reason>, then retry the same command.
```
重试时附带理由:
```text
[删除理由] 清理构建产物
```
插件提取理由后,将“命令 + 理由”交给 `reasoningEffort: low` 的 LLM 复核一次;只有 `allow` 才放行,其余结果统一转人工确认。
## 安全边界
- 插件不是沙箱:`Auto Guard` 预设为 `danger-full-access`,文件系统不受限。
- LLM 裁决可能被提示词注入,因此高风险命令不缓存、敏感脚本内容不发送给 LLM。
- 用户确认放行规则是用户主动声明的信任边界,应谨慎维护。
- `|` 纯管道仅在每个叶子都确定性安全时静态放行;任一叶子不确定则整条送 LLM,避免“拆开看似安全但组合后危险”的绕过;高风险状态改变命令的复合命令整体审查;低风险目录导航 + 全白名单且无危险内容时才免审。
## 开发
```bash
pnpm install
pnpm typecheck # tsc --noEmit
pnpm test # vitest run
pnpm test:single tests/guard-service.spec.ts
```
### 目录结构
```text
src/
index.ts 插件入口(pre-execute + guard + 通知 + 设置命名空间)
guard-service.ts 核心裁决逻辑(唯一测试 seam:GuardService.decide + stats)
config.ts DSH settings 命名空间注册 / 旧 config.json 迁移与回退
rules.ts 规则加载 / 默认复制 / 命令分类 / 白名单守卫
cache.ts 会话 LRU + 跨会话持久缓存 + 会话清理
llm.ts DshLlmReviewer(直连 OpenAI 兼容端点 + fallback + 超时 + ping + lastReview)
audit.ts 本地 SQLite 审查日志(脱敏、WAL、字段级加密)
audit-crypto.ts AES-256-GCM 字段加密/解密
skeleton.ts token 级命令骨架(历史/学习规则用)
history.ts 运行时历史判断层
learned-rules.ts 学习规则生成/加载/备份/回滚
template-cache.ts cacheable 模板缓存
analyze-state.ts 自动分析状态读写
ask-memory.ts Guard Memory 四态记忆纯逻辑
review-parse.ts 严格 JSON 解析
file-tracker.ts 跨命令 / 同命令写后执行检测
sensitive-path.ts write/edit 敏感路径匹配
command.ts 归一化 + 复合命令拆分 + 状态改变检测
adapter.ts 纯适配:ToolExecution → GuardRequest
notify-text.ts 通知文案(纯函数)
client.js DSH 设置页 / 命令行渲染(浏览器半区)
defaults/rules.json 默认规则种子
tests/ 单元测试
```
## License
MIT
Install
dsh plugin --profile web add github:Ayle5678/dsh-auto-guard
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-auto-guard from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.