跳到主内容

防御性编程:结果报告、清理与凭据

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% 误分支),是用户体验分水岭。