Skip to content
dsh.fish
Bundle

deskpet-guard

Agent 行为守护桌宠:跨 agent 监控可疑外传(加密打包直传对象存储 / DNS 外泄线索 / 敏感密钥被读),发现即告警,明确确认后按「一次一个」终止目标 agent;事件写入只追加 guard-events.jsonl,并通过 DSH host 工具 + MCP(stdio) 供其它 agent 查询

Source
cny1230
License
BSD-3-Clause
Updated
Updated 5 days ago

Readme

# deskpet-guard 🐾

> **Agent 行为守护桌宠** —— 跨 agent 监控可疑外传行为(加密打包直传对象存储 / 敏感密钥被触达 / DNS 外泄线索),
> 发现即告警,在**明确确认后**才终止目标 agent 进程;事件写入只追加的 `guard-events.jsonl`,
> 并通过 **DSH host 工具** 与 **MCP(stdio)** 供其它 agent 查询。
>
> *A desktop-pet-shaped guardian for AI agent exfiltration: read-only probing, rule-based judgement,
> append-only event log, two-step human-confirmed process termination (one target per confirmation),
> exposed to other agents via DSH host tools and an MCP stdio server.*

| | |
|---|---|
| 形态 | DSH 插件(host 工具 + 面板/桌宠)· 独立 CLI · MCP server |
| 平台 | Windows(探针基于 `Get-*`;其它平台会显式上报"能力降级",不假装安全) |
| 依赖 | **运行时零依赖**(只用 Node 内置),不用装构建工具链就能跑 |
| 许可 | BSD-3-Clause |
| 测试 | 全离线套件,不联网、不杀进程、不需要 DSH |

![CI](https://github.com/cny1230/deskpet-guard/actions/workflows/ci.yml/badge.svg)

![面板与桌宠](docs/assets/deskpet-panel.png)

---

## 目录

- [它是什么 / 它不是什么](#它是什么--它不是什么)
- [缘起:为什么做这个](#缘起为什么做这个)
- [快速开始](#快速开始)
- [规则集](#规则集)
- [面板与桌宠](#面板与桌宠)
- [给其它 agent 用(MCP)](#给其它-agent-用mcp)
- [扩展到一个新 agent](#扩展到一个新-agent)
- [架构与实现](#架构与实现)
- [测试](#测试)
- [已知限制(读这里再决定要不要用)](#已知限制读这里再决定要不要用)
- [安全](#安全)

---

## 它是什么 / 它不是什么

**是**:一个**只读侦察 + 分级告警 + 一步确认后处置**的小守护。它回答的问题是
"本机现在有没有 agent 正在做可疑外传动作",并把答案做成**可被其它 agent 查询的公共事件源**。

**不是**:防火墙。用户态**无法阻断**另一个进程的 TLS 出网 —— 能阻断的三条路
(杀进程 / 防火墙与 WFP / 内核级文件拦截)分别是"会打断用户会话""要管理员且改系统""不现实"。
详见 [SECURITY.md](SECURITY.md)。

> 一句话:**它是案发现场的公告板,外加一支需要人点头的手。**

## 缘起:为什么做这个

对某桌面 AI 客户端(ZCode v3.11.2)做过一次完整只读取证,结论是它**确实存在"工作区打包加密后直传阿里云 OSS"的通道**:

| 证据层 | 内容 | 位置 |
|---|---|---|
| 打包产物 | 3 个 `.tar.gz.enc`(最大 **25.7 MB**),含知识库全量 16,917 文件 | `~\.zcode\v2\checkpoints\<id>\pending\` |
| 上传意图 | 明文 `pendingUpload` / `activeUpload` / `uploadCredentialHandle` / `kind:"baseline"` | 同目录 `state.json` |
| 上传实现 | `uploadOssForm` / `buildOssFormFields`,构造 `x-oss-signature` / `x-oss-credential` / `policy`(阿里云 OSS PostObject + STS 直传) | 客户端 `app.asar` |
| 凭证来源 | 服务端接口 `/api/v1/snapshot/upload-credential`;上传器日志名 `repo-snapshot-upload` | 同上 |
| 加密方式 | `tar.gz` → `aes-256-ctr`,密钥 `rsa-oaep-sha256` 包裹 | `pending\*.envelope.json` |

**未证实**的部分(必须保留在结论里,别当既成事实):本机 3 个包**全部滞留在 `pending\`**,
`failureCount` 12/222/20,DNS 缓存中 **0 条 OSS 桶域名**,采样时该客户端出网连接 **0**。
即"**能力确证 + 已打包 + 上传未成功(本机证据)**",不是"已确认泄露"。

**关键设计推论**:与其做"某客户端专杀",不如做**通用 agent 行为守护** ——
① 这类行为不是某一家独有;② 用户要求"其它 agent 也能搜到并使用";③ profile 化画像表可覆盖任意 agent。

> 免责:本项目与上述厂商无任何关联,不含任何厂商代码,全部证据来自本机只读观察,用于防御目的。

## 快速开始

### 方式一:装进 DSH(插件形态,推荐)

**标准安装(推荐,等同插件市场的"一键装")**:本包带 `cordis.patch.yml`(`dsh.bundle.patch`),
所以既能从仓库装,也能在支持的市场里一键装:

```bash
# 仓库直装(pnpm 会把本包装进 web profile,并按 cordis.patch.yml 插入 loader entry)
dsh plugin --profile web add github:cny1230/deskpet-guard

# 按包名装(npm 发布后;包名就是 deskpet-guard,不带 scope)
dsh plugin --profile web add deskpet-guard

# MCP 客户端:npx deskpet-guard
```

**注入器安装(开发/热重载用,免重启)**:

```bash
git clone https://github.com/cny1230/deskpet-guard.git D:/deskpet-guard
# 在 DSH 会话里:
#   dev_inject_plugin {"dir":"D:/deskpet-guard"}      # 注入
#   dev_uninject_plugin {"match":"deskpet-guard"}     # 卸载
```

> ⚠️ 两条路径**选一条**:同时用会让插件被加载两次(工具重名、面板重复)。
> 从注入器切到标准安装前,先 `dev_uninject_plugin`。

装好后你会得到:

- 5 个 host 工具:`guard_status` / `guard_events` / `guard_scan` / `guard_prepare_kill` / `guard_confirm_kill`
- 右下角常驻**桌宠**(`shell.overlay`)+ 会话侧栏**详情面板**(`conversation.view`)
- 本地 HTTP API:`GET /deskpet-guard/api/status|events`、`POST /deskpet-guard/api/scan|prepare-kill|confirm-kill`
- MCP server:`npx deskpet-guard`(npm 发布后)或 `node <repo>/bin/deskpet-guard-mcp.js`

配置(`apply(ctx, config)` 的 config):

```jsonc
{
  "dataDir": "D:/guard-data",     // 默认 $DSH_HOME/super-injector/deskpet-guard
  "intervalMs": 15000,            // 采样周期,最小 5000
  "killMode": "audit",            // "execute"(默认) | "audit" —— audit = 只要预警,拒绝任何终止
  "profiles": [ /* 追加/覆盖 agent 画像,见下 */ ]
}
```

### 方式二:CLI(不装 DSH 也能用)

```bash
node lib/index.js              # 人读的一次采样
node lib/index.js --json       # 结构化输出(含候选处置目标)
node lib/index.js --status     # 最近一次落盘状态
node lib/index.js --events 20  # 最近 20 条事件
node lib/index.js --endpoint   # host API 端点(面板/MCP 用)
```

### 方式三:MCP(给别的 agent 用)

```bash
node bin/deskpet-guard-mcp.js        # stdio MCP server
node lib/mcp.js --list               # 看看工具清单与数据目录
```

客户端配置片段与降级语义见 **[docs/MCP.md](docs/MCP.md)**。

## 规则集

| 规则 | 触发条件 | 级别 | 建议 |
|---|---|---|---|
| `R0-probe-degraded` | 探针不可用 | medium/high | 告警(**防止"假安全"**) |
| `R1-agent-to-object-storage` | agent 进程连向 OSS/S3/COS。远端拿到的是 **IP**(`Get-NetTCPConnection`),会先用 DNS 缓存反查成域名再匹配;画像也支持直接写 IPv4/CIDR | **critical** | **建议终止**(两步确认,执行前有 PID 复用验证) |
| `R2-fresh-bundle-artifact` | 10 分钟内出现打包产物 | high | 告警 |
| `R3-bundle-burst` | 5 分钟内成簇刷包 ≥3 | high | 告警 |
| `R4-secret-file-touched` | 密钥/凭据文件最近被写(mtime 只能反映"写",测不到"读",见已知限制) | medium | 告警 |
| `R5-dns-object-storage-residue` | DNS 缓存残留对象存储域名 | info | 线索(**非外传证据**) |
| `R6-agent-alive-quiet` | agent 存活 + 当前外发连接数 | info | 状态显示 |
| `R7-upload-intent-marker` | indexFiles 指向的状态文件里出现上传通道标记(`pendingUpload` / `uploadOssForm` 等,画像的 `uploadPathPatterns`) | high | 告警("已表达上传意图",比"只打包了"更进一层) |

`moodOf()` 把 findings 归一为桌宠表情:`watching`(安静)/ `alert`(中高)/ `panic`(critical)。

**防误报口径**:`egressHostPatterns` 只放"对象存储/文件托管"类域名。
反例:`*.log.aliyuncs.com` 是阿里云**日志服务**,实测出现在本机 DNS 缓存里,
但**不能**据此判定"工作区被上传" —— `test/rules.test.mjs` 有专门用例锁死这条。

**事件流防刷屏口径(0.3.0 起)**:同一现象(指纹)落库后默认冷却 1 小时(`DEDUP_COOLDOWN_MS`),
冷却期内不重复刷事件但计数照加;冷却期后再次出现会记一条 `occurrence` 递增的**复发**事件 ——
"同一目标反复外传"必须留痕,而不是像旧版那样永久压制。事件文件超过 5MB 改名归档为
`guard-events.jsonl.1`(改名而非删除,历史段仍完整可读),读取只取尾部 512KB。

**多实例口径(0.3.0 起)**:同一数据目录下多个实例(DSH host + 多个 MCP 客户端)用锁文件选主,
只有 leader 周期采样,其余降级为只读跟随者(工具/API 仍可用,读 leader 落盘的数据);
心跳停更超过 3 个采样周期视为前任已死,自动接管。

## 面板与桌宠

面板挂在两个 DSH client slot 上(都是宿主白名单内的合法 slot):

| slot | 形态 | 内容 |
|---|---|---|
| `shell.overlay` | 右下角常驻桌宠 | 表情(`( ◕‿◕ )` / `( ◉_◉ )` / `( ✖﹏✖ )`)+ 一句话结论 + 新事件角标,点击展开最近事件 |
| `conversation.view` | 会话侧栏详情面板 | 统计芯片、探针降级警示(R0)、候选处置目标(**两步确认,一次一个**)、最近 20 条事件流 |

![alert 态:探针降级如实上报,不装安全](docs/assets/deskpet-panel-alert.png)

- 数据面是本地 HTTP API(`lib/api.js`),3 秒轮询,两个 slot 共用一条轮询;
  处置流程的状态挂在闭包上 —— **轮询重画不会冲掉进行中的两步确认**(有回归用例锁)
- 所有系统字符串(进程名/路径/域名)都用 `textContent` 写入 —— **不用 `innerHTML`**,
  否则一个精心命名的进程就能在面板里注入标记(有测试盯着这条)
- 面板是**手写 bundle**(`lib/client.js`,DSH `ModuleLoader` 约定),不需要打包器 —— 见 [架构](#架构与实现)
- 上面两张图来自 `tools/panel-preview.html`(**离线预览 harness**:加载的是真实交付物
  `lib/client.js`,但宿主 slots 服务与 `fetch` 是 stub、数据为预览样例)。
  用任意浏览器打开即可肉眼验收面板,不必装 DSH:

  ```bash
  start "tools/panel-preview.html?mode=panic&step=2"   # step=2 会把两步确认展开
  start "tools/panel-preview.html?mode=alert"
  ```

## 给其它 agent 用(MCP)

`guard_status` / `guard_events` / `guard_scan`(只读)+ `guard_prepare_kill` / `guard_confirm_kill`(变更)。
MCP 进程与 host 是两个进程,所以:

- 优先走 host 的本地 HTTP API(token 在 host 里签发/核销,单一事实源)
- host 不可达时 **只读工具降级本地**(读落盘快照 / 本进程采样)
- host 不可达时 **终止类工具明确拒绝** —— 绝不本地签一个 host 不认的 token

细节与给 agent 的系统提示模板:[docs/MCP.md](docs/MCP.md)。

## 扩展到一个新 agent

只改 `lib/profiles.js`(TS 镜像 `src/guard/profiles.ts` 同步),**不用碰规则引擎**:

```js
{
  id: 'my-agent',
  label: 'MyAgent',
  processPattern: 'myagent(\\.exe)?$',                      // 进程名/路径正则
  dataRoots: ['{home}\\AppData\\MyAgent'],                   // 该 agent 的数据根
  bundlePatterns: ['\\.bundle$'],                            // 打包产物文件名正则
  indexFiles: [],                                            // 工作区映射文件
  secretFiles: ['{home}\\AppData\\MyAgent'],                 // 密钥/凭据文件或目录
  egressHostPatterns: ['upload\\.myagent\\.example\\.com'],  // 外发目标
  uploadPathPatterns: [],
}
```

运行时追加也可以(不改代码):`apply(ctx, { profiles: [ {...} ] })`。
`test/rules.test.mjs` 里有一条用例专门证明"加一条画像即可生效、无需改规则代码"。

## 架构与实现

```
lib/profiles.js   agent 画像表(跨 agent 扩展点:新增 agent 只加一条定义)
lib/rules.js      规则引擎(纯函数,可离线单测)
lib/probe.js      只读探针(Windows: PowerShell Get-* / Test-Path)
lib/events.js     事件流(唯一写盘点:只追加、只写自己的数据目录)
lib/api.js        本地 HTTP API(纯函数分发,可离线单测;三重闸门)
lib/toolkit.js    工具定义适配层(defineTool 等价形状,零依赖)
lib/index.js      核心库 + CLI + DSH 插件入口(apply)
lib/client.js     面板/桌宠 bundle(手写,DSH ModuleLoader 约定,无需构建)
lib/mcp.js        MCP stdio server(JSON-RPC 2.0,桥接 HTTP API)
lib/agentid.js    agent 归属层(进程名/路径/cmdline/画像 → 哪个 agent)
lib/pet.js        桌宠窗口(PowerShell + WinForms,跨 agent 各一只)
lib/sampler.js    独立采样守护(0.4.0):锁选主 + 自托管 HTTP API + endpoint.json,
                  DSH 不在时它就是唯一采样者(解决"DSH 关了快照过期")
assets/dashboard.html 桌宠看板页面(0.4.0):接真实 API 的正式 UI,守护托管、桌宠窗口同载
assets/           桌宠形象图(mascot-*.png,随包发布;换图 = 同名覆盖)
bin/…-mcp.js      MCP 可执行入口
bin/…-sampler.js  独立采样守护入口(--stop / --status)
src/guard/*.ts    TS 权威源码(类型),与 lib/*.js 由 consistency 测试锁死一致
tools/…preview.html  离线预览 harness(浏览器里肉眼验收面板,无需 DSH)
test/*.mjs        15 个离线套件;test/dom-shim.mjs 是跑面板用的极简 DOM
```

### 为什么有"手写运行时 + 无构建面板"

开发环境里**没有 DSH 源码检出**(只有 pnpm 打包版),`tsc` 与 `tsdown` 都装不上。
于是核心逻辑做成**零依赖纯 JS**(`lib/*.js`),并把面板也写成手写 bundle:
`tsdown.config.ts` 与占位用的 `src/client/index.ts` 已删除 —— 它们存在的唯一效果就是
"跑一次 `npm run build:client` 把真正的面板覆盖回一行文字"(和上面那个坑同一类)。
`src/guard/*.ts` 保留为带类型的权威源码,两侧用 `test/consistency.test.mjs` 锁死语义一致。
若日后接入构建工具链:把 `lib/client.js` 迁回 `src/client/index.ts` 并重建 tsdown 配置,
同时把 `test/client-bundle.test.mjs` 的产物检查指向新输出。

### ⚠️ 一个真实踩过的坑(保留在仓库里当路标)

`dev_scaffold_plugin` 生成的模板落在 `src/index.ts`,而 `tsconfig` 是
`rootDir: src` / `outDir: lib` —— 一旦跑 `tsc`,它会把 **`lib/index.js`(真正的守护实现)
覆盖成一个只会读 `self-heal.log` 的 LLM 自省循环**,整套探针/规则/终止链路当场消失。

现在有三道防线:

1. 模板挪到 `src/daemon/index.ts`(并改名,不再是宿主入口)
2. `tsconfig` 的产物目录改为 `build/`
3. `scripts/build.sh` 里有硬守卫:`outDir=lib` 或 `src/index.ts` 存在即**拒绝构建**

`test/` 里的 `client-bundle.test.mjs` 也会检查 `package.json` 的入口/类型/bin 指针**不悬空**
(第一版就犯过"`exports["./client"]` 指向一个不存在的 `lib/client.js`")。

## 桌宠:怎么开、怎么关

桌宠是**独立桌面窗口**(Windows / PowerShell + WinForms,零依赖),**全机只有一只**
(0.4.1 起:不管开着几个客户端,后到的拉起请求都会让位;在跑的还是旧版文本宠时,
新的拉起会把它升级成带看板页的 pet2)。
它不在客户端 UI 里渲染——因为 ZCode / Claude Code 这类 agent 的插件 API 只提供
skills / commands / agents / hooks / MCP,**没有"往界面插悬浮组件"的能力**。

**形象跟谁**:看板页的"自动"模式跟随**最早启动且还在跑**的 agent(按进程创建时间判断),
它退出后自动落到下一个还在跑的 agent;也可以在形象下拉框里钉死某个 agent。

**自动拉起**:

| 场景 | 机制 |
|---|---|
| DSH | 宿主插件在第一次 tick 时拉起 |
| ZCode | 插件 `hooks/hooks.json` 的 `SessionStart`(刷新市场后生效) |
| 任何 MCP 客户端 | 它的 MCP server 启动时顺带拉起(幂等:已有一只就跳过) |

**0.4.0 起还有一层保障**:桌宠/MCP 启动时都会幂等拉起**独立采样守护**
(`node bin/deskpet-guard-sampler.js`,锁选主与 DSH 宿主协调,同一时刻只有一家采样)。
DSH 关掉后守护自动接管采样并托管本地看板 —— 各 agent 的桌宠不再出现"快照过期"。
守护与 DSH 宿主并存时,先到者采样、后来者跟随;`deskpet-guard-sampler --stop` 让位(10 秒内接管)。

**手动开/关**(随时可用,不必等客户端):

```bash
node bin/deskpet-guard-pet.js                   # 开:桌宠(全机一只;已开着就跳过)
node bin/deskpet-guard-pet.js --client zcode    # 同上(--client 只用作日志/来源记录)
node bin/deskpet-guard-pet.js --status          # 只看文字状态,不开窗
node bin/deskpet-guard-pet.js --list            # 桌宠在不在、来源(全局锁/进程发现)
node bin/deskpet-guard-pet.js --stop            # 关掉桌宠(也清历史遗留的 per-client 锁)
```

也可以**右键桌宠 → "退出桌宠"**直接关掉。Windows 上可以**双击仓库根目录的
`start-pet.cmd`**(可带参数,如 `start-pet.cmd --client zcode`)。

**手动关掉之后**:不会自己弹回来(关闭是你的明确意图,宿主不会跟你抢)。
想再开就上面任意一条命令 / 双击 `start-pet.cmd`;ZCode 侧重启 ZCode 也会重新拉起。

> ⚠️ `start-pet.cmd` **必须保持纯 ASCII**:批处理按控制台代码页读取,GBK 双字节字符的
> 尾字节可能撞上 `&`/`|`,cmd 会把后半行当命令执行(踩过,报错很迷惑)。测试里有守卫。

## 测试

```bash
npm test        # 15 个套件 / 213 例(= node test/all.mjs;离线:不联网、不杀进程、不需要 DSH、不需要 Windows)
npm run check   # 交付 JS 的语法自检(本仓库没有编译步骤兜底)
node test/panel-ui.test.mjs   # 只跑面板行为(DOM shim 里挂真面板)
node test/mcp-stdio.test.mjs  # 只跑 MCP 传输层(单请求单响应 / close 不吞响应)
```

| 套件 | 关注点 |
|---|---|
| `rules.test.mjs` | 规则判定 / 防误报(SLS 日志域名反例)/ 画像扩展性 |
| `kill.test.mjs` | 处置链路安全:无确认不可能误杀、token 一次性、**一次确认只杀一个**、执行前 **PID 复用验证**(进程名+创建时间对不上即拒绝) |
| `consistency.test.mjs` | `src/guard/*.ts` 与 `lib/*.js` 双实现一致性 |
| `api.test.mjs` | HTTP API 与鉴权边界:预检拒绝、跨源拒绝、secret 路径、body 边界 |
| `plugin.test.mjs` | 假 ctx 跑 `apply()`:工具真注册(走 `ctx.effect`)、API 真挂载、两步链路连通、audit 必拒、**第二实例降级为只读跟随者** |
| `mcp.test.mjs` | JSON-RPC 往返 + **工具名/HTTP 路径跨文件一致** + host 不可达的降级语义 |
| `mcp-stdio.test.mjs` | 传输层:单请求单响应(防双 server 抢 stdin)、close 不吞最后一个响应、stdout 纯净 |
| `client-bundle.test.mjs` | 面板产物约定、slot 白名单与注入器自检正则、API 路径与 host 一致、不用 `innerHTML`、入口指针不悬空、全量语法检查 |
| `panel-ui.test.mjs` | 在 DOM shim 里跑**真面板**:渲染、两步确认(第一步不执行)、**轮询不冲掉确认区**、取消、API 不可达、dispose 停轮询 |
| `events.test.mjs` | 事件流:冷却去重与复发计数、大小轮转归档、真 tail 读取(大文件不整载、行不残缺)、Set 兼容 |
| `probe.test.mjs` | 探针纯函数:PS 5.1 JSON 归一化(单元素/BOM/垃圾前缀/截断判降级)、secretFiles 通配拆分 |

## 已知限制(读这里再决定要不要用)

1. **沙箱内探针会被拒**:受限沙箱禁止程序打开命名管道,抓 PowerShell 输出会失败,
   三个系统探针(进程 / TCP / DNS)全挂 → `R0` 上报降级。**文件类探针不受影响**
   (就是它在沙箱里扫出了那 3 个 `.tar.gz.enc`)。要看进程/连接侧,请在**你自己的终端**里跑。
2. **不是防火墙**(见上)。
3. **只在 Windows 上有完整探针**;其它平台会明确上报 `unsupported`。
4. **画像表**:zcode / dsh / cursor / claude-desktop 四条。zcode 与 dsh 桌面端有本机实证
   (dsh 桌面端 = Electron 应用 `DeepSeek Harness.exe`,user-data 目录
   `AppData/Roaming/@deepseek-ai/dsh-desktop`;其上传链路未逐一实证,外发画像沿用同厂
   OSS/S3/COS 口径)。cursor / claude 仅有基础画像 —— 不懂的 agent 就是没有画像,这是待办。
5. **文件名匹配是启发式**:打包产物靠文件名/扩展名识别,改名可绕过。
6. **没有基线学习**:新装的 agent 一律按陌生进程处理,误报会随安装量上升。
7. **`killMode:'execute'` 会真的杀进程**(需两步人工确认 + 一次一个)。生产环境建议先用 `audit` 跑一段时间。
8. **R1 的域名反查依赖本机 DNS 缓存**:如果目标 agent 走加密 DNS(DoH)或自建解析,缓存里可能
   没有对应记录 → 反查不到 → R1 漏报(此时 R5 也不会有线索)。对已知固定出口,画像里直接写
   IP/CIDR 可以兜底。另外反查基于"IP → 曾解析过的域名",CDN 共享 IP 理论上可能串出误报 ——
   所以 R1 的 evidence 永远带 `resolvedHost`(实际命中的域名),人工复核一眼可辨。
9. **R4 实际只测"写"不测"读"**:mtime 不反映读取行为(Windows 默认还禁用 last-access 更新)。
   要测"密钥被读"需要 ETW/Sysmon 一类内核遥测,目前不做(见 Roadmap)。

## 安全

- 探针只读;不写被监控 agent 的任何目录;不碰防火墙 / hosts / 注册表 / 证书存储;host 侧不出网
- 事件流**只追加**
- 变更端点三重闸门(自定义头 + 同源 + JSON,或 0600 secret);预检一律 403,永不发 CORS 头
- 终止必须两步确认、token 一次性、**一次一个目标**

完整模型与已知弱点:[SECURITY.md](SECURITY.md)。漏洞请走私密渠道(Security advisory),不要开 public issue。

## 状态 / Roadmap

- [x] 只读探针 + 规则引擎 + 只增事件流 + CLI
- [x] DSH host 工具(5 个)+ 本地 HTTP API + 面板/桌宠
- [x] MCP stdio 暴露层
- [ ] 基线/白名单学习(降低误报)
- [ ] 更多 agent 画像与实证
- [ ] 系统托盘通知 / 声音
- [ ] macOS / Linux 探针

## 发布状态(能不能在插件市场里搜到)

| 渠道 | 状态 | 说明 |
|---|---|---|
| GitHub 仓库 | ✅ 已发布 | <https://github.com/cny1230/deskpet-guard>(public,CI 绿) |
| `dsh plugin add github:…` | ✅ 可用 | 本包带 `cordis.patch.yml` + `dsh.bundle.patch`,标准安装路径能装 |
| GitHub topic `dsh-plugin` 等 9 个 | ✅ 已打 | 已被 GitHub 搜索索引;`topic:dsh-plugin` 下有 1.5 万仓库且按 star 排序,0★ 新仓库不会出现在列表首页,但按名字搜得到 |
| npm 包 | ✅ **已发布** | [`deskpet-guard@0.1.0`](https://www.npmjs.com/package/deskpet-guard)(BSD-3-Clause,maintainer `cny1230`)。实测:`npx -y deskpet-guard` 从 npm 拉下来跑通 MCP(initialize + 5 个工具);也可 `dsh plugin --profile web add deskpet-guard` |
| dsh.so 收录 | ✅ **已提交两次** | 用它自己的提交页跑完 checker 后点 `Submit to dsh.so` → `POST /api/submit` HTTP 200、页面 `✓ Submitted`(第二次起条目里已带上 `npm.packageName`)。它的公开 artifact 页 `/artifact/deskpet-guard/` 仍在等它静态重建(15,459 → 尚未 +1) |

**下一步(可选,非阻塞)**:

1. 未来版本发布建议改用 [Trusted publishing (OIDC)](https://docs.npmjs.com/trusted-publishers):
   由 GitHub Actions 用 OIDC 发布、不存凭据 —— 因为 npm 已宣布 **2027-01 起 bypass-2FA token 不能再直连发布**;
   本仓库已有 CI,加一个 `release.yml` + 在 npm 上把 trusted publisher 指到 `cny1230/deskpet-guard` 即可;
2. dsh.so 的 artifact 页要等它自己的重建周期(条目已在其后台)。

用法汇总:**GitHub 直装**(`dsh plugin --profile web add github:cny1230/deskpet-guard`)、
**npm 装**(`dsh plugin --profile web add deskpet-guard`)、**MCP**(`npx deskpet-guard`)。
逐步操作与自检清单见 **[docs/PUBLISHING.md](docs/PUBLISHING.md)**。

## 变更记录

### 0.3.0(2026-09-28)—— 检测主链路修复 + 处置安全加固

- **R1 修复(关键)**:远端 IP 先经 DNS 缓存反查成域名再匹配 `egressHostPatterns` ——
  修复前 R1 拿 IP 比域名正则,**真实环境永不命中**(测试夹具把域名塞进了 IP 字段,
  把错误假设锁进了测试)。`egressHostPatterns` 同时支持 IPv4 字面量 / CIDR。
- **新增 R7 上传意图标记**:indexFiles 指向的状态文件里出现 `pendingUpload` / `uploadOssForm`
  等明文标记 → high(画像的 `uploadPathPatterns` 字段首次接入规则引擎)。
- **处置安全**:confirm 执行前做 PID 复用验证(进程名 + 创建时间与确认时不一致即拒绝,
  防 10 分钟窗口内 PID 被回收复用误杀无辜进程);kill 计划列出目标子进程(仅展示)。
- **事件流**:去重从"永久压制"改为"冷却期 + 复发计数";超 5MB 轮转归档;读取改真 tail。
- **多实例**:同一数据目录单采样者锁,其余实例降级为只读跟随者。
- **修复**:`underDataRoot` 前缀越界误匹配(`.zcode-v2` 不再认成 `.zcode`);
  cursor 画像 `secretFiles` 通配静默失效;探针输出损坏时判为降级(R0)而非空数据;
  R4 的目录匹配统一转义口径(与 TS 镜像一致)。

### 0.2.8 及以前

见 `git log`。

## 许可

[BSD-3-Clause](LICENSE)。

Install

dsh plugin --profile web add github:cny1230/deskpet-guard

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source