阅读需 8 分钟

使用会安全失败的虚假凭据测试 MCP 工具

使用虚假凭据和一次性账户测试 MCP 工具,让团队能够验证成功、拒绝、超时和清理路径,同时避免生产风险。

使用会安全失败的虚假凭据测试 MCP 工具

安全地测试 MCP 工具,不能只是把生产令牌替换成一个名为 TEST_TOKEN 的字符串。工具可能会解析这个虚假值,返回友好的成功消息,但代理第一次遇到真实的权限边界、审批拒绝、响应缓慢的服务商,或只完成一半的写入操作时,问题才会暴露出来。

测试环境必须同时证明两件事:工具发送了预期请求,而且即使测试出错,也不会造成真实的生产后果。一次性账户和虚假凭据分别解决其中一个问题。请把它们视为两种独立的控制措施。

虚假凭据测试的是解析,不是权限

虚假凭据只能证明工具能正确处理符合凭据格式的值。它不能证明服务商会接受该凭据,不能证明凭据拥有预期的权限范围,也不能证明撤销后的值会干净地失败。

之所以容易混淆,是因为很多团队把所有非生产密钥都称为虚假密钥。实际上有三种差别很大的情况:

  • 合成字符串只存在于本地存根中,无法在任何地方完成身份验证。
  • 测试凭据可以向真实服务商完成身份验证,但只在一次性租户或项目内使用。
  • 受限的生产凭据可以访问生产环境,即使它的权限范围看起来很小。

第一种适合单元测试和契约测试。第二种适合集成测试。第三种不应出现在自动化代理测试套件中。能够读取生产客户记录的凭据,已经越过了你原本想保护的边界。

让虚假值接近代码实际收到的格式。如果 API 客户端拒绝没有前缀或长度不足的令牌,就使用能通过本地验证的合成值。不要复制真实令牌后只修改一个字符。人们会把测试夹具粘贴到问题跟踪系统、终端记录和聊天内容中。几乎真实的密钥带来全部处理风险,却没有任何测试收益。

好的夹具名称应当说明它没有哪些权限。例如,stub_token_no_networktoken123 表达得更清楚。像 billing_test_writer 这样的测试凭据引用,说明实际值由密钥存储系统解析,而不是由代理解析。保持引用稳定,并在需要时轮换底层测试凭据。

不要把模拟成功误认为授权成功。存根返回 200,只是接受了你编写进去的契约。这能证明你构造请求的方式正确,却无法说明服务商的权限范围模型。

区分无效输入、拒绝和运行故障

对于错误参数、被拒绝的操作和损坏的依赖服务,代理需要不同的处理建议。如果 MCP 工具把三者都变成 request failed,代理就会重试本应停止的操作,也会放弃本来可以修复的操作。

使用一组简洁的结果类别,并在工具结果中保留它们。我通常使用五类:

  • 验证错误:代理提供了无效或不完整的参数,代理可以修正调用。
  • 身份验证失败:凭据缺失、过期、格式错误或已撤销,重复相同调用没有帮助。
  • 授权拒绝:身份已通过验证但没有权限,或人工拒绝了审批,代理不得绕过边界继续试探。
  • 冲突:请求有效,但无法应用到当前资源状态,代理可能需要先获取最新状态。
  • 运行故障:超时、连接失败、速率限制或服务商故障。如果操作可以安全重试,重试可能有意义。

HTTP 定义了这一区分中常见的部分。RFC 9110 将 401 Unauthorized 描述为身份验证质询,尽管这个名称在历史上容易造成误解;403 Forbidden 则表示拒绝履行请求。你的服务商可能不会严格使用这些状态码,所以也要测试响应正文和文档中的错误码。不要只根据状态码决定代理行为。

Model Context Protocol 的工具结果支持 isError 标志以及内容。工具调用失败时应使用它,同时把可执行的错误类别放进客户端预期的文本或结构化内容中。传输层的 JSON RPC 错误和工具层错误也不一样。JSON RPC 错误应保留给格式错误的协议请求或不可用的方法。当工具已经运行,但上游操作失败或被拒绝时,应返回正常的 tools/call 结果,并将 isError 设为 true

这样的响应格式能让代理采取具体行动:

{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "authorization_rejected: identity billing_test_writer cannot create invoices in tenant test-acme. Request a role change or stop."
      }
    ],
    "isError": true
  }
}

不要在这个结果中加入 bearer 令牌、授权标头、完整的上游请求或服务商原始错误。错误消息属于代理的上下文窗口,因此可能会被你无法控制的系统保留。错误需要足够详细,以便指导行为,但不需要变成取证记录。

一次性账户需要明确的硬边界

只有在无法连接生产资源时,一次性账户才是安全的。单独的电子邮件地址和一个 test 标签并不能建立这种边界。

从服务商提供的最强隔离单元开始。它可能是独立的组织、租户、云项目、数据库或自托管实例。把测试账户放入这个隔离单元,并确认账户无法通过标识符、共享角色或默认配置值切换到生产单元。

然后只授予完成预期测试所需的权限。如果工具要在测试中创建发票,测试身份需要创建和列出测试发票的权限,但不需要导出权限、管理员权限或共享支付配置的访问权。测试权限过宽很常见,因为它能缩短配置时间,却也掩盖了工具实际需要哪些权限。

为测试套件创建的每项资源使用明确的测试运行标记。将它放进受支持的元数据字段、描述、标签或名称中。标记能让清理更安全,也能让泄露的测试资源容易识别。不要删除测试租户中所有碰巧存在的资源,那里可能还有另一名开发者正在复现问题。

最小配置可以如下所示:

run_id: mcp-it-20250308-7f3c
account: [email protected]
allowed_tenant: test-acme
resource_prefix: mcp-it-20250308-7f3c-
cleanup_after_minutes: 90

这份配置能防止一个平凡却代价高昂的故障:由于开发者 shell 中的环境变量覆盖了受版本控制的配置,测试运行器指向了错误的租户。初始化代码应在创建任何资源前获取当前租户身份,并将其与 allowed_tenant 比较。如果不同,就在第一次写入前停止。

一次性不等于匿名或无人负责。指定负责人,记录凭据的签发方式,并明确设置过期时间。被遗忘的测试身份仍然是一个拥有访问权的身份。

围绕可观察请求建立契约

好的契约测试会检查工具发送的请求、返回的结果,以及它拒绝暴露的信息,而不只是断言某个函数被调用。

在客户端前放置本地 HTTP 存根,让它检查方法、路径、标头、查询参数和正文。存根应拒绝意外字段。宽松的模拟对象会让工具不断发送意外参数,直到真实服务商拒绝请求。

对于 create_invoice 工具,聚焦的测试可以发出这样的调用:

{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "create_invoice",
    "arguments": {
      "tenant": "test-acme",
      "customer_id": "cus_mcp_it_7f3c",
      "amount_cents": 500,
      "currency": "USD"
    }
  }
}

存根应期待 POST /v1/invoices,确认授权标头包含合成测试值,并确认请求包含运行标记。它可以返回类似 inv_mcp_it_001 的固定标识符。MCP 结果应暴露发票标识符和状态,但绝不能回显授权标头。

对于每个敏感请求字段,至少写一条否定契约断言。如果模式可能接受调用者提供的 authorizationbase_urlaccount_idtenant 字段,就发送这些字段。确认工具会拒绝或忽略可能把操作重定向到其他账户的值。许多凭据泄露都始于一个看似无害的自定义端点逃生口。

在真正重要的字节层面测试请求序列化。服务商可能区分省略字段和 null、空字符串和缺失值,以及数字和数字字符串。代理经常生成意外的参数形状,尤其是在工具描述没有说明单位时。如果服务商要求最小货币单位,模式应写成 amount_cents,而不是 amount

契约测试也适合强制执行日志卫生。捕获结构化日志事件,并断言其中包含凭据引用或脱敏标记,而不是合成密钥本身。工程师一旦习惯在各处打印虚假密钥,它就会变成真正的泄露问题。

用矩阵测试权限,而不是只测成功路径

快速撤销测试运行
Sessions 日志会记录代理运行,并让你立即撤销正在运行的会话。

单个经过授权的测试身份无法告诉你工具是否正确处理权限。你需要几个权限刻意不同的身份,以及一组明确写出预期结果的用例。

矩阵应小到足以维护。对于写入工具,以下情况通常值得保留:

身份状态请求操作预期结果
测试租户中的写入者创建带标记的记录成功
测试租户中的读取者创建带标记的记录授权拒绝
另一个测试租户中的写入者在目标租户中创建记录授权拒绝
已撤销的凭据列出带标记的记录身份验证失败
已过期的凭据创建带标记的记录身份验证失败

这个矩阵能发现一个常见缺陷:工具检查令牌是否存在,却从不检查令牌实际代表哪个租户。成功路径会通过,因为写入者拥有广泛权限。当代理在任务中收到租户标识符,而客户端没有将它绑定到所选凭据却直接遵循时,问题才会出现。

条件允许时,应针对真实的一次性服务商运行矩阵中的每个用例。服务商在继承角色、默认范围和延迟撤销方面的行为经常与文档不同。保持用例隔离。如果一个测试升级了另一个测试预期不存在的角色,并行运行就会产生看似授权错误的失败。

不要只在存根中伪造禁止响应,然后就认为套件完成了。存根能确认错误映射器识别 403,真实测试身份才能确认凭据签发流程、服务商配置和工具路由确实产生了真实拒绝。

有一个实用例外:测试无法稳定触发的服务商响应,例如格式错误的 JSON 或无效内容类型。这些情况由存根负责。重点不是追求纯粹,而是明确每项测试提供了什么证据。

在写测试代码前制定写入清理计划

对于每个会改变状态的工具,在编写成功测试前决定如何删除或中和这些状态。如果无法描述清理方式,就选择其他目标或其他操作。

优先选择在短期测试项目中创建记录的操作。避免发送电子邮件、扣款、轮换共享密钥、触发部署或联系真实第三方的测试调用。服务商的沙盒仍可能向你多年前配置的端点发送 Webhook。要检查周边影响,而不只是 API 标签。

使用唯一的运行标记,并在 finally 代码块或等效位置执行清理。清理必须能处理资源从未创建、重试后创建两次,以及账户只完成部分配置的情况。幂等清理能让测试在初始化中途失败时少花很多时间。

值得测试的一种失败情况是:工具发送创建请求,服务商创建记录,却在返回响应前断开连接。代理看到运行故障后重试。没有幂等值,重试就会创建重复记录。没有运行标记,清理就无法安全找到两条记录。

为创建请求提供由运行标识和逻辑操作共同生成的幂等值,而不是由传输尝试生成。第一次尝试和重试必须使用相同值。然后用存根测试连接断开情况:存根记录第一次请求,关闭连接,再次收到相同幂等值时返回成功。断言服务商一侧只有一条记录。

如果服务商不支持幂等性,就让工具在重试写入前使用唯一外部引用执行查找。这种方法在并发时可能发生竞争,因此要记录剩余风险。不要因为通用 HTTP 客户端认为 POST 可以重试,就静默重试资金转移或不可逆写入。

超时和速率限制会暴露糟糕的代理行为

一个能正确处理成功和 403 的工具,在依赖服务变慢时仍可能造成损害。代理通常会为了完成任务而重试。你的工具必须给出有界且真实的响应。

测试请求尚未到达服务器前的连接超时、服务器收到请求后的响应超时,以及服务商返回 429 的情况。这些情况不同。第一种通常意味着没有产生副作用。第二种可能意味着服务器已经完成写入。429 可能包含重试指令,但只有在服务商记录了该字段且工具保留其含义时,才能使用它。

设置较短的测试超时,让套件保持可用,但不要在代码中用测试值替换生产超时。注入时钟或传输配置。硬编码的两秒超时只是测试便利,最终可能在有人部署时变成故障。

断言应覆盖面向代理的行为以及 HTTP 客户端。对于速率限制,返回说明服务商的运行故障,并明确延迟后是否允许重试。对于写入后的响应超时,应说明结果未知,并要求调用方通过幂等值或外部引用查找操作。把这种情况称为普通失败,会诱发重复写入。

不要只通过断言次数来测试重试。记录重试是否复用了幂等值、是否在应等待时等待,以及是否在达到配置上限后停止。一个最终会结束的重试循环,仍可能制造突发流量,耗尽小型测试租户,并掩盖真正缺陷。

审批拒绝必须让目标保持不变

避免在测试中加入策略逻辑
固定的三层控制决策链让审批行为清晰可见,无需配置容易出错的策略规则。

如果人工可以审批代理操作,就在副作用明显的端点上测试拒绝路径。界面显示拒绝,并不能证明执行层已经停止。

设置一个带计数器、标记记录或只追加测试事件列表的一次性目标。启动代理进程,发出工具调用,然后拒绝会话或调用。接着使用独立的测试观察者身份直接查询目标。预期计数应保持不变。

这个测试能发现一种错误的执行顺序:工具先启动上游请求,然后在等待响应时请求审批。如果服务商响应较慢,这种流程在演示中可能看起来正常,却违背了审批的目的。授权决定必须发生在操作分发器打开网络连接或启动 SSH 辅助程序之前。

分别测试全新的代理进程,以及同一进程中的第二次调用。对于按运行授予授权的系统,这是两项不同的安全承诺。还要测试签名身份不同于预期身份的进程。审批提示必须展示足够的来源信息,让批准者能够区分预期代理和任意本地进程。

对于本地 Mac 工作流,Sallyport 会将 API 和 SSH 凭据保存在加密保险库中,并可要求每次新代理运行,或每次使用选定凭据时获得审批。应针对一次性端点测试这些控制措施,而不要因此跳过上游权限测试。

SSH 测试需要可丢弃的目标

SSH 会带来 HTTP 示例中隐藏的失败模式:主机验证、命令引用、环境继承、远程 shell 行为,以及连接断开后遗留的文件。绝不要把自动化代理 SSH 测试指向开发者工作站或共享管理主机。

创建受控测试机器或短期虚拟机,并配置专用账户。为该账户提供不含有用数据的主目录,在可行时限制其命令集,并确保它没有能访问其他系统的凭据。使用专用测试 SSH 密钥,并在环境过期时丢弃它。

测试会破坏简单 shell 构造的命令参数,包括空格、引号、换行字符、以短横线开头的路径,以及看起来像 shell 语法的数据。工具应将参数传递给远程命令,而不是把用户提供的值拼接进 shell 字符串。如果所需的远程接口只能接受 shell 文本,就限制允许的命令语法,并拒绝其他所有内容。

一种安全测试可以要求目标创建一个以运行标记命名的文件,然后读取这个确切文件。拒绝测试可以请求测试账户允许目录之外的路径,并预期目标拒绝访问。运行故障测试可以在命令启动后终止 SSH 连接,然后检查远程进程是否继续运行。

收集退出状态、有长度上限的标准输出和有长度上限的标准错误。不要向代理返回无限量的命令输出。过大的输出会消耗上下文,命令输出还经常包含原本不应离开主机的配置值。

审计证据必须把代理调用和副作用关联起来

测试审批边界
默认要求新代理进程获得一次批准,并在执行任何操作前显示其代码签名权限。

代理测试失败时,你需要知道工具是否发起了调用、服务商是否收到调用,以及清理是否删除了资源。一堆控制台文本无法稳定回答这些问题。

为每次测试运行分配一个标识符,并让它贯穿 MCP 请求、工具日志、服务商元数据和清理日志。不要把凭据放进这个标识符。实用的证据记录应包含工具名称、操作类别、凭据引用、目标租户、请求关联标识符、结果类别和返回的资源标识符。

将代理会话记录与操作记录分开。会话记录说明哪个进程在什么时候发起运行,以及何时撤销了它。操作记录说明发生了哪次外部调用。通过关联标识符将两者连接起来后,拒绝测试就具备了可审计性:你可以证明代理尝试了操作,授权拒绝了它,并且没有相匹配的外部操作记录。

当你使用测试结果审查新的工具边界时,防篡改证据很重要。Sallyport 会从加密、哈希链式审计日志中生成 Session 和 Activity 日志,sp audit verify 可以在没有保险库密钥的情况下离线验证这条链。这不能替代服务商日志,但能让本地测试发现被修改的操作历史。

不要把审计输出当作秘密收集点。记录凭据引用和经过脱敏的属性,并在测试中断言这些规则。审计轨迹应帮助你还原操作,而不应成为窃取相关访问权限的最方便地点。

发布候选版本应逐步获得生产访问权

工具应逐步通过越来越真实的边界:本地合成存根、真实的一次性服务商、被拒绝和已过期的身份、故障注入,以及工作流中存在人工审批时的审批测试。由于沙盒不同就直接跳到生产环境,是团队最终在压力下发现安全行为的常见原因。

保留一套每次变更都运行的简短发布测试,以及一套不那么频繁配置一次性资源的深度测试。短测试应覆盖工具模式、请求构造、脱敏和代表性故障。深度测试应覆盖真实权限范围、生命周期清理、撤销和目标隔离。

在允许新操作访问生产环境前,检查一次刻意拒绝的调用和一次刻意制造的写入超时所提供的证据。这些情况能揭示工具是否尊重边界,以及服务商可能已经执行操作时,工具是否如实告知代理。成功路径很容易安排。真正决定生产访问是受控还是鲁莽的,是拒绝和不确定性路径。

常见问题

可以使用权限受限的真实账户测试 MCP 工具吗?

使用无法访问生产数据或生产计费的独立租户、项目或工作区。为它配置仅用于测试的用户、小额配额,以及与测试场景匹配的访问范围。生产租户中的虚假令牌仍然属于生产访问。

收到 403 响应就能证明 MCP 工具安全吗?

不能。被拒绝的请求只能证明在当前条件下,一条授权路径拒绝了一项操作。你还需要测试参数格式错误、凭据过期、访问被撤销、上游服务中断、速率限制,以及存在人工审批时的审批拒绝。

虚假 API 凭据应该是什么样?

虚假凭据应当具备工具和客户端库所要求的格式,但只能对一次性服务或本地存根进行身份验证。不要把生产令牌前缀、签名材料或复制的凭据值放进测试夹具。要把夹具当作最终可能出现在日志或缺陷报告中的代码来处理。

MCP 工具应该如何报告凭据过期?

工具应返回结构化的工具错误,告诉代理它能否修正请求、稍后重试,还是必须停止。对于过期令牌,应明确说明身份验证失败,需要重新授权。不要返回凭据值、完整授权标头,或包含这些内容的服务商响应。

MCP 工具测试应该使用模拟对象还是真实测试账户?

使用确定性的本地存根测试精确的请求载荷和错误契约,再针对真实的一次性服务运行一小组测试。存根能让超时和格式错误响应测试保持可重复。一次性账户则能发现存根遗漏的假设,例如实际分页、权限范围行为和服务商验证。

如何安全地测试已撤销的 API 令牌?

如果服务商提供撤销接口,就调用它,然后使用同一个凭据引用重新运行相同的工具调用。预期结果应是清晰的身份验证或授权拒绝,绝不能因为缓存而静默成功。同时使用新签发的一次性凭据测试恢复路径。

集成测试后如何清理一次性账户?

为每条一次性记录写入唯一的运行标识,并在清理阶段按该标识删除记录。清理必须能够处理部分初始化,因为失败的测试运行往往会留下最难排查的残留物。如果服务商提供自动过期功能,应启用它,但不要只依赖自动过期。

可以安全地测试执行 SSH 命令的 MCP 工具吗?

可以,前提是目标主机是受控的测试机器,SSH 账户无法连接生产系统。使用专用测试密钥、受限账户,并确保收集到的命令输出安全。不要使用工程师平时的 SSH 密钥进行测试,即使目标看起来无害。

如何测试代理操作周围的人工审批网关?

直接测试操作边界。从预期的代理进程发送一次工具请求,拒绝会话或单次调用,然后确认外部系统没有收到请求。界面上显示拒绝很有用,但外部副作用确实没有发生,才是需要的证据。

虚假凭据可以提交到测试代码库吗?

提交到代码库的测试夹具必须是虚构的、有效期短的,并且不能授权访问任何真实服务。密钥扫描器有帮助,但无法判断某个令牌是否能访问生产环境。可靠的解决方案是架构层面的:生产凭据绝不能参与测试环境。

Sallyport

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

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