# IDE 扩展宿主身份能证明代理是谁吗？

编辑器扩展宿主可以带有有效的代码签名，但你仍然无法确定究竟是哪个扩展请求了敏感操作。这不是代码签名的缺陷，而是必然结果：你让操作系统身份机制回答了一个存在于单个进程内部的问题。

当 AI 编程代理通过 IDE 运行时，这一点尤其重要。宿主可能加载多个扩展，接受来自工作区的命令，还会把任务交给能够访问 API 或 SSH 目标的网关。如果网关只批准一次宿主，便把这次批准当作某个特定扩展意图的证明，它授予的权限就超出了现有证据能够支持的范围。

我见过这种错误披着整洁安全设计的外衣出现：验证签名后的编辑器，在审批提示中显示签名者，然后允许运行。这是一项有用的控制措施。但当提示让人以为它能提供实际上并不存在的精确归因时，就会变得危险。宿主可能值得信任，但跨过宿主边界的请求仍然可能无法确定来源。

## 宿主签名识别的是容器，不是其中的使用者

代码签名能证明可执行映像的一些事实。在 macOS 上，Apple 将代码签名描述为确认软件来源和完整性的一种方式。系统可以检查受信任的签名机构是否签署了代码，以及已加载的代码是否仍与签名材料一致。当安全决策要回答「哪个应用进程正在发起请求」时，这正是所需的证据。

但它无法证明「这个进程中的哪个扩展函数发起了请求」。在这个边界上，一个进程只有一个可执行身份。如果编辑器启动扩展宿主，而宿主加载了十个插件，内核不会为它们的 JavaScript、字节码、回调或扩展 API 调用分别生成十个代码签名身份。

这一区别会直接影响实际操作。网关可以留下这样的合理记录：

```text
caller executable: /Applications/Editor.app/.../extension-host
signing authority: Example Software Team ID ABC123
process id: 8421
parent process: Editor.app pid 8304
```

但它无法仅凭签名推导出下面这些信息：

```text
extension: publisher.cloud-deploy
command: deployCurrentProject
prompt source: chat request 18
```

第一段是操作系统证据，第二段是应用层来源信息。两者都可能有用，但在日志和审批界面中应区别对待。

人们常把这两者都称为「身份」，实现时便逐渐忘记它们的区别。不要这样做。签名者告诉你是谁制造了这个盒子，却不会告诉你盒子里的哪位乘客伸手去操作了控制器。

## 共享扩展宿主会把多个权限主体合并为一个

扩展宿主的存在，是为了让编辑器加载和协调扩展。这种设计很方便，但也让宿主成了一个权限集合。补全插件、格式化工具、源代码管理集成、聊天助手和工作区扩展，都可能在同一个宿主进程中执行。

设想一个网关：验证编辑器签名后，就允许发起 HTTPS 调用。扩展 A 要求宿主调用部署端点。扩展 B 可以访问某个扩展 API，让同一个宿主发起出站操作，可能是直接发起，也可能是通过 A 注册的命令发起。在网关看来，两种请求拥有相同的进程标识符和签名机构。网关无法通过检查宿主签名来区分它们。

当扩展通过共享宿主服务通信时，情况会更复杂。一个插件可能注册命令，另一个插件调用它。聊天扩展可能从仓库文件、问题描述或粘贴的终端结果中接收指令，然后调度命令。此时归因分成了几层：签名后的宿主、发起调用的扩展、提供输入的扩展，以及影响这一过程的人或不受信任内容。

这并不意味着 IDE 扩展天生不安全，而是说宿主级审批实际上批准了宿主所包含的全部权限。如果某项操作对这样的权限范围来说过于宽泛，就需要第二项能够看到具体操作的控制措施。

## 只有宿主绑定扩展名称时，它才算来源信息

像 `publisher.name` 这样的扩展标识符很有用，但网关不应把调用方自行提供的字符串误认为证据。任何能够构造请求的代码，都可以把 `publisher.name` 写入请求头、JSON 请求体、命令参数或环境变量。这只能说明它声称自己是谁。

如果宿主从自己的扩展注册表中获取标识符，将它绑定到当前执行上下文，并通过扩展无法伪造的受保护本地通道发送出去，这个声明就更可信。即便如此，它回答的仍然是一个范围更窄的问题：哪个由宿主管理的扩展上下文发起了这次请求。它可能仍然无法识别影响这个上下文的提示、仓库内容或具体人员。

检验方法很简单。逐个询问每个字段来自哪里，以及谁可以修改它。

| 字段 | 可以证明什么 | 谁可以伪造或修改它 |
|---|---|---|
| 宿主签名机构 | 已加载宿主可执行文件的身份 | 扩展通常无法伪造 |
| 进程标识符和启动时间 | 某个正在运行的宿主实例 | 由操作系统分配 |
| 请求中的扩展 ID | 某个声明的扩展身份 | 任何能够创建请求的代码，除非由宿主绑定 |
| 工作区路径 | 宿主声明的上下文 | 宿主或扩展可以虚报 |
| 审批决定 | 人工批准了界面上显示的请求 | 审批界面必须将它绑定到具体操作 |

因此，「我们在审计日志中记录了扩展名称」并不是完整的安全声明。保留这个名称，但除非架构让网关有理由把它视为已验证信息，否则应标记为宿主报告的信息。这个标签能避免未来的调查人员把便利性元数据当成证据。

## 会话审批有明确边界，也有严格上限

批准一个新启动且经过验证的宿主进程是有价值的。它可以发现不同的可执行文件、签名机构变化、全新的进程生命周期以及异常的启动路径，也让操作电脑的人有机会看到谁即将获得访问权限。对于使用范围有限凭据的日常工作来说，这种打断可能是值得的取舍。

问题在于，会话审批会让整个获批进程拥有该会话允许的一切能力。如果进程承载了多个扩展，它无法区分你预期的扩展发出的请求，和另一个已加载扩展创建的请求。清晰的审批卡可以展示宿主签名者和进程身份，但不应承诺它无法提供的插件级归因。

团队还常常忽略第二种失效情况。他们早上批准了编辑器进程，之后又在同一个长期运行的会话中安装或启用了扩展。如果宿主重新加载或加载新代码，却没有改变网关检查的外部身份，那么这次批准的范围仍然比人们记得的更大。具体应对方式取决于宿主的行为，但原则不变：已批准宿主内部发生的变化，不会自动对外部代理可见。

应让会话审批回答它真正能够回答的问题：「这个签名后的宿主进程在运行期间，可以使用这一类访问权限吗？」不要把它扩展成：「这个特定扩展可以执行这个特定的不可逆操作吗？」

## 在真正需要的时候按次审批，可以弥补归因不清

对于敏感凭据，应在操作形成时请求审批，而不是只在宿主首次出现时审批。提示应展示足够多的拟议操作，让人能够做出有意义的决定：目标、方法或 SSH 目标、凭据标签，以及会产生后果的请求部分。

假设宿主向本地操作网关发送以下请求：

```json
{
  "channel": "http",
  "credential": "production-deploy",
  "method": "POST",
  "url": "https://deploy.example.internal/releases",
  "body": {"service": "billing", "version": "a1b2c3d"}
}
```

合适的审批卡应将决定绑定到确切的方法、目标、凭据以及请求体，或请求体的稳定摘要。它不应只写「编辑器想使用 production-deploy」。这种措辞会把一次操作审批变成一张空白支票，让宿主在决定失效前发起任何调用。

按次审批不受欢迎，因为它会打断工作流程。对于无害调用，这个反对理由很合理。但对于生产环境写入、范围很广的凭据，或能够修改机器的 SSH 命令，它就不够有力了。审批人不必百分之百确定究竟是哪个扩展有问题，只需要看到即将离开机器并产生后果的操作，再判断它是否属于当前任务。

保险库网关能增加会话审批无法提供的另一道边界。保险库锁定时，拒绝所有操作，包括来自之前已批准宿主的调用。Sallyport 会在会话授权前执行这道绝对闸门，选定的凭据还可以要求每次使用时都审批。这样一来，各项控制措施的作用就很清楚：宿主签名识别调用方，会话决定允许该进程在有限生命周期内运行，按次决定则覆盖具体的敏感操作。

## 范围有限的凭据可以降低正确审批带来的损害

审批不能替代凭据范围设计。人们可能批准了正确的宿主和正确的请求，却发现凭据允许的操作远远超出原本的意图。这是凭据设计失败，不是审批失败。

应根据后果拆分访问权限。只能读取一个仓库的令牌，不应同时拥有管理所有项目的权限。部署凭据不应能够创建用户或读取无关密钥。对于 SSH，如果远端支持，应使用独立账户或强制命令配置，而不是给一个只需要执行单项维护任务的工作流提供通用 shell。

给代理一个范围很广的开发令牌很受欢迎，因为设置只需五分钟。但当令牌跨越多个环境或拥有写入权限时，这种做法就是错误的。宽泛令牌会把扩展宿主内部的每个模糊点，变成宿主外部同样宽泛的模糊点。范围有限的凭据让审批提示更容易阅读，因为可执行操作本身已经有了清晰边界。

对于 HTTP 操作，如果设计允许，应在凭据记录或网关配置中限制目标。注入到任意 URL 的 bearer token 可能被诱导发起请求的宿主窃取，并发送到攻击者控制的端点。对于 SSH 操作，在决定会话审批是否足够前，应记录主机、账户和预期命令类别。

## 一个常见的失败场景，始于无害的命令面板操作

某位开发者安装了助手扩展和部署扩展。两者都运行在同一个签名后的扩展宿主中。当助手请求检查 staging API 时，开发者批准了宿主，因为审批卡正确显示了编辑器的签名机构。

后来，开发者打开了一个包含助手操作说明的仓库。助手解析该文件，并调用部署扩展注册的宿主命令。这个命令要求同一个网关使用生产凭据。该请求与 staging 请求拥有相同的签名进程身份。只依赖会话审批的网关看到一个已获批准的调用方，就会继续执行。

整个过程不需要伪造签名，也不需要攻破操作系统。问题源于一次覆盖宿主的审批，以及一个覆盖了开发者并未打算授予的操作的凭据。如果审计记录只写着「已批准的编辑器发起了 HTTP 调用」，团队就无法判断这条链路究竟由部署扩展、助手还是任务文件启动。

应从三个地方调整设置。每次使用生产凭据都要求明确审批。在审批中显示目标和发布载荷。将宿主身份与宿主报告的扩展上下文分开记录，并保留审批事件与调用之间的关联。请求可能仍然合法，但不能再伪装成例行的后台工作。

## 审计记录需要证据栏，而不是好听的故事

当日志把已验证事实和自行报告的上下文压缩成一句话时，就会变得具有误导性。「插件 X 部署了服务 Y」看起来很精确，却可能隐藏了未经验证的插件标签和不明的因果链。应记录原始事件，并标明每项信息的真实来源。

一种有用的事件结构可以把这些声明分开：

```json
{
  "time": "2026-07-24T10:16:43Z",
  "caller": {
    "signing_authority": "Example Software Team ID ABC123",
    "pid": 8421,
    "started_at": "2026-07-24T09:58:03Z"
  },
  "host_reported_context": {
    "extension_id": "publisher.cloud-deploy",
    "workspace": "/work/payments"
  },
  "action": {
    "channel": "http",
    "method": "POST",
    "destination": "https://deploy.example.internal/releases",
    "credential": "production-deploy"
  },
  "authorization": {
    "session_approved": true,
    "per_use_approved": true
  }
}
```

重点不是增加更多日志字段，而是保留系统验证的事实与宿主作出的陈述之间的边界。发生事件时，这一区别决定了调查人员能否追踪一次操作，还是只能重复一个标签。

防篡改证据同样重要。被攻破的进程可以改写本地日志，因此它自行记录的行为只能算薄弱证据。Sallyport 会从加密且带哈希链的审计日志中生成会话和活动视图，`sp audit verify` 可以在离线状态下验证这条链，无需保险库密钥。这无法解决扩展归因问题，但能防止有人事后悄悄修改记录，美化整个故事。

## 需要精确归因时，应隔离这项操作

有些操作需要共享宿主无法提供的更强答案。如果某个凭据可以转移资金、修改生产访问权限、删除数据或运行不受限制的远程命令，就应通过一个拥有独立可执行身份且接口范围有限的组件来处理。这样，网关可以验证这个 helper，而不是从拥挤的扩展宿主中推测意图。

helper 应接受明确的请求结构，拒绝额外参数，并将自身权限控制在很小范围内。例如，发布 helper 可以只接受允许列表中的服务名称和版本摘要，同时拒绝任意 URL 和 shell 片段。父级编辑器仍然可以发起操作，但不能把 helper 变成通用网络客户端。

进程分离并不是魔法。如果宿主可以向 helper 发送任意请求，那么同样的歧义只是被转移到了另一个进程。接口必须移除宿主本不应拥有的选择。一个独立签名的 helper 配上一个开放的「运行任意操作」请求，只是在做表面功夫。

如果隔离成本太高，就退回到按操作审批和范围有限的凭据。即使无法弄清编辑器内部的具体来源，这种组合仍然能让人有机会发现即将发生的后果。除非设计能够说明这种确定性来自哪里，否则不要声称具备插件级确定性。

## 把审批措辞视为访问边界的一部分

审批提示会改变人的行为，因此它的措辞需要和执行访问控制的代码一样谨慎。如果提示写着「允许编辑器访问部署」，用户就会学着批准某一类权限。如果提示写着「允许这个签名后的扩展宿主使用这个凭据，将这次发布 POST 到这个目标」，用户就能评估自己正在授权的具体操作。

应突出显示进程签名者，因为它能帮助发现错误的应用程序。如果宿主提供了扩展名称，也应保留显示，因为它有助于用户识别预期工作。当网关无法验证扩展名称时，应将它标记为报告的上下文。界面明确说明不确定性，比用精致的标签制造保证感，更能帮助人们正确处理风险。

先盘点 IDE 宿主可以访问的每个凭据。对每个凭据写清楚：它可以安全继承的宿主级审批是什么，需要重新决定的操作形态是什么，以及审计记录保留的确切证据是什么。如果有人问「这次操作是哪个扩展发起的」，而答案只是猜测，就不要把猜测当成事实来授予权限。
