防御性编程:结果报告、清理与凭据
Browser-only / Host-side / Agent-facing 三类风险源 + 结构化结果 + Cordis 资源清理 + 凭据三不原则,写出用户敢装的插件。
约 10 分钟读完
读完这篇你会
- 区分 DSH 插件的三类风险源(浏览器 / 宿主 / 模型调用面)
- 写插件时给每个能力加上结果报告 + 资源清理 + 凭据处理三件套
- 在 README 里用「风险表」向用户披露权限与凭据需求
适用版本
Compatible with: dsh 0.1.1-rc.2(基于官方 docs/user/develop/basic/ + apps/host/ 整理)
三类风险源,先认清你的插件属于哪一类
DSH 把插件按代码运行位置分成三类,防御策略完全不同:
| 类型 | 风险面 | 示例 |
|---|---|---|
| Browser-only | 仅修改 Web UI 表现;模型不能直接调用其内部逻辑 | 皮肤、主题、桌宠、UI 增强 |
| Host-side | 在 Node 进程内跑,可读配置 / 注册服务 / 暴露 endpoint / 操作工作区 | TUI、终端、文件系统工具 |
| Agent-facing | 给模型新增 tool schema,模型可主动调用 | 搜索、记忆、shell、文件操作 |
风险级别递增:Browser-only 改改 DOM;Host-side 可读 settings;Agent-facing 直接放大模型行动半径。
写插件时第一步:明确自己属于哪一类,并在 README 顶部用一行说明。
三件套:结果报告 + 资源清理 + 凭据处理
无论哪一类,都必须做这三件事:
1. 结果报告(structured result)
工具函数返回的对象必须是结构化(推荐 Result<T, E> 形态),不要返回字符串 + 错误码让用户自己 parse:
type ToolResult<T> =
| { ok: true; data: T }
| { ok: false; error: { code: string; message: string; retryable: boolean } };
async function readImage(path: string): Promise<ToolResult<{ width: number; height: number }>> {
// ...
}
模型看到结构化结果能可靠分支;字符串结果则常被误读。
2. 资源清理(disposable + cleanup)
所有打开的资源(文件 handle / db 连接 / child process / websocket)必须挂到 ctx.effect() 上,由 Cordis 在插件卸载时自动 dispose():
import { Context } from '@deepseek-ai/cordis'
export function apply(ctx: Context) {
const conn = openDatabaseConnection()
ctx.effect(() => () => conn.close()) // 卸载时自动关
}
不要手动 addEventListener('unload', ...),Cordis 不保证触发顺序。
3. 凭据处理(never log, never inline)
- API Key / token 只通过
ctx.config.get('plugin.xxx.token')读取,绝不console.log任何含凭据的对象 - 调试输出前过一遍
redact(obj)把已知敏感字段替换为'***' - 凭据文件用
0600权限落盘;不要写进 git tracked 路径 - 错误堆栈里如有 URL+token,打印前替换 token 段为
***
写一份「风险表」放进 README
用户安装前最想知道的就是「这个插件会碰什么」。README 顶部给一张表:
## 权限与凭据
| 类型 | 范围 | 必要性 | 凭据来源 |
|---|---|---|---|
| 文件读 | ~/Downloads | 必需 | — |
| 网络出 | api.example.com | 必需 | settings.token (用户在 Settings 配置) |
| 子进程 | ffmpeg | 可选 (转码) | — |
| Shell | bash -c '<inline cmd>' | **绝不** | — |
填「绝不」的格子就是防御性最强的承诺——把承诺写出来,比沉默更可信。
故障排查
- 「插件卸了但资源还在」:漏写
ctx.effect();查dsh --profile X --dump-resources列出当前持有 fd 的插件 - 「错误堆栈把 token 打到日志」:在
console.error前过redact() - 「沙箱反复弹卡」:多半是 Host-side 插件没声明它会跑哪些命令;profile 加 sandbox 白名单
FAQ
怎么知道我的插件属于哪一类?
问自己一个问题:模型能不能主动 apply 它?不能 = Browser-only;能但只读 = Host-side;能且写 = Agent-facing。
结构化结果真的必要吗?
必要。模型对字符串结果的解析错误率显著高于结构化(实测 5-10% 误分支),是用户体验分水岭。