阅读需 8 分钟

AI 代理的代码签名:用证据审批会话

AI 代理的代码签名能帮助团队识别代理进程、审查共享 Mac 上的审批,并限制凭据使用,但不能让团队盲目信任签名。

AI 代理的代码签名:用证据审批会话

如果审批提示只写着“AI 代理请求访问”,它要求人类为一个身份不明的对象背书。在共享开发机器上,这可能意味着有人批准了复制来的脚本、遗留的终端,或由错误用户启动的进程。更有用的问题应该具体得多:哪个可执行进程正在请求访问?谁为它签名?它是如何进入系统的?这次运行期间它能做什么?

代码签名可以为这个决定提供证据。它能把正在运行的程序与签名机构关联起来,并显示签名内容是否发生变化。但它无法告诉你代理是否收到了恶意提示,无法证明可信发布者没有发布有问题的版本,也无法判断请求的生产操作是否合理。把签名误当成行为保证,团队就容易陷入麻烦。

在共享 Mac 上,可以利用签名机构识别已知代理客户端、拒绝异常请求,并让每次审批随获得授权的进程结束而失效。同时还要配合清晰的账户边界、范围受限的凭据,以及能够还原审批和后续调用的记录。

签名能识别代码,不能识别代码背后的意图

代码签名可以确认某段具体代码由谁签署,以及 macOS 是否仍能验证签名内容;它不能证明这段代码应该获得系统访问权限。每个审批流程都必须清楚保留这一区别。

Apple 的代码签名文档 Technical Note TN2206 将签名描述为验证代码并表达 designated requirement 的方式,macOS 可以据此在之后识别同一份代码。designated requirement 很重要,因为它比文件名更精确。名为 agent 的可执行文件可以被复制到任意位置并改名。如果签名链和要求仍能通过验证,签名身份会提供更强的连续性。

这种连续性可以回答一个实际问题:“这是我们同意允许的客户端吗?”但它无法回答以下同样实际的问题:

  • 代理是否收到了不该进入生产环境的指令?
  • 用户是否有意从预期项目目录启动了这个进程?
  • 进程是否加载了会改变行为的扩展、插件或配置?
  • 请求的 API 调用是否属于当前任务?
  • 调用背后的凭据是否拥有超出任务所需范围的权限?

人们常把发布者名称当成结论,但它不是。签名机构只能告诉你,谁控制了用于签署该构建产物的签名凭据。大型发布者可以签署许多程序,小型内部团队也可以签署完全合适的构建。审批时应将观察到的身份与团队能够解释的允许列表比较,而不是因为颁发者听起来熟悉就批准。

还有一个容易被忽略的限制:签名验证的是被签署的代码,不会自动覆盖进程之后读取的一切。配置文件、提示文件、环境变量、仓库中的数据、下载的扩展和远程响应,都可能改变一个正确签名的程序的行为。即使代理的二进制文件看起来熟悉,也必须控制它可以执行的操作。

把审批卡当作归属记录来阅读

审批卡应在操作员批准前提供足够信息,让他把请求归属到真实进程。签名机构应放在顶部,因为它比进程标签更难伪造,但必须和进程路径、会话详情一起显示。

对于在共享机器上请求访问的进程,我希望在同一处看到:

  • 可执行文件或应用身份,以及完整的本地路径。
  • 用于识别它的签名机构或 designated requirement。
  • 进程 ID 和父进程,以便知道它由什么启动。
  • 拥有该进程的 macOS 用户账户。
  • 操作通道和目标,例如 API 主机或 SSH 主机。

前三项事实可以捕捉不同类型的问题。熟悉的显示名称配上异常路径,通常意味着二进制文件被复制,或有人使用了包装器。熟悉的路径配上陌生的签名机构,可能意味着有人重新构建或替换了文件,也可能是符号链接指向了其他位置。熟悉的可执行文件由异常父进程启动,则可能说明是另一款自动化工具启动了它,而不是正在查看提示的开发者。

在共享 Mac 上,用户账户不是装饰。如果两位工程师共用一个登录账户,审批几乎无法说明是哪位用户启动了代理。机器仍能告诉你哪个进程发起了调用,但团队在审批流程开始之前,就已经失去了清晰的人类责任边界。为每个人设置独立的 macOS 账户,比事件发生后争论终端历史记录便宜得多。

不要训练人们只根据徽标、简短命令名或单独的机构字符串来批准。应让他们识别完整的预期组合:已批准的代理客户端、预期签名机构、预期本地位置、自己的账户,以及与任务相关的目标。缺少大部分证据的审批卡,会把一次点击变成猜测。

把可执行文件检查清楚,再将其设为已批准身份

在团队依赖某个客户端的签名机构开展实际工作前,应检查计划批准的确切应用或可执行文件。在初次设置时完成检查,将预期结果记录到内部操作手册中,并在有意升级客户端时重复检查。

在 macOS 上,codesign 可以显示签名详情。详细输出会写入标准错误,因此如果要保存审查材料,需要进行重定向:

codesign -dv --verbose=4 /Applications/ApprovedAgent.app 2\u003e\u00261

输出通常会包含类似字段:

Executable=/Applications/ApprovedAgent.app/Contents/MacOS/ApprovedAgent
Identifier=com.example.approved-agent
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20500 size=...
Authority=Developer ID Application: Example Developer (ABCDE12345)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
TeamIdentifier=ABCDE12345
Sealed Resources version=2 rules=13 files=...

应将 Identifier、叶节点 AuthorityTeamIdentifier 放在一起看。单独使用 Team ID 作为审批规则并不可靠,因为一个组织可以为多个应用签名。单独使用 identifier 也更弱,因为任何人都可以创建一个具有相同 bundle identifier 的未签名程序。签名链共同构成了有用的身份声明。

然后验证签名内容:

codesign --verify --deep --strict --verbose=2 /Applications/ApprovedAgent.app

成功时通常不会输出内容。失败则表示嵌套组件被修改,或存在其他签名问题。--deep 会要求 codesign 递归检查嵌套代码,适合用作检查辅助,但不要把它当成所有内置组件都符合安全策略的证明。Apple 说明,深度验证会递归执行,可能掩盖应用包内部签名设计需要直接审查这一事实。

如果应用来自受管理软件渠道之外,还应让 Gatekeeper 评估它:

spctl --assess --type execute --verbose=4 /Applications/ApprovedAgent.app

spctlcodesign 回答的是相关但不同的问题。codesign 针对构建产物验证签名,spctl 则询问系统评估策略是否接受它。通过评估是有用的来源信号,但不代表应用的命令、脚本或远程行为适合生产环境。

记录检查过的路径。如果有人后来因为 /Users/alex/bin/agent 的标签看起来像在 /Applications 中检查过的应用,就批准了它,那么他并没有重复这项检查。路径本身就是证据的一部分。

解释器的签名不会为它运行的脚本背书

已签名的终端、运行时或 Shell,不能为启动时交给它的任意脚本担保。许多听起来合理、实际却失效的审批,都源于这个缺口。

例如,开发者可能用下面的命令启动代理:

/usr/bin/python3 /Users/dev/work/demo/tools/agent_runner.py

系统 Python 可能有已知签名,但这只能说明解释器二进制文件的情况。它无法说明 agent_runner.py、仓库中导入的文件、读取的 .env 文件,或通过标准输入传入的指令。如果审批界面只显示 python3,攻击者无需修改解释器,只要修改脚本或项目状态即可。

node、Shell、编辑器扩展和通用自动化运行器也有同样问题。像“批准由该运行时发布者签名的进程”这样的宽泛规则,会让团队轻易批准大量没人审查过的行为。它之所以受欢迎,是因为能减少审批阻力,但对于能够接触重要凭据的通道,这种做法并不安全。

选择下面两种模型之一,并明确告诉团队采用哪一种。更强的模型是批准一个专门构建、已签名的代理客户端,由该客户端的会话进程直接请求访问。更灵活的模型允许解释器,但把每次脚本启动都视为独立会话,并在解释器签名机构旁显示脚本路径、项目目录、参数和父进程。

可以用标准工具检查运行中进程的基本上下文:

ps -p 4821 -o pid=,ppid=,user=,command=
ps -p 4812 -o pid=,ppid=,user=,command=

第一条命令可能显示代理进程,第二条显示其父进程。将命令行与开发者正在进行的工作对照。如果子进程来自预期项目中的终端,证据是一致的。如果它来自无人值守的调度器、浏览器辅助进程或另一款代理,就不要再把请求视为普通的开发者审批。

对于基于脚本的客户端,应将脚本摘要纳入审批记录。一个简单的本地检查就能让变化变得可见:

shasum -a 256 /Users/dev/work/demo/tools/agent_runner.py

摘要不会让脚本自动可信,但当有人询问批准的脚本在两次运行之间是否发生变化时,它能提供具体答案。只应为经过审查的版本或受控项目状态保存预期摘要,不要养成从聊天消息复制哈希值的形式主义。

审批规则之前,共享机器需要账户边界

将代理与凭据分开
Sallyport 自行执行 HTTP 和 SSH 操作,只返回结果,不向代理暴露凭据。

只要每个人使用独立账户、每次代理运行都有明确负责人,共享开发 Mac 就可以管理。如果所有人共用一个登录账户,签名机构无法弥补缺失的归属信息。

为每位开发者创建独立的 macOS 账户,日常工作不要使用公共管理员账户。代理进程应在启动它的用户下运行。这样,项目文件、终端历史、环境变量和审批决定才有明确的负责人。共享仓库不等于必须共享操作系统账户。

可行的设置还应区分敏感目标。为开发、预发布和生产环境使用不同的凭据条目,并采用能让操作员看出用途的标签。对 inventory-staging 的审批不应因为两个环境指向同一个 API 客户端,就悄悄选择生产凭据。如果凭据名称掩盖了环境,人类就无法在关键时刻做出可靠选择。

物理访问同样重要。有人坐在一台未锁定的共享 Mac 前,就可以在当前账户下启动进程,然后等账户所有者点击审批提示。离开时锁定屏幕,设备休眠后要求重新登录,也不要在公共区域留下正在运行的高权限终端。这些控制措施看似普通,所以团队往往会跳过它们,直到不得不收拾一场本可避免的混乱。

不要用发布一份庞大的已批准签名机构列表来解决共享机器风险。列表会不断加入编辑器、语言运行时、包管理器、构建工具和辅助应用,最后只能说明这台机器用于开发。对于能够请求外部操作的代理,应保持允许集合足够小,并记录每个身份为何属于其中。

会话审批应绑定一个进程,而不是永久绑定一个人

会话审批应在该进程运行期间授权一个已观察到的代理进程,并在进程退出时失效。这是在每次无害请求都要求决定,与授予会持续存在的长期权限之间的实用折中。

进程边界比日历时间限制更重要。代理退出并重启后,就是新的执行上下文。它可能拥有不同的工作目录、被修改的二进制文件、不同的扩展、不同的父进程,或键盘前的不同用户。重启时要求重新授权,能让操作员再次看到这些变化。

这正是代码签名发挥作用的地方。审批流程可以先显示签名机构,帮助操作员识别预期客户端;会话绑定则防止这种识别变成无限期授权。代理进程退出后,审批也应随之消失。如果操作员发现可疑行为,应能在详细调查代码签名前立即撤销会话。

要把授权与身份验证分开。Touch ID 或密码可以证明当前的 macOS 用户批准了请求,但无法识别进程。代码签名可以帮助识别进程,但两者都无法决定目标和操作是否合适。优秀的审批卡会展示这三类证据,而不是假装它们可以相互替代。

对于普通仓库工作,一次会话决定可以覆盖针对开发 API 的重复读取操作。若操作会修改部署配置、轮换凭据、删除记录,或打开到生产主机的 SSH 连接,就应对该调用要求新的决定。阻力应放在后果发生变化的地方,而不是在计时器到期后随机出现。

签名机构无法阻止人们以为它能阻止的失败

审批已签名的进程
Sallyport 会在新会话审批时显示请求进程的代码签名颁发者。

有效的签名链无法阻止可信客户端收到有害输入、被攻破的开发者账户签署恶意代码,或已批准进程发出不明智的请求。应把它们视为不同的失败路径,并为每条路径设置正确的控制措施。

第一条路径是提示或仓库指令攻击。代码助手读取恶意注释,被要求通过 HTTP 请求窃取配置文件。可执行文件可能正是已批准且正确签名的客户端。审批需要显示目标和请求通道,因为签名无法说明它遵循了什么指令。

第二条路径是团队尚未审查的合法更新。发布者可以使用同一签名机构为新版本签名。如果不检查身份和发布来源,就批准该机构未来发布的任何版本,那么整个策略就变成了“相信发布者所有权”。对于低风险本地工具,这或许可以接受;对于能够使用生产凭据的代理,这远远不够。

第三条路径是本地进程操控。已签名应用可能加载插件、继承环境变量,或由提供异常参数的父进程启动。macOS 的保护措施可以减少某些篡改方式,但审批系统仍需显示真实的进程上下文。证据相互冲突时,应拒绝请求并检查机器,不要因为机构字符串熟悉就自行编造一个良性解释。

第四条路径是权限过大。身份确认无误的客户端,仍可能使用一个允许它执行远超任务所需操作的凭据。应把凭据权限限制到代理需要访问的 API、主机、仓库和环境。代码签名可以告诉你哪个客户端使用了凭据,却无法事后缩小凭据权限。

因此,“已签名就等于安全”是一条有害建议。它听起来简单,也能减少提示,但会鼓励人们在没有目标、操作摘要或会话边界的情况下批准请求。签名信号能帮助人类区分已知代码和未知代码,却绝不能抹去决策的其他部分。

不可逆或高影响凭据应逐次调用审批

当一次请求可能造成会话审批不应默默允许的后果时,每次使用凭据都应要求人工决定。判断标准是操作影响,而不是代理可执行文件看起来是否可信。

对能够写入或删除生产数据、改变身份或权限、建立外部承诺、发布软件,或进入敏感 SSH 环境的凭据,应启用逐次调用审批。操作员在使用时应看到凭据标签和目标。像“使用密钥”这样的通用提示,会迫使人们在压力下记住过多信息。

低影响工作仍应保持易用。如果开发者每次读取测试 API 都必须审批,就会学会不阅读提示直接点击。这会削弱控制,也让真正重要的提示更容易被忽略。当代理针对开发目标执行重复且范围明确的工作,且进程身份符合预期时,会话审批很合适。

决策阶梯应简单到让人经历忙碌的一周后仍能解释清楚。首先,锁定的凭据保管库拒绝所有操作。接着,新代理进程需要会话授权。最后,选定的凭据每次使用都需要审批。不要把这些决定埋进只有一个人能解释的自定义策略语言中,隐藏例外正是共享机器规则逐渐失效的地方。

Sallyport 直接采用这三种控制:锁定保管库会拒绝操作,新代理进程默认请求会话授权,选定凭据可以要求每次使用都审批。其审批卡首先显示进程的代码签名机构,这对于多人共用一台 Mac 的情况是正确的起点。

将审批和调用作为两个独立事件审计

让密钥远离代理
将 API 密钥和 SSH 密钥加密保存在 Sallyport 保管库中,不让代理接触凭据。

代理会话和单次操作需要分别记录,因为单独任何一种记录都无法回答所有事件问题。会话记录说明哪个进程何时获得授权,以及授权何时结束。操作记录说明它在批准后尝试了什么、针对哪个通道或目标,以及返回了什么结果。

实用的调查顺序如下:

  1. 找到请求访问的进程对应的会话记录。
  2. 检查用户账户、可执行文件路径、签名机构、父进程和审批时间。
  3. 找出该会话期间发起的调用,并将目标与分配的任务比较。
  4. 如果会话仍处于活动状态,先撤销它;如果调用显示出滥用迹象,再停用或轮换受影响的凭据。
  5. 在修改项目文件或重新安装客户端前,保留相关记录。

顺序很重要。团队常常一开始就阅读代码,结果丢失了实际发生过什么的证据。应先确认授权与操作的先后关系,再检查可执行文件、仓库状态、Shell 历史和相关配置。

防篡改证据在这里很有价值。如果本地进程可以重写审计记录,而该进程本身又属于正在调查的事件,那么这类记录提供的保障很少。哈希链可以让验证者发现记录序列中的修改或删除,但无法证明所有可能发生的事件都被捕获。必须准确说明这个限制,防篡改证据不是全知全能。

Sallyport 将会话和活动日志记录在同一份加密、哈希链式审计日志中,sp audit verify 可以离线检查链条,无需保管库凭据。这让事件后例行验证,或将记录交给其他审查者,都更加实际。

让审批决定可复现,而不是依赖个人判断

团队应该能够解释某个代理会话为何获得批准,而不依赖点击审批者的记忆。为每个允许的代理客户端写一份简短的审批配置,并将它放在仓库操作说明附近。

配置应列出预期应用或可执行文件路径、标识符、签名机构、正常父进程、允许的用户账户、可用环境,以及每次调用都需要决定的凭据。这不是为了形式主义,而是为新工程师提供可观察的标准,也让值班人员能够拒绝异常请求,而不用争论个人偏好。

出现以下变化时应审查配置:代理客户端更新、团队采用新的运行时包装器、凭据获得更强权限,或原本本地的流程开始访问共享服务。如果签名身份发生变化,应通过团队信任的软件来源验证这一变化。不要先点击一次、打算以后再调查,从而让异常机构变得“正常”。

在共享 Mac 上进行一次有意设计的失败演练。从预期账户启动已批准客户端,确认显示的签名机构和会话行为。然后通过已签名解释器启动未批准脚本,从不同账户启动客户端,并请求一个标记为逐次调用审批的凭据。每种情况下,正确行为都应让操作员一眼看懂。如果提示看起来过于相似,就在真正犯错前改进显示的信息。

代码签名之所以有用,是因为它把模糊的进程名称替换成了可以检查的证据。让它的职责保持在这里。为眼前的工作批准已知进程,限制凭据权限,并让可疑会话在证据仍然完整时即可撤销。

常见问题

代码签名能为 AI 代理进程证明什么?

它是附加在可执行代码上的身份声明。在 macOS 上,有效签名可以识别签名颁发者,并检测签名内容是否在签名后发生变化。但它不能证明软件安全、适合你的代码仓库,或正在按预期范围运行。

Apple Team ID 足以批准代理会话吗?

不能。Team ID 只能说明代码由哪个 Apple 开发者账户签名,范围太宽,不能单独作为审批依据。还要检查可执行文件或应用包的标识、签名颁发者、进程路径,以及该身份是否与团队明确允许的工具一致。

我应该批准未签名的本地 AI 代理吗?

把未签名进程视为需要更严格审查的例外,而不是因为它在本地运行就自动安全。确认它由谁构建、来自哪里、由哪个父进程启动,并在团队建立可重复的识别方式前,将它限制在低影响任务中。

可以信任运行代理脚本的已签名 Shell 进程吗?

通常不应该。Shell 进程可能只是从任意目录运行脚本的通用解释器,Shell 的签名几乎无法说明脚本本身的情况。批准前要检查命令参数、工作目录、父进程和脚本来源。

codesign 验证和 Gatekeeper 有什么区别?

有效签名表示 macOS 能够在相应验证条件下验证已签名代码及其签名链。Gatekeeper 评估还会检查来源和系统策略,但两者都不会评估代理收到的提示、环境变量、已安装扩展或它将请求执行的操作。

代理什么时候需要每次调用都审批?

当进程身份与已批准工具一致,且请求的操作符合当前监督的工作时,可以批准一次会话。若凭据能够修改生产数据、转移资金、改变访问权限、发布构建产物或暴露敏感记录,就应要求每次使用都重新审批。

已批准的代理进程重启或被替换后会怎样?

进程被替换后会产生新的进程身份,应要求重新决定会话。如果审批机制无法区分替换后的进程与原进程,请先撤销现有会话并调查,再允许继续操作。

团队如何安全地共用一台开发 Mac?

先分开工作空间和账户,不要试图从一台拥挤的机器上推断用户意图。为每位开发者建立独立的 macOS 账户,使用彼此独立的代理进程,并限制凭据权限,避免一次误批就能触及所有环境。

有效签名意味着代理值得信任吗?

不能。开发者账户可以为许多无关应用签名,已签名应用也可能有缺陷,或在获得授权后执行有害行为。签名能帮助你更准确地判断发起请求的身份,但不能替代对请求操作的审查。

代理审批的审计记录应包含什么?

记录时间、进程身份、签名颁发者、会话结果、请求访问的目标或主机、凭据标签和操作结果。将会话记录与单次操作记录分开保存,因为要回答“是谁执行了这件事”,通常需要结合两类记录。

Sallyport

Sallyport 替你的 AI 智能体执行 API 调用和 SSH 命令。密钥留在你 Mac 上的本地密钥库里;每次运行由你批准,每个操作都落入一份密封的审计日志。

© 2026 Sallyport · 依据 Apache-2.0 开源 · Oleg Sotnikov