阅读需 8 分钟

为什么相似的审批请求很危险?

相似的审批请求可能授权完全不同的操作。了解如何设计和测试审批流程,让用户准确看到自己正在批准的请求。

为什么相似的审批请求很危险?

只有当用户能看清一次点击究竟授权了什么时,人工审批才有安全价值。如果两个请求在屏幕上看起来一样,但正文、标头、运行身份或命令不同,那么审批就成了表面功夫。用户并没有检查这项操作,只是认出了一个熟悉的标签,然后继续点击。

在代理工具中,这种问题很容易出现,因为可见摘要通常从最不重要的字段开始:标题、主机名、方法,也许再加一条简短命令。这些字段能帮助用户快速定位,但不能定义权限。向同一端点发送 POST,既可能创建无害的草稿,也可能发布生产变更。SSH 命令既可能打印版本,也可能删除目录。即使 URL 中的每个字都没变,只要请求带着不同的租户标头,就可能越过边界。

我见过审批流程因为一些善意的设计而失去作用。有人希望减少打扰,于是把相似调用归为一组。有人希望卡片更整洁,于是折叠请求正文。有人希望分析数据使用稳定标签,于是把同一个标签用于一类操作中的每个请求。等审核者看到第十张熟悉的卡片时,系统已经让他们形成了一个习惯:细节并不重要。攻击者或存在缺陷的代理只需要藏起一个差异。

相同标签可能隐藏不同权限

标题和目标地址只是线索,不是用户正在授权的对象。把它们当作身份,会形成一类碰撞:许多不同操作被压缩成一个容易识别的提示。

设想一个部署服务,它只有一个端点:POST /v1/releases。代理先提交一个发布草稿,稍后再请求发布。如果两张卡片都写着“发布请求”,并显示 api.example.test,审核者就必须打开详情抽屉,阅读正文才能区分它们。实际上,重复出现的卡片会让人觉得抽屉里的内容每次都只是同样的噪音。发布请求往往就在这种习惯形成之后到来。

更常见的工作中也会出现同样的问题。源代码管理 API 可能使用同一条路径来更新拉取请求标题、修改合并设置或替换审核者。云 API 可能通过一个字段,在同一资源路径下区分试运行和实际变更。工单系统端点可能根据标头,把内容写入私有事件记录或公开状态页。目标地址只能告诉你请求送到了哪里,不能告诉你远程服务会做什么。

对于命令,虚假的相似性通常始于一条友好的显示文本。下面这些命令不应该共享审批身份:

  • find build -type f -deletefind build -type f -print
  • 从个人克隆仓库和发布检出目录运行的 git push origin HEAD
  • 使用创建草稿的 JSON 正文和使用发送消息的 JSON 正文执行的 curl -X POST
  • 使用只读账户和使用有权修改服务文件的账户执行的 ssh deploy@host

人们容易混淆的是相似与等价。相似表示两个操作有足够的共同点,可以在视觉上归为一组。等价表示一次审批确实可以覆盖另一个操作。前者是展示选择,后者是在授予权限。把两者混在一起,紧凑的界面就会在无声中扩大权限范围。

提示可以为了易读性把重复操作归为一组,但不能让这种分组决定之前的点击是否适用于后续操作。授权检查需要使用比卡片标签更严格的对象。

审批必须绑定实际执行的请求

获得批准的对象应是执行器将要发送的操作的规范化描述,以及发送请求时使用的身份上下文。只要任何与权限有关的字段发生变化,系统就必须再次询问,或者明确指出变化的字段并要求用户重新决定。

对于 HTTP,这种描述通常包括方法、协议和主机、规范化路径、查询参数值、正文的字节内容或明确定义的规范化正文形式、选定的标头、凭据引用,以及代理运行身份。不要为了做到这一点,就把秘密值放进显示内容或日志中。像 payments-production 这样的凭据引用可以标识权限,却不会暴露令牌。执行器可以将审批绑定到它实际使用的内部保险库记录。

标头需要得到比大多数界面更仔细的处理。有些标头只影响传输层装饰,另一些则会选择组织、启用管理模式、设置幂等范围、选择区域账户,或改变接收方解释正文的方式。应明确列出那些可以省略的字段,因为它们不会影响远程操作。其余字段都应保留在规范化身份中,直到有人证明省略它们不会带来风险。

对于命令,应根据执行计划构建描述,而不是根据 shell 字符串构建。执行计划包括可执行文件、参数数组、工作目录、适用时的目标主机、用户身份、凭据引用,以及会影响行为的环境变量。shell 字符串是一种有损的展示格式。引号规则、展开结果、继承的变量和变化后的工作目录,都可能让一条看起来相同的命令变成另一项操作。

这时,团队经常会提出一种宽松规则,例如“目标相同、动词相同,就算足够接近”。这种规则很受欢迎,因为它能快速减少提示,让演示看起来更顺畅。但它是错的,因为 HTTP 动词描述的是宽泛类别,不是实际后果。POST 不是权限类别,ssh 也不是。

使用请求指纹绑定决定,但不要把指纹作为唯一证据展示。摘要非常适合在审批引擎内部检查是否相等,人则需要看到导致结果的字段。两者都提供:在卡片上展示结构化、易读的差异,并在卡片背后保留精确的规范化身份。

重复并不能证明调用安全

一次重试可能与之前的尝试共享所有执行字段,但它与无关的重复操作仍然需要不同处理。系统应先判断重复的原因,再决定是否可以跳过提示。

最安全的重复调用情况是传输层重试:执行器知道第一次请求从未到达远程服务,或者远程服务提供了与精确操作绑定的幂等机制。即便如此,审批引擎也应将重试绑定到原先批准的操作,并记录重试关系。不能只因为最近的历史记录中某处出现了两个相同哈希,就简单放行。

网络故障结果不明时发生的重复调用则不同。连接中断之前,远程服务可能已经接受了第一次调用。再次发送可能导致第二封邮件、第二个问题单,或重复扣费。审核者需要看到明确说明:上一次尝试的结果未知。一张只写着“正在重试请求”的熟悉卡片,隐藏了他们需要做的判断。

来自另一个运行的后续相同操作又是另一种情况。它可能来自新的代理进程、新代码、复制出的终端会话,或同一台机器上的另一个人。按运行进行授权,是因为进程身份和生命周期都很重要。跨运行复用旧审批,会把一次有边界的决定变成长期授权。

在模型中使用相互独立的状态:

  1. 用户批准了一项精确的计划操作。
  2. 执行器尝试执行该操作,并知道请求是否已发送。
  3. 远程端报告成功、失败或未知结果。
  4. 后续操作声称与第一项操作存在关系。

不要把这些状态压缩成一个绿色徽章。审批记录的是意图,传输结果记录的是送达证据,远程响应记录的是结果。它们回答的是不同问题,审计记录应保留这三者。

正文需要语义检查和字节级绑定

请求正文可能承载一次 API 调用的全部后果,因此把它隐藏在“已附加负载”这类通用标签后面,是设计错误。应先提供易读形式供审核,再将审批绑定到执行器实际发送的精确表示。

JSON 会让这件事变得棘手,因为它的外观和含义可能不同。对象字段的顺序通常不会改变接收方的解释,但数组顺序可能完全改变结果。空白通常不重要,但包含空白的字符串则不同。服务进行宽松或自定义解析时,写成 1 的数字可能会与 1.0 得到不同处理。不要自创一个通用 JSON 规范化器,然后假定它能保持语义不变。

一种实用方法分为两层。第一,保留即将发送的正文字节,并对其计算加密摘要,用于授权绑定。第二,将已知内容类型解析成展示树,让字段易于阅读。如果解析器无法安全解释正文,就显示转义后的片段、长度和摘要;当调用具有实际后果时,还应要求用户展开正文后再批准。

使用专门设计来迷惑粗略审核的样例对进行测试。下面两份测试文件共享标题、方法和目标地址,但必须产生不同的指纹,并在审批视图中显示出差异。

{
  "title": "Release request",
  "method": "POST",
  "url": "https://api.example.test/v1/releases",
  "headers": {"Content-Type": "application/json"},
  "body": {"version": "2.4.1", "state": "draft"}
}
{
  "title": "Release request",
  "method": "POST",
  "url": "https://api.example.test/v1/releases",
  "headers": {"Content-Type": "application/json"},
  "body": {"version": "2.4.1", "state": "published"}
}

测试不应只断言 fingerprintA != fingerprintB。还应断言渲染后的卡片明确指出 state 发生变化,用于草稿样例的审批无法通过发布样例的验证,并且活动记录保留正文摘要和字段级展示摘要。最后一项断言可以发现一个常见的修复遗漏:工程师修好了授权检查,却让审核者和调查人员继续面对没有帮助的日志。

二进制正文和表单正文也需要同样严格。多部分上传可能保留文件名和内容类型,却换掉上传的文档。编码后的表单可能把字段中的 role=user 改成 role=admin,而紧凑卡片完全不显示该字段。只要远程操作重要,正文就重要。

标头和凭据会造成隐藏的范围变化

验证操作轨迹
加密审计日志采用哈希链保护,即使没有保险库密钥,也能离线验证。

用于发送请求的凭据属于操作的一部分,即使 URL 和正文逐字节都没有变化。一张写着“更新发票”的卡片,如果隐藏了使用的是沙箱凭据还是生产凭据,就等于要求审核者盲目批准。

绝不要显示 bearer token、密码、私钥或自定义秘密标头。应为每条秘密记录分配稳定、易读的标签,并显示标签、账户类别,以及操作者主动配置的权限警告。审核者可能不需要看到令牌文本,但必须知道凭据是否从 billing-read 变成了 billing-admin,或操作是否改用另一个 SSH 账户执行。

自定义标头最容易制造意外,因为它们常常看起来只是传输细节。X-Organization 值可以把原本熟悉的请求重定向到另一个租户。X-Mode: live 标头可以让操作从模拟变成真实执行。Idempotency-Key 可以决定接收方把请求视为重放还是新指令。当这些字段会影响范围时,应将它们放入渲染后的比较区域。

脱敏必须保留变化检测能力。把所有敏感值替换成 *** 只有在界面仍能指出秘密引用发生变化时才可接受。如果两个不同的秘密都显示为 ***,你就亲手制造了一个相似请求碰撞。应显示非秘密标签或稳定的内部别名,绝不能显示秘密本身。

有用的测试样例集应一次只改变一个维度:请求不变但凭据引用不同,凭据不变但组织标头不同,标头不变但查询值不同,以及所有变化同时发生。单维度样例能找出规范化器遗漏的字段,组合样例则能发现界面代码在第一个差异之后截断其余变化。

会话能说明是谁再次发起请求

审批也是对调用者的判断。同一操作由新的代理进程发起时,不会因为共享工作目录或可执行文件名,就自动继承你对之前进程的信任。

代码签名权限很有用,因为进程可以声称任何友好的名称。但这仍不意味着来自该进程的所有请求都相同。受信任的进程可能运行经过修改的提示,读取被投毒的仓库文件,或遵循操作者并未打算执行的指令。进程身份回答“是谁启动了这次运行”,请求绑定回答“这次运行将做什么”。两者都需要。

在进程启动时记录会话标识,并将其附加到每项提出和执行的操作上。进程退出后,其临时授权也应随之结束。如果出现新进程,即使标题和目标地址与最近的操作相同,也要显示新的审批。不要因为两个进程拥有相同的可执行文件路径,就认定它们是同一个进程。更新器、复制的二进制文件和本地开发构建都会让这种捷径变得不可靠。

对长期运行的工具要保持谨慎。一个运行数日的进程并不会因为尚未退出,就值得获得无限期的全面审批。对于敏感凭据或具有不可逆后果的操作,应要求每次调用都获得批准。对于低风险的重复工作,可以将权限限制在明确的会话内,并在日志中显示该会话。操作人员应能直接撤销正在产生异常请求的运行,而不必在更宽泛的账户设置中寻找。

Sallyport 默认采用按会话授权,也可以要求每次使用选定保险库条目时都进行确认。相比一堆事故发生时无人能够还原的例外规则,这种明确的划分更容易理解。

SSH 命令需要执行计划,而不是漂亮的字符串

审批代理运行
每个新的代理进程都会获得与其代码签名权限绑定的会话审批。

Shell 文本特别容易伪装成它自己。审核者看到的是一条紧凑命令,而 shell 可能会解析别名、展开变量、继承目录,并使用显示内容中没有指明的凭据进行连接。

如果执行器接受直接的参数数组,就保持这种方式。将可执行文件和每个参数分别展示,再补充解析后的工作目录、主机、远程用户和凭据标签。如果必须调用 shell,就明确说明这一点,并显示精确的 shell 程序和脚本字节。美化 sh -c 内容却隐藏 sh -c 本身的卡片,恰恰遗漏了最重要的部分。

下面这些命令看起来相关,但授权的行为有实质差异:

ssh [email protected] 'systemctl status web'
ssh [email protected] 'systemctl restart web'

在这个简单样例中,动词差异是可见的。真实故障往往更难察觉:变量展开成另一个主机,远程路径来自环境变量,或者命令替换从代理先前修改过的文件中读取指令。尽可能在展开后捕获值。如果展开发生在远程端,或无法安全解析,就说明这一限制,并要求用户更谨慎地审批。

不要把读操作和写操作当作总能从命令文本中推断出的属性。cat 可能触发带有副作用的设备读取。看似无害的客户端也可能运行远程钩子。能使用语义明确的命令族时,应优先使用它们;面对含义不明确的 shell 执行,则要求人工审批。假装能够精确分类每条命令,只会制造虚假的信心。

用碰撞样例对测试边界

审批测试应从用户可能误认为同一操作的样例对开始。只验证正常流程,即请求能获批并发送出去的单元测试,几乎无法应对这类失败。

先列出所有与权限有关的维度,再为每个维度制作一组配对样例。大多数情况下固定标题和目标地址,目的是证明引擎不会错误地把这些方便的标签当成身份。

变化维度要测试的样例对预期结果
正文草稿与发布字段新审批,并显示正文差异
标头一个组织值与另一个组织值新审批,并显示租户
凭据staging 记录与 production 记录新审批,并显示凭据标签
会话新进程发出的相同请求新运行审批
命令上下文参数相同但工作目录不同新审批,并显示目录

在三层运行这些样例。在规范化层断言身份不同。在授权层尝试使用针对样例 A 签发的审批提交样例 B,并预期请求被拒绝。在界面层获取快照或结构化无障碍捕获结果,并断言变化字段无需打开二级视图就能显示。只有点击几次后才出现的差异,大多数审核者都会错过。

然后加入变异测试。在测试分支中逐个从规范化身份中移除字段,并确认碰撞测试会失败。有人重构请求对象后,可能悄悄停止将标头、工作目录或凭据标签传入审批适配器。这个做法听起来很琐碎,但它能让这种遗漏立即暴露。

Sallyport 的操作路径会将秘密保存在加密保险库中,并把结果返回给代理,而不是返回秘密材料。这种分离有利于测试,因为测试样例可以通过标签引用凭据记录,同时确认代理从未收到实际凭据。

一次看似无害的首次调用也可能开启失败

让保险库成为关卡
保险库锁定后,Sallyport 会直接拒绝所有 HTTP 和 SSH 操作。

设想一个负责维护发布说明的代理。它通过一个熟悉的端点创建草稿,操作员检查正文后批准了第一张卡片。随后代理对同一草稿进行几次小幅更新。卡片标题仍是“发布请求”,主机也没有变化,操作员逐渐认为每次审批都是例行流程。

后来,一条仓库指令告诉代理“完成发布”。代理只把 statedraft 改成 published,并添加了一个选择线上组织的标头。如果界面只显示标题、主机和方法,最后一张卡片看起来就和之前完全一样。如果审批缓存按标题和目标地址对调用分组,系统甚至可能不会再次询问。

这个故事不需要模型遭到入侵,也不需要戏剧性的漏洞利用。它只需要一个遵循错误指令的代理,一个已经习惯重复提示的审核者,以及一个隐藏了两个效果变化字段的界面。这些条件在日常自动化中很常见。

修复必须覆盖整条路径。让变化后的状态和组织清晰可见。将审批绑定到这两个值以及凭据记录。即使其他部分完全匹配,也要把线上请求视为新操作。以操作员日后能够检查的形式记录提出的操作、审批决定和已发送请求的身份。只修复卡片或只修复缓存,都会给碰撞留下另一条路径。

摩擦应放在有意义的差异上

只要系统诚实地展示例行操作,并且只在权限变化处增加摩擦,人们就能批准许多日常操作。这需要一条清晰规则:在已知且受限的情况下,完全相同请求的重试可以免去提示;正文、标头、凭据、会话、命令上下文或目标发生变化时,都必须重新决定。

不要用更多警告颜色、更长的文字或更吓人的确认按钮,来弥补卡片信息模糊的问题。这些手段会拖慢日常工作,并让人习惯忽略视觉噪音。应将与决定有关的值放在稳定布局中,用直白语言标出变化,并保留足够细节,让用户能将当前请求与上一次批准的请求进行比较。

如果发现一组操作确实可以共享审批,就在代码和测试中记录等价性声明。例如,幂等重试可能携带一个不可变的操作标识,而远程服务保证只执行一次。这是一项范围狭窄、且有证据支持的声明。“它们在界面上看起来相似”不是证据。

先收集最近十次标题和目标地址相同的审批。比较它们的规范化请求字段、凭据标签和会话 ID。如果发现审核者在主卡片上看不到的差异,那么你已经发现了授权缺陷,即使它还没有被利用。

常见问题

什么会让两个审批请求看起来很像?

它们共享显眼的标签,却改变了点击背后的权限。目标地址可以保持不变,但正文可能修改付款金额,标头可能切换租户,命令也可能指向另一条路径。

审批系统应如何识别唯一请求?

使用已执行请求的规范化表示,而不是卡片标题。应包括方法、规范化后的目标地址、正文摘要、凭据身份、相关标头、会话身份和执行通道。

第二次完全相同的 API 调用可以自动批准吗?

不能。重复请求可能是合理重试、意外重复操作,也可能来自另一个代理运行。审批界面必须先说明属于哪种情况,用户才能把它当作例行操作。

哪些 HTTP 标头应显示在审批提示中?

只显示会影响授权、路由、租户范围或请求解释方式的标头。凭据值需要隐藏,但应显示凭据标签,并说明标头值是否相较之前的请求发生变化。

请求哈希可以替代审批卡中的请求详情吗?

摘要适合帮助比较,不能证明两个请求具有相同含义。应保留原始结构化字段供检查,尤其是正文包含数组、重复字段或转义文本时。

为什么代理会话与审批有关?

将其作为单独的身份维度进行测试。相同命令由新进程、新批准的运行或继承的 shell 发出时,即使文本不变,也可能对应不同的信任决策。

如何测试面向 AI 代理的审批界面?

把审批视为授权边界的一部分,用容易混淆的对抗性测试样例对进行测试。视觉检查很有必要,但还要断言后端不会在权限发生变化后继续复用原审批。

批准 SSH 命令前,人必须看到什么?

对于 shell 操作,应包括可执行文件、参数、工作目录、会改变行为的环境变量、目标主机,以及用于身份验证的身份。美化命令格式只有在这些字段仍然彼此区分时才有帮助。

为什么审批提示出现得太频繁会有危险?

审批提示过于频繁会造成审批疲劳,让人习惯于直接点击。更好的设计只在同一受限请求由同一运行重复执行时记住决定,同时让每个差异都清晰可见。

已批准操作的审计日志应保留什么?

防篡改记录能帮助你事后还原发生了什么,但无法把含糊的审批变成知情决定。应记录呈现给用户的摘要和请求的规范化身份,方便调查人员进行比较。

Sallyport

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

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