deskpet-guard
Verifieddeskpet-guard · v0.2.8 · BSD-3-Clause · Web UI
Agent 行为守护桌宠:跨 agent 监控可疑外传(加密打包直传对象存储 / DNS 外泄线索 / 敏感密钥被读),发现即告警,明确确认后按「一次一个」终止目标 agent;事件写入只追加 guard-events.jsonl,并通过 DSH host 工具 + MCP(stdio) 供其它 agent 查询
Install
dsh plugin add deskpet-guard Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
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 |

目录
它是什么 / 它不是什么
是:一个只读侦察 + 分级告警 + 一步确认后处置的小守护。它回答的问题是 "本机现在有没有 agent 正在做可疑外传动作",并把答案做成可被其它 agent 查询的公共事件源。
不是:防火墙。用户态无法阻断另一个进程的 TLS 出网 —— 能阻断的三条路 (杀进程 / 防火墙与 WFP / 内核级文件拦截)分别是"会打断用户会话""要管理员且改系统""不现实"。 详见 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),
所以既能从仓库装,也能在支持的市场里一键装:
# 仓库直装(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
注入器安装(开发/热重载用,免重启):
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):
{
"dataDir": "D:/guard-data", // 默认 $DSH_HOME/super-injector/deskpet-guard
"intervalMs": 15000, // 采样周期,最小 5000
"killMode": "audit", // "execute"(默认) | "audit" —— audit = 只要预警,拒绝任何终止
"profiles": [ /* 追加/覆盖 agent 画像,见下 */ ]
}
方式二:CLI(不装 DSH 也能用)
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 用)
node bin/deskpet-guard-mcp.js # stdio MCP server
node lib/mcp.js --list # 看看工具清单与数据目录
客户端配置片段与降级语义见 docs/MCP.md。
规则集
| 规则 | 触发条件 | 级别 | 建议 |
|---|---|---|---|
R0-probe-degraded |
探针不可用 | medium/high | 告警(防止"假安全") |
R1-agent-to-object-storage |
agent 进程连向 OSS/S3/COS | critical | 建议终止(两步确认) |
R2-fresh-bundle-artifact |
10 分钟内出现打包产物 | high | 告警 |
R3-bundle-burst |
5 分钟内成簇刷包 ≥3 | high | 告警 |
R4-secret-file-touched |
密钥/凭据文件最近被写 | medium | 告警 |
R5-dns-object-storage-residue |
DNS 缓存残留对象存储域名 | info | 线索(非外传证据) |
R6-agent-alive-quiet |
agent 存活 + 当前外发连接数 | info | 状态显示 |
moodOf() 把 findings 归一为桌宠表情:watching(安静)/ alert(中高)/ panic(critical)。
防误报口径:egressHostPatterns 只放"对象存储/文件托管"类域名。
反例:*.log.aliyuncs.com 是阿里云日志服务,实测出现在本机 DNS 缓存里,
但不能据此判定"工作区被上传" —— test/rules.test.mjs 有专门用例锁死这条。
面板与桌宠
面板挂在两个 DSH client slot 上(都是宿主白名单内的合法 slot):
| slot | 形态 | 内容 |
|---|---|---|
shell.overlay |
右下角常驻桌宠 | 表情(( ◕‿◕ ) / ( ◉_◉ ) / ( ✖﹏✖ ))+ 一句话结论 + 新事件角标,点击展开最近事件 |
conversation.view |
会话侧栏详情面板 | 统计芯片、探针降级警示(R0)、候选处置目标(两步确认,一次一个)、最近 20 条事件流 |

数据面是本地 HTTP API(
lib/api.js),3 秒轮询,两个 slot 共用一条轮询; 处置流程的状态挂在闭包上 —— 轮询重画不会冲掉进行中的两步确认(有回归用例锁)所有系统字符串(进程名/路径/域名)都用
textContent写入 —— 不用innerHTML, 否则一个精心命名的进程就能在面板里注入标记(有测试盯着这条)面板是手写 bundle(
lib/client.js,DSHModuleLoader约定),不需要打包器 —— 见 架构上面两张图来自
tools/panel-preview.html(离线预览 harness:加载的是真实交付物lib/client.js,但宿主 slots 服务与fetch是 stub、数据为预览样例)。 用任意浏览器打开即可肉眼验收面板,不必装 DSH: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。
扩展到一个新 agent
只改 lib/profiles.js(TS 镜像 src/guard/profiles.ts 同步),不用碰规则引擎:
{
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)
bin/…-mcp.js MCP 可执行入口
src/guard/*.ts TS 权威源码(类型),与 lib/*.js 由 consistency 测试锁死一致
tools/…preview.html 离线预览 harness(浏览器里肉眼验收面板,无需 DSH)
test/*.mjs 8 个离线套件;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 自省循环,整套探针/规则/终止链路当场消失。
现在有三道防线:
- 模板挪到
src/daemon/index.ts(并改名,不再是宿主入口) tsconfig的产物目录改为build/scripts/build.sh里有硬守卫:outDir=lib或src/index.ts存在即拒绝构建
test/ 里的 client-bundle.test.mjs 也会检查 package.json 的入口/类型/bin 指针不悬空
(第一版就犯过"exports["./client"] 指向一个不存在的 lib/client.js")。
桌宠:怎么开、怎么关
桌宠是独立桌面窗口(Windows / PowerShell + WinForms,零依赖),每个客户端各一只。 它不在客户端 UI 里渲染——因为 ZCode / Claude Code 这类 agent 的插件 API 只提供 skills / commands / agents / hooks / MCP,没有"往界面插悬浮组件"的能力。
自动拉起:
| 场景 | 机制 |
|---|---|
| DSH | 宿主插件在第一次 tick 时拉起,看护对象 = 宿主自己 → 宿主退出它自己关 |
| ZCode | 插件 hooks/hooks.json 的 SessionStart(刷新市场后生效) |
| 任何 MCP 客户端 | 它的 MCP server 启动时顺带拉起(.mcp.json → npx deskpet-guard) |
手动开/关(随时可用,不必等客户端):
node bin/deskpet-guard-pet.js --client dsh # 开:DSH 桌宠(自动看护 DSH 宿主)
node bin/deskpet-guard-pet.js --client zcode # 开:ZCode 桌宠(按进程名看护 ZCode)
node bin/deskpet-guard-pet.js --status # 只看文字状态,不开窗
node bin/deskpet-guard-pet.js --list # 现在有哪几只、看护谁
node bin/deskpet-guard-pet.js --stop all # 一次全关(含没有锁文件的孤儿)
Windows 上也可以双击仓库根目录的 start-pet.cmd(默认起 DSH 桌宠,可带参数,如
start-pet.cmd --client zcode)。
手动关掉之后:不会自己弹回来(关闭是你的明确意图,宿主不会跟你抢)。
想再开就上面任意一条命令 / 双击 start-pet.cmd;ZCode 侧重启 ZCode 也会重新拉起。
⚠️
start-pet.cmd必须保持纯 ASCII:批处理按控制台代码页读取,GBK 双字节字符的 尾字节可能撞上&/|,cmd 会把后半行当命令执行(踩过,报错很迷惑)。测试里有守卫。
测试
npm test # 9 个套件 / 126 例(= 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 一次性、一次确认只杀一个 |
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 停轮询 |
已知限制(读这里再决定要不要用)
- 沙箱内探针会被拒:受限沙箱禁止程序打开命名管道,抓 PowerShell 输出会失败,
三个系统探针(进程 / TCP / DNS)全挂 →
R0上报降级。文件类探针不受影响 (就是它在沙箱里扫出了那 3 个.tar.gz.enc)。要看进程/连接侧,请在你自己的终端里跑。 - 不是防火墙(见上)。
- 只在 Windows 上有完整探针;其它平台会明确上报
unsupported。 - 画像表偏薄:zcode / cursor / claude-desktop 三条,只有 zcode 有实证画像。
- 文件名匹配是启发式:打包产物靠文件名/扩展名识别,改名可绕过。
- 没有基线学习:新装的 agent 一律按陌生进程处理,误报会随安装量上升。
killMode:'execute'会真的杀进程(需两步人工确认 + 一次一个)。生产环境建议先用audit跑一段时间。
安全
- 探针只读;不写被监控 agent 的任何目录;不碰防火墙 / hosts / 注册表 / 证书存储;host 侧不出网
- 事件流只追加
- 变更端点三重闸门(自定义头 + 同源 + JSON,或 0600 secret);预检一律 403,永不发 CORS 头
- 终止必须两步确认、token 一次性、一次一个目标
完整模型与已知弱点:SECURITY.md。漏洞请走私密渠道(Security advisory),不要开 public issue。
状态 / Roadmap
- 只读探针 + 规则引擎 + 只增事件流 + CLI
- DSH host 工具(5 个)+ 本地 HTTP API + 面板/桌宠
- 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 包 | ✅ 已发布 | [email protected](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) |
下一步(可选,非阻塞):
- 未来版本发布建议改用 Trusted publishing (OIDC):
由 GitHub Actions 用 OIDC 发布、不存凭据 —— 因为 npm 已宣布 2027-01 起 bypass-2FA token 不能再直连发布;
本仓库已有 CI,加一个
release.yml+ 在 npm 上把 trusted publisher 指到cny1230/deskpet-guard即可; - 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。