插件运行时与安全
插件安全保护的是本地执行边界,不只是插件市场页面。Runner 可以持有第三方凭据、读取用户授权文件、启动二进制并产生外部写操作,因此每条远程指令都必须指向精确用户、设备 generation、不可变 release、connection、permission 和 payload。
执行边界
指定设备离线、fenced、缺少已验证 runtime 或无法耐久保存入站事件时,插件不可用。系统不会选择其他设备、在服务器执行或把工作留到以后。
设备信任链
登记、租约、ticket 和 WSS 解决不同问题:
有效 Desktop 重连必须复用 enrollment。每次点击都重新登记会制造重复设备,无法修复租约或 readiness 问题。撤销会推进 generation,fence 旧 WSS、动作、Runtime Session 和调用。
渠道信任资源
每个 Desktop 发布渠道只读取一份渠道专属 trust resource。启动时必须验证:
- 构建 channel 与信任资源 channel 一致;
- 插件 WSS origin 与当前 backend origin 同源;
- 路径精确为
/scilaxy/ws/v1/runner/device; - 至少一把 Ed25519 公钥在当前时间有效且未撤销;
- 服务端 signing key id 能映射到该渠道的可信公钥。
Dev、Beta/Test、RC/UAT、Stable/Prod 不共享 runtime signing identity。跨渠道 fallback 会让较弱环境控制另一个渠道的本地 Runner;同源和精确路径校验则阻止代理漂移把本地执行通道指向无关 WebSocket。
这些校验对执行边界是合理的,但产品必须区分 lease_failed、trust_config_invalid、server_signing_unavailable、transport、protocol 和普通 readiness blocker。安全严格不等于错误可以模糊。
运行包
可选第一方 provider runtime 不捆绑进 Desktop installer,也不在 API/Worker 容器执行。
Desktop 构建携带与构建绑定的 plugin-runtime-manifest.json。首次使用时,指定 Runner:
- 解析 release 对应平台 archive;
- 下载到当前 channel 私有缓存;
- 校验 manifest、archive digest 和每个声明成员;
- 强制每个成员的 executable role;
- 原子发布已验证缓存项;
- 后续执行前继续复核缓存证据。
下载失败、平台不支持、digest 不匹配或缓存缺失都会使能力不可用,不存在服务器 provider-host fallback。
插件代码可以包含协议常量、schema version、规范 API path、闭合枚举和安全上限。产品目录项属于 builtin/catalog.json;环境 origin、channel、signing key 和部署 secret 来自 typed build/runtime configuration。Provider 协议 endpoint 只属于对应 adapter。
凭据边界
Device-local 配置使用 plugin_device_action,其中只有 provider key、profile label、目标 device/generation 和 request digest。动作未及时领取和完成会过期。
飞书流程为:
- 用户选择 profile label 和在线 Desktop;
- Desktop 使用短期 user-gesture ticket 领取动作;
- Electron Main 打开隔离窗口,输入
cn或global、App ID、App Secret; safeStorage把记录加密到当前 channel 私有插件目录;- Desktop 签名完成事实;服务端原子完成 action 并创建
readyconnection; - Runner 获得一次性本机 bootstrap,启动 provider 长连接。
API、Worker、renderer 和公开 DTO 只获得 profile label、不透明 profile id、device id、connection/credential revision 和不可逆摘要。飞书与 GitHub device-local connection 的 plugin_connection.credential_id 保持为空,操作 secret 不进入 plugin_secret。
签名 Provider 调用
内层 plugin-provider/1 当前只允许闭合 tools.list 与 tools.call。外层签名信封绑定:
- signing key id、owner、device、channel、app version;
- device generation、connection id、nonce、request id;
- installation、release、capability、authority digest;
- connection/permission revision 与 credential version;
- issued-at、expiry 与完整 body digest。
Runner 在 provider I/O 前拒绝未知字段、重复 JSON key、过期请求、body 不匹配、revision 漂移、authority 漂移和 device-generation 漂移。
外部效果
潜在外部写操作表示为 plugin_effect_intent。不可变信封记录审批的精确内容:action、arguments、destination、release/tool/config 证据、connection/permission revision、device generation、runtime/workspace authority 和 provider idempotency key。
状态会区分安全失败与结果不明:
failed_before_dispatch:没有跨过 provider 边界,可以安全重试。uncertain:provider 可能已经接受写入,不得自动重放。reconciled_succeeded/reconciled_failed:provider 证据已经解决歧义。
外部写操作不能配置成永久允许,只能每次提示或拒绝。
入站与 Webhook
飞书通过本地 provider 的官方长连接工作。Runner 先记录事件 journal,再通过已认证设备通道发送并等待服务端耐久接收。服务端去重、校验当前 route authority,在一个事务中创建 inbound event/outbox 与 Turn,然后确认本地 digest。设备离线时,服务器不会代收或延迟投递。
GitHub Webhook 是受控公网 ingress 例外:
- endpoint 绑定精确 installation/connection revision、device generation、release 和 authority digest;
- 服务端校验
X-Hub-Signature-256、event type、delivery id、大小和速率; - 只有绑定设备在线才转发;
- Runner 耐久保存事件并返回 digest-bound ACK;
- 服务端收到 ACK 后才向 provider 返回成功;
- offline、fenced、timeout 或本地持久化失败返回可重试失败,不建立离线工作队列。
服务器保存 webhook verifier material,因为公网 ingress 必须认证,但它不授予 GitHub API 权限。服务器不保存 GitHub Token 或原始 delivery body。
安全不变量
- 插件代码、操作凭据、进程、调用和访问的本地文件只存在于指定 Desktop/Runner。
- 设备缺席必须明确不可用,绝不回退其他设备或云端执行。
- Renderer、公开 DTO、API/Worker 日志和审计 metadata 不包含 device-local secret 或可执行路径。
- Release、config、tool catalog、permission、connection、device generation 或 runtime 漂移时,旧异步工作在 provider I/O 前被 fence。
- 入站成功要求耐久接收;外部写入歧义要求对账而非盲目重试。
- 已删除 Session snapshot 表、旧 provider-host 执行、fallback wire format 和跨渠道 trust 均不受支持。