阅读需 8 分钟

强制退出测试能证明智能体网关什么?

强制退出测试揭示智能体网关在崩溃后能证明什么,从凭据注入和网络 I/O,到审计提交与恢复。

强制退出测试能证明智能体网关什么?

保存 AI 智能体凭据的网关,必须在进程于最糟糕的时刻消失时仍然安全。顺利路径测试可以证明批准出现了、请求成功了,之后也有审计记录。但它无法证明请求中断时凭据有没有泄露,也无法证明审计轨迹有没有把从未完成的工作说成已完成。

强制退出测试会揭开这些说法之间的空隙:「请求已开始」「凭据已附加」「远程系统已收到」「记录已持久化」。这些是彼此独立的事实。把它们当成一个事件,团队就可能交付一个看起来受控的操作网关,直到一次崩溃把普通重试变成重复部署,或变成无法追踪的出站调用。

对于 macOS 网关,要区分正常退出和进程死亡。Apple 将正常应用终止描述为一种生命周期路径,在这条路径上,应用可以保存状态并运行终止处理。强制终止则可能根本不给它这些时间。Apple 还指出,用户强制退出应用时可能产生 SIGKILL。测试计划必须假定清理处理器、延迟写入和尽力而为的遥测都不会运行。

有用的问题不是「应用能重启吗?」而是:在每个中断点上,你能否准确说明哪些副作用可能已经发生,哪些不可能发生,以及哪些证据能够保留下来?

强制退出必须穿透清理流程

只有在测试真正剥夺网关整理现场的机会时,强制退出测试才有效。如果测试调用了礼貌的关闭函数,等待队列排空,刷新日志,然后退出,那么你测试的是有序终止。这当然有价值,但它并不对应安全工程师最担心的故障情形。

使用两种终止模式,并准确标注它们:

  • 正常终止请求测试取消处理、有序关闭文件,以及界面如何报告中断的会话。
  • 强制杀死测试的是:在没有任何应用清理工作运行时,内存、文件缓冲区、打开的套接字和进行中的工作处于什么状态。

在 macOS 上,测试工具可以用 kill -TERM 测试第一种情况,用 kill -KILL 测试第二种情况。SIGTERM 会给进程处理终止的机会,SIGKILL 不会。不要在结果表中把两种情况都叫作「强制退出」,因为它们回答的是不同问题。

# 找到你为测试启动的网关进程。
pgrep -fl Sallyport

# 正常终止。
kill -TERM 48192

# 突然终止。
kill -KILL 48192

# 确认进程已经消失。
ps -p 48192
# PID TTY           TIME CMD
# 48192 ttys003    0:00.42 gateway-test
# SIGKILL 之后,ps 不应打印任何进程行。

进程 ID 不是测试标识符。每次测试运行都要分配运行 ID、操作 ID 和远程请求 ID,并把这些 ID 同时写入夹具记录和网关记录。没有共享标识符,你最终只能比较时间戳,猜测某个远程 POST 是否属于被你杀死的那次运行。

不要对生产端点进行测试,即使你认为调用没有危害。这项工作的目的,是有意制造结果不明确的情况。搭建一个可以在接收正文前、读取请求头后、记录正文后,或释放响应前暂停的接收器。真实的第三方 API 不会稳定地提供这些边界,还可能加入你没有要求的重试或缓存。

为请求设置明确的中断点

如果实现没有定义「在凭据注入期间」具体指什么,就无法在这个时刻杀死进程。在操作流水线周围加入可观察的暂停点。这些是测试控制,不是策略语言,也不是生产授权机制。

对于 HTTP 操作,可以采用这样的顺序:

  1. 网关接受经过授权的操作描述,并分配操作 ID。
  2. 如果设计要求在网络工作之前记录意图,就写入意图记录。
  3. 在受保护进程内解析凭据。
  4. 构造出站请求并注入凭据。
  5. 打开连接并写入请求。
  6. 接收足够的响应,以判断远程结果。
  7. 写入完成记录或未知结果记录,然后把结果返回给智能体。

在每个编号操作之后的边界设置暂停点。暂停必须能从独立进程观察到。测试专用的本地套接字、命名管道或由测试工具控制的文件描述符都可以。单纯调用 sleep 不够可靠,因为时间漂移会把边界测试变成竞态。

一个简洁的夹具协议可以如下:

{
  "action_id": "fq-2026-07-22-017",
  "hold_after": "request_headers_built",
  "release": false
}

网关在创建请求头后、尝试连接前暂停。测试工具确认状态后杀死进程,再询问接收器是否看到连接。预期结果是零个入站请求和零个携带凭据的请求头。如果接收器在此时看到了请求,说明检查点设置得太晚,或者某个未被记录的后台任务越过了边界。

检查点要足够窄。「网络 I/O 之前」范围太大,因为 DNS 解析、建立连接、TLS 协商和正文传输可能运行在不同任务中。你不必在每个库函数内部设置检查点,但必须有足够清晰的结构,说明远程服务器是否可能已经收到凭据或会改变状态的正文。

SSH 也遵循同样的思路,只是证据不同。在 sp-ssh 收到带凭据的连接请求之前、辅助工具启动但尚未认证时、认证完成但命令尚未开始时,以及远程命令返回但本地完成状态尚未提交时,都设置暂停点。在一次性测试主机上记录远程命令的开始和退出。不要根据 TCP 连接已打开就推断命令已经执行。

注入前杀死进程,远程端不应留下痕迹

最早的故障情形有最清楚的预期结果:如果网关在附加凭据或启动传输之前死亡,任何外部服务都不应看到该操作的任何内容。

这听起来很明显,但实现经常把准备和分发混在一起。客户端库可能在另一个任务获取或格式化请求头时已经开始连接。重试封装器可能在代码写入预期日志记录前就分配了出站请求。指标回调也可能写入包含 URL 查询参数的操作描述,而普通的脱敏路径根本不会处理这个参数。

测试四个分发前中断点:

  • 授权之后、查找任何凭据之前。
  • 查找凭据之后、构造请求之前。
  • 构造请求之后、建立套接字连接之前。
  • 连接建立开始之后、任何携带凭据的字节离开进程之前。

每个中断点都有不同的断言。在查找凭据之前,检查不会泄露值或可能在之后被替换的占位符的诊断信息和日志。查找凭据之后,检查相同输出,并确认秘密从未越过进程边界。建立连接之前,接收器不应有连接记录。连接建立期间,接收器可能看到失败的握手或连接尝试,但不应收到 Authorization 请求头、basic-auth 字段、自定义凭据请求头或 SSH 认证尝试。

这一区别很重要,因为连接尝试不等于经过认证的操作。如果远程边缘记录了连接尝试,不要报告「没有发生任何操作」。应准确报告:没有发送凭据,也没有收到应用请求。安全记录如果抹平了操作员在事后调查中需要的证据,就会失去价值。

Sallyport 的设计值得直接测试这个边界:智能体永远不应持有明文凭据,由应用执行带凭据的 HTTP 或 SSH 操作,再把结果返回给智能体。测试并不是因为智能体没有秘密就结束了。只有确认被杀死的网关也无法把半准备状态的操作变成携带秘密的出站请求,测试才算完成。

注入是本地事件,不是分发证明

凭据注入是团队最容易犯下严重记账错误的地方。代码创建请求头,或把身份交给 SSH 库时,他们记录「已使用凭据」。这个事件只能说明网关准备进行认证,不能证明任何对等方收到了凭据。

至少要区分以下四种本地状态:

状态可以如实声明不能声明
已解析凭据受保护进程为此次操作读取了凭据对等方已经收到凭据
已组装请求进程在内存中创建了带凭据的请求已经打开连接
已开始传输写入进程尝试发送字节远程应用已经处理这些字节
已观察到远程结果进程收到了来自远程端的证据完成记录已经持久化

在凭据解析后杀死一次进程,再在请求对象存在但传输写入尚未开始时杀死一次。两种情况下,智能体都不应收到秘密,远程夹具也不应记录携带凭据的请求。操作日志应显示中断的准备状态,或不应有持久记录,具体取决于你的持久化边界所在位置。不要为了让日志看起来整洁而伪造已完成的操作。

然后在传输库第一次可能写入数据的时刻杀死进程。这是最棘手的测试。虽然本地进程可能已经调用了写函数,但内核、代理、TLS 层或远程服务可能没有收到完整请求。除非接收器有积极证据证明请求已经或尚未收到,否则预期结果应为 outcome=unknown

不要为了让测试更容易而记录完整请求头。那会创建第二条秘密路径。应让夹具根据收到的凭据值计算不可逆的测试标记,只保存该标记。网关可以保存凭据引用或内部凭据标识符。测试期间比较标识符和标记,不要打印凭据本身。

一个有用的夹具记录可以是:

{
  "action_id": "fq-2026-07-22-017",
  "connection_seen": true,
  "headers_complete": true,
  "credential_marker": "sha256:7b8c...",
  "body_complete": false,
  "response_sent": false
}

使用测试专用的凭据,并确保它的值不会出现在夹具之外。即便如此,也不要让它进入截图、shell 历史记录和普通日志。一次性秘密仍然是秘密形态的对象,而测试习惯很容易进入生产代码。

网络 I/O 会产生诚实的未知状态

逐次批准敏感密钥
凭据每次使用都需要 Touch ID 或一键批准时,使用逐次调用密钥。

一旦会改变状态的请求能够离开机器,本地确定性就会在远程系统回应之前结束。这种情况必须影响重试行为、界面措辞和审计语义。

假设一个 POST 请求要求部署服务启动一次发布。网关写出了完整请求,服务持久化了部署任务,但响应到达网关之前,网关进程被杀死。重启后,可能有三种现实:

  1. 服务从未收到请求。
  2. 服务收到了请求,但拒绝了它。
  3. 服务接受了请求,并创建了部署。

本地网关可能不知道是哪一种。情况三中,把记录标为 failed 是错误的;情况一和二中,把记录标为 succeeded 也是错误的。应将其标为 unknown,并保留操作 ID、远程请求 ID、目标、方法,以及本地观察停止的时刻。

这也是盲目重试会造成损害的地方。团队喜欢自动重试,因为瞬时网络故障很常见,顺利演示也看起来很流畅。对于 GET,如果不会造成服务器端记账或速率限制问题,重试通常可以接受。对于 POST、PATCH、远程命令或能写入数据的 API 调用,重试需要幂等机制或后续对账。

远程系统提供幂等功能时,应使用它。提供针对具体操作的幂等令牌,不要在整个智能体会话中重复使用同一个令牌。如果远程 API 不支持幂等,就建立远程查询路径,在重试前回答该操作 ID 是否已被接受。如果两者都不存在,就让人来决定。这不如自动恢复优雅,但比重复执行安全得多。

使用接收器测试以下精确顺序:

网关写入意图记录
网关发送带有操作 ID fq-2026-07-22-017 的 POST
接收器保存操作 ID 和正文
接收器延迟 HTTP 响应
测试工具使用 SIGKILL 杀死网关
网关重启
智能体请求重试
网关在再次发送 POST 前查询接收器中的操作 ID

结果应显示一项已保存的远程操作,以及一条结果中断或未知的本地记录,之后该记录与远程状态完成对账。如果第二个 POST 在对账检查之前出现,测试就找到了重试漏洞。如果日志覆盖了不确定性,只留下干净的成功记录,就找到了审计漏洞。

不要把 TCP 关闭事件当作远程应用没有执行操作的证明。套接字关闭时,对等端可能早已把请求交给应用代码。远程端持久化的操作记录才是重要证据。

审计提交必须有明确的持久化承诺

审计日志包含哈希链,并不代表它自动可信。哈希链可以揭示现存序列中的篡改或删除,却无法找回进程死亡前从未到达持久存储的事件。

用一句话写下承诺。例如:「在网关开始会改变状态的外部操作之前,它会持久记录操作意图;观察到结果后,它会在把结果返回给智能体之前持久记录该结果。」然后测试「开始」和「持久」这两个动词,不要把带时间戳的内存对象当成记录。

POSIX 将 fsync() 定义为把文件数据传输到关联存储的请求,并说明它要么在该操作完成后返回,要么报告错误。标准也提醒,存储保证取决于实现和配置。这不是跳过调用的理由,而是说明你必须准确界定软件能承诺什么,并测试它所能控制的恢复行为。

对于加密哈希链式日志,至少测试以下中断点:

  • 写入候选条目后、持久化屏障之前杀死进程。
  • 持久化屏障之后、内存索引更新之前杀死进程。
  • 完成条目持久化之后、智能体收到响应之前杀死进程。
  • 在压缩、轮换或任何日志投影过程中杀死进程。

每次重启后,运行离线验证命令,同时检查原始审计序列和面向用户的投影。Sallyport 提供 sp audit verify,无需保险库密钥即可验证其加密哈希链式审计日志。这对这组测试很有用,但验证应只是多个断言之一,不能成为整个测试。

预期结果需要有细微区分。如果在持久化边界之前杀死进程后记录消失,而外部操作尚未开始,这可以接受。如果网关已经开始发送会改变状态的请求,而设计承诺了预写意图,那么记录消失就不可接受。出现未知结果的记录通常是正确的。在观察到远程响应前就声称完成,即使测试通常碰巧通过,也是不正确的。

检查恢复时,要把智能体运行历史与操作调用历史分开。一个会话可能突然结束,而其中多个操作的结果各不相同。一条写着「已终止」的运行级记录,不能替代那些已经到达远程系统的请求和仍停留在内存中的请求各自的调用记录。

批准状态不能脱离其对象继续存在

让 HTTP 认证保持隔离
让 bearer、basic 和自定义请求头认证都通过 Sallyport 处理,避免把密钥暴露给智能体。

重启测试的不只是持久性,也包括授权状态。如果网关为某个会话授权了特定智能体进程,那么杀死网关不能把这项授权变成可供新进程重复使用的许可,即使新进程发出了类似请求。

最简单的安全规则是:会话批准属于一个已观察到的进程身份,并随该运行一起失效。重启后,新的智能体进程需要新的会话授权。即使原进程仍然存活而网关重启,只要网关无法重新建立精确的身份绑定,或产品没有明确记录这种行为,也需要新的决定。不要从命令名称、工作目录或友好的进程标签等松散缓存记录中恢复批准。

用两个在命令行上看起来相似、但签名权限或可执行文件来源不同的智能体测试重启边界。批准第一个智能体。在一次操作期间停止网关并重启,然后让第二个智能体发出相同操作。网关必须显示新的批准决定,而不能继承第一个智能体的会话。

逐次调用批准有不同的测试方法。批准卡片显示时杀死网关,然后重启并重复操作。旧的界面事件不能为新的调用授权。在用户批准之后、凭据解析之前杀死进程,然后确认新进程不能复用旧批准。这些测试能发现一个常见错误:持久化了没有绑定到操作 ID、具体会话和过期条件的「approved」布尔值。

Sallyport 的固定控制让这项测试有清晰形态。它的保险库入口在锁定时拒绝操作,会话授权绑定到新的智能体进程,选定凭据还可以要求每次使用都批准。强制退出测试应证明,即使界面、保险库状态和操作流水线在不同时间重启,这些边界仍然成立。

运行测试套件前先建立结果矩阵

把 SSH 密钥留在保险库中
通过内置的无状态 sp-ssh 辅助工具执行 SSH 操作,同时让密钥留在应用中。

仅仅写「在阶段 X 杀死进程」的测试,留下了太多解释空间。创建一个矩阵,用行表示中断点,用列表示可观察结果。在编写测试代码之前先审查预期结果。如果团队无法就应当发生什么达成一致,说明实现还没有定义故障契约。

使用以下列:

中断点远程端可能看到连接吗?远程端可能看到凭据吗?允许的本地操作状态恢复后智能体结果必需证据
查找凭据之前未开始或已中断新批准或重试路径仅网关跟踪
凭据解析之后准备中断新批准或重试路径仅网关跟踪
请求写入期间可能未知重试前先对账接收器记录和本地记录
远程接受之后在观察或对账前为未知重试前先对账远程持久记录
持久完成之后已完成返回缓存或完成对账的结果已验证的审计记录

「可能」和「未知」不是弱点,而是对分布式工作的诚实描述。危险的词是「失败」,尤其是在网关没有证据证明远程服务没有执行操作时。

重复运行每一行,但不要让重复代替控制。随机杀死一百次,仍可能错过日志追加和持久化屏障之间的边界。一次在暂停点上受控的杀死,就能确定这条边界的行为。之后再重复测试,用来发现竞态、调度错误和检查点意外移动的问题。

每次运行都从两端收集产物:测试工具事件时间线、远程夹具的持久操作列表、进程退出信息、网关恢复诊断、审计验证输出,以及智能体可见的输出。把运行 ID 放进文件名。测试失败时,在重新运行前保存证据。第二次运行经常会破坏唯一有用的线索。

把差异当作设计工作,而不是测试噪声

当强制退出测试发现网关日志和远程夹具之间不一致时,不要急着修改断言直到它通过。这种不一致通常就是测试发现的问题。

如果夹具报告请求已接受,而网关没有记录意图,就把持久意图边界提前,或让操作在越过该边界前停止。如果网关报告完成,而夹具没有操作记录,就查明网关是否把本地写入误认为远程结果。如果重启后的智能体可以不经新批准就执行操作,就修复身份绑定,而不是增加更长的超时时间。

最好的结果不是仪表板上满是绿色测试,而是一份操作员在压力下也能使用的故障契约:这项操作确定没有离开机器;这项操作可能已经到达远程系统,需要对账;这项操作已经完成,且记录通过了验证。只有这些类别,才能让崩溃后的决策经得起审查。

每当你修改凭据处理、传输库、日志记录、重试行为、进程监管或批准处理时,都要运行这套测试。网关只有在没有机会解释自己时仍能以可预测的方式行动,才真正值得信任。

常见问题

退出和强制退出智能体网关有什么区别?

正常退出会给进程时间运行清理代码、刷新缓冲区并处理未完成的工作。强制退出的价值在于它不会提供这些机会。两种情况都要测试,但决定故障边界的应是强制终止结果。

取消请求足以测试网关的崩溃安全性吗?

不够。取消请求只能证明客户端能处理正常的取消流程。杀死网关则能测试进程没有机会清理时,凭据、审计记录、进行中的套接字连接和批准状态是否仍然安全。

如果网关在凭据注入前死亡,应当发生什么?

在请求被接受后、网关选择或读取凭据之前杀死它。预期结果很明确:没有出站连接,没有由凭据生成的请求头。如果设计会在这个阶段记录尝试,则日志中应有一条描述中断尝试的记录。

智能体网关应如何处理网络 I/O 期间的崩溃?

除非有能够证明所声明结果的持久证据,否则网关不应声称操作已完成。如果远程系统可能已经收到请求,但网关丢失了响应,就应将结果记为未知,并让操作员与远程系统核对。

请求失败后,应该把原始 HTTP 错误发给智能体吗?

不应这样做。响应正文可能包含智能体不需要接收的秘密、个人数据、令牌或运行细节。只返回足以让智能体继续工作的最小结果,并将传输诊断信息排除在普通的智能体可见输出之外。

意图记录和完成记录有什么区别?

预写记录是在副作用开始前持久记录的操作意图。完成记录则是在副作用发生后,对已观察到结果的证明。如果顺序相反,崩溃可能让审计轨迹讲述一个网络从未发生过的故事。

网关强制退出后,智能体可以安全重试吗?

不可以。中断的 POST 之后盲目重试,可能造成两张工单、两次扣款、两次部署或两次破坏性变更。只有在远程 API 支持幂等性、操作本身天然幂等,或操作员已经解决未知结果后,才应重试。

如何搭建安全的强制退出测试环境?

使用你控制的服务器,或能记录连接时间、请求头、正文接收情况和响应释放时间的 HTTP 测试夹具。加入暂停点,让请求可以在每个阶段停住,然后在暂停点生效时从另一个终端杀死网关。

网关重启后,批准状态应如何处理?

批准应随智能体进程或会话一起失效,而不是仅仅因为网关重启就继续存在。重启不能把一次中断的运行变成静默获批的新运行。当身份边界发生变化时,必须要求新的会话批准。

哈希链式审计日志能证明没有操作丢失吗?

它能证明网关可以检测磁盘上现存审计序列是否被篡改,但不能证明每次尝试的操作都在突然终止前到达了持久存储。你仍然需要受控的中断测试,以及远程一侧的证据来覆盖网络中的模糊窗口。

Sallyport

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

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