Passer au contenu principal
C

deskpet-guard

cny1230/deskpet-guard

Compagnon de bureau qui surveille le comportement des agents. Détecte les exfiltrations suspectes entre agents (chiffrement puis envoi direct vers un stockage objet, indices de fuite DNS, lecture de clés sensibles) et déclenche une alerte. L’agent visé n’est arrêté qu’après confirmation explicite, un à la fois. Les événements sont ajoutés à `guard-events.jsonl` sans modification des entrées existantes ; les autres agents peuvent les consulter via les outils host DSH et MCP(stdio).

Installer

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

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

面板与桌宠


目录


它是什么 / 它不是什么

是:一个只读侦察 + 分级告警 + 一步确认后处置的小守护。它回答的问题是 "本机现在有没有 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-storageagent 进程连向 OSS/S3/COScritical建议终止(两步确认)
R2-fresh-bundle-artifact10 分钟内出现打包产物high告警
R3-bundle-burst5 分钟内成簇刷包 ≥3high告警
R4-secret-file-touched密钥/凭据文件最近被写medium告警
R5-dns-object-storage-residueDNS 缓存残留对象存储域名info线索(非外传证据)
R6-agent-alive-quietagent 存活 + 当前外发连接数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 条事件流

alert 态:探针降级如实上报,不装安全

  • 数据面是本地 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:

    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 自省循环,整套探针/规则/终止链路当场消失。

现在有三道防线:

  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,零依赖),每个客户端各一只。 它不在客户端 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.mjssrc/guard/*.ts 与 lib/*.js 双实现一致性
api.test.mjsHTTP API 与鉴权边界:预检拒绝、跨源拒绝、secret 路径、body 边界
plugin.test.mjs假 ctx 跑 apply():工具真注册(走 ctx.effect)、API 真挂载、两步链路连通、audit 必拒
mcp.test.mjsJSON-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 停轮询

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

  1. 沙箱内探针会被拒:受限沙箱禁止程序打开命名管道,抓 PowerShell 输出会失败, 三个系统探针(进程 / TCP / DNS)全挂 → R0 上报降级。文件类探针不受影响 (就是它在沙箱里扫出了那 3 个 .tar.gz.enc)。要看进程/连接侧,请在你自己的终端里跑。
  2. 不是防火墙(见上)。
  3. 只在 Windows 上有完整探针;其它平台会明确上报 unsupported。
  4. 画像表偏薄:zcode / cursor / claude-desktop 三条,只有 zcode 有实证画像。
  5. 文件名匹配是启发式:打包产物靠文件名/扩展名识别,改名可绕过。
  6. 没有基线学习:新装的 agent 一律按陌生进程处理,误报会随安装量上升。
  7. 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 包✅ 已发布deskpet-guard@0.1.0(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): 由 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。

许可

BSD-3-Clause。

Plugins associés