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

安全地测试 MCP 工具,不能只是把生产令牌替换成一个名为 TEST_TOKEN 的字符串。工具可能会解析这个虚假值,返回友好的成功消息,但代理第一次遇到真实的权限边界、审批拒绝、响应缓慢的服务商,或只完成一半的写入操作时,问题才会暴露出来。
测试环境必须同时证明两件事:工具发送了预期请求,而且即使测试出错,也不会造成真实的生产后果。一次性账户和虚假凭据分别解决其中一个问题。请把它们视为两种独立的控制措施。
虚假凭据测试的是解析,不是权限
虚假凭据只能证明工具能正确处理符合凭据格式的值。它不能证明服务商会接受该凭据,不能证明凭据拥有预期的权限范围,也不能证明撤销后的值会干净地失败。
之所以容易混淆,是因为很多团队把所有非生产密钥都称为虚假密钥。实际上有三种差别很大的情况:
- 合成字符串只存在于本地存根中,无法在任何地方完成身份验证。
- 测试凭据可以向真实服务商完成身份验证,但只在一次性租户或项目内使用。
- 受限的生产凭据可以访问生产环境,即使它的权限范围看起来很小。
第一种适合单元测试和契约测试。第二种适合集成测试。第三种不应出现在自动化代理测试套件中。能够读取生产客户记录的凭据,已经越过了你原本想保护的边界。
让虚假值接近代码实际收到的格式。如果 API 客户端拒绝没有前缀或长度不足的令牌,就使用能通过本地验证的合成值。不要复制真实令牌后只修改一个字符。人们会把测试夹具粘贴到问题跟踪系统、终端记录和聊天内容中。几乎真实的密钥带来全部处理风险,却没有任何测试收益。
好的夹具名称应当说明它没有哪些权限。例如,stub_token_no_network 比 token123 表达得更清楚。像 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 结果应暴露发票标识符和状态,但绝不能回显授权标头。
对于每个敏感请求字段,至少写一条否定契约断言。如果模式可能接受调用者提供的 authorization、base_url、account_id 或 tenant 字段,就发送这些字段。确认工具会拒绝或忽略可能把操作重定向到其他账户的值。许多凭据泄露都始于一个看似无害的自定义端点逃生口。
在真正重要的字节层面测试请求序列化。服务商可能区分省略字段和 null、空字符串和缺失值,以及数字和数字字符串。代理经常生成意外的参数形状,尤其是在工具描述没有说明单位时。如果服务商要求最小货币单位,模式应写成 amount_cents,而不是 amount。
契约测试也适合强制执行日志卫生。捕获结构化日志事件,并断言其中包含凭据引用或脱敏标记,而不是合成密钥本身。工程师一旦习惯在各处打印虚假密钥,它就会变成真正的泄露问题。
用矩阵测试权限,而不是只测成功路径
单个经过授权的测试身份无法告诉你工具是否正确处理权限。你需要几个权限刻意不同的身份,以及一组明确写出预期结果的用例。
矩阵应小到足以维护。对于写入工具,以下情况通常值得保留:
| 身份状态 | 请求操作 | 预期结果 |
|---|---|---|
| 测试租户中的写入者 | 创建带标记的记录 | 成功 |
| 测试租户中的读取者 | 创建带标记的记录 | 授权拒绝 |
| 另一个测试租户中的写入者 | 在目标租户中创建记录 | 授权拒绝 |
| 已撤销的凭据 | 列出带标记的记录 | 身份验证失败 |
| 已过期的凭据 | 创建带标记的记录 | 身份验证失败 |
这个矩阵能发现一个常见缺陷:工具检查令牌是否存在,却从不检查令牌实际代表哪个租户。成功路径会通过,因为写入者拥有广泛权限。当代理在任务中收到租户标识符,而客户端没有将它绑定到所选凭据却直接遵循时,问题才会出现。
条件允许时,应针对真实的一次性服务商运行矩阵中的每个用例。服务商在继承角色、默认范围和延迟撤销方面的行为经常与文档不同。保持用例隔离。如果一个测试升级了另一个测试预期不存在的角色,并行运行就会产生看似授权错误的失败。
不要只在存根中伪造禁止响应,然后就认为套件完成了。存根能确认错误映射器识别 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 密钥进行测试,即使目标看起来无害。
如何测试代理操作周围的人工审批网关?
直接测试操作边界。从预期的代理进程发送一次工具请求,拒绝会话或单次调用,然后确认外部系统没有收到请求。界面上显示拒绝很有用,但外部副作用确实没有发生,才是需要的证据。
虚假凭据可以提交到测试代码库吗?
提交到代码库的测试夹具必须是虚构的、有效期短的,并且不能授权访问任何真实服务。密钥扫描器有帮助,但无法判断某个令牌是否能访问生产环境。可靠的解决方案是架构层面的:生产凭据绝不能参与测试环境。