业务系统的 Agent 工具清单应该公开吗?如何用签名保护 OpenAPI 工具源

简介: 本文探讨AI Agent接入业务系统时,OpenAPI工具清单的安全暴露问题:公开清单可能泄露退款、库存等敏感能力。提出“签名保护+负向验证”方案,通过HMAC验签与未签名/错误签名探针,确保清单仅受信方可读。开源项目BailingHub(百灵中枢)v0.2.0已实现该可验证、可配置的安全机制。

关键词:Agent 工具源、OpenAPI 工具清单、AI Agent 接入业务系统、工具清单签名、HMAC 验签、接口清单泄露、MCP 工具安全、BailingHub、百灵中枢

当企业准备让 AI Agent 查询订单、修改库存、创建工单或发起退款时,通常要先给它一份“能做什么”的清单。

这份清单可能来自 OpenAPI,也可能由业务侧 SDK 自动生成,然后挂在一个固定地址上:

https://business.example.com/.well-known/bailing/tools.json

Agent 控制面定期拉取它,就能知道业务系统新增了哪些工具、参数怎么填写、哪些操作需要可信主体、哪些操作风险较高。

这种方式非常适合自动化接入,但也带来一个经常被忽略的问题:

只要知道这个 JSON 地址,任何人都可以下载整份工具清单吗?

如果答案是“可以”,别人看到的可能不只是几个普通接口名称,还包括退款、库存调整、员工冻结、客户资料导出等业务能力,以及它们的路径、参数、scope、风险和审批提示。

这些信息通常不应该包含密码或 Token,但它们仍然是一份机器可读的业务能力地图。

因此,真正需要讨论的不是“JSON 文件能不能放到网上”,而是:

这份能力清单是否本来就希望被公开发现?如果不希望公开,系统能不能证明未签名和错误签名的请求确实读不到它?

本文会把这个问题拆成清单读取、工具暴露、请求认证和业务授权四层,并结合开源项目 BailingHub(百灵中枢) v0.2.0 的真实实现,给出一套可配置、可验证、可兼容升级的处理方式。

一、OpenAPI 工具清单里到底暴露了什么

OpenAPI Specification 的价值,就是让人和机器在不阅读源码的情况下理解一个 HTTP 服务能做什么。它可以被用于生成文档、客户端、测试以及 Agent 工具定义。

例如,一项退款能力可能被描述为:

openapi: 3.1.0
paths:
  /orders/{
   order_id}/refund:
    post:
      operationId: order_refund
      summary: 为指定订单发起退款
      x-agent-capability:
        version: 1
        enabled: true
        scope: order.refund.create
        risk:
          level: high
        subject:
          required: true
        approval:
          required: true
      parameters:
        - name: order_id
          in: path
          required: true
          schema:
            type: string

这段声明没有包含数据库密码,也没有直接赋予调用权限,但它已经告诉读取者:

  • 系统存在退款能力;
  • 接口路径和 HTTP 方法是什么;
  • 业务动作需要哪些参数;
  • 它属于哪个业务 scope;
  • 操作风险较高,并且需要可信主体和人工审批。

如果清单继续包含内部域名、历史版本、调试接口、过度详细的错误说明或尚未下线的操作,它还可能帮助外部人员更快理解系统暴露面。

这并不意味着“藏住 OpenAPI 就安全了”。真正的工具接口仍然必须做好认证、授权、参数校验、限流和审计。隐藏清单不能代替这些安全措施。

但反过来也不能说:“反正接口最终还要鉴权,所以工具清单随便公开也没关系。”

清单是否公开,本来就应该是一项明确的架构决定。

二、公开工具清单不一定错,但必须是主动选择

有些工具清单适合公开:

  • 开源 Demo,希望开发者直接理解并体验能力;
  • 公共 API 或公开工具目录,本来就需要被生态发现;
  • 面向第三方开发者的平台,接口路径和参数已经是公开产品的一部分;
  • 只包含无敏感业务含义的公开查询能力。

另一些清单更适合受保护:

  • 企业内部 ERP、CRM、客服、财务或运维系统;
  • 包含退款、库存调整、账号冻结、批量通知等写操作;
  • 描述租户、权限、风险等级或内部审批语义;
  • 只有指定控制面或网关需要读取;
  • 同一个 URL 位于公网,但其内容只服务于服务器之间的自动同步。

所以更合理的默认原则不是“所有 OpenAPI 都必须公开”或者“所有 OpenAPI 都必须隐藏”,而是:

明确希望公开发现
  -> 显式选择公开

只供受信控制面读取
  -> 默认要求签名保护

OpenAPI 官方规范中的 Security Filtering 也明确允许对接口描述本身增加访问控制,甚至根据调用者身份只展示部分路径和操作。OWASP API Security 的 API9:2023 Improper Inventory Management 同样强调 API 清单、环境、版本和文档访问范围需要被持续管理。

关键不是使用哪个框架,而是把“是否允许匿名读取”从模糊默认值变成可审查的配置意图。

三、清单读取、工具暴露和业务授权是不同的门

很多设计混乱,来自把下面几件事放在一起讨论:

层次 要回答的问题 典型控制
清单读取 谁能下载 OpenAPI / Agent 工具目录? URL 访问策略、签名、缓存控制
工具暴露 哪些 operation 可以进入当前 Agent 的可见范围? 显式 opt-in、scope、路由白名单
请求认证 这次工具请求是否确实来自受信控制面,内容是否被篡改? HMAC、时间戳、HTTPS
业务授权 当前主体此刻能否对这个业务对象执行动作? 租户、原权限、对象状态、审批、业务规则

它们必须分别成立。

工具是否应该被 Agent 看见,和谁能下载这份工具目录,是两道不同的门。即使一项能力声明了 enabled: false,整份 OpenAPI 仍可能暴露其他内部路径;即使清单受到签名保护,也不代表当前用户已经获得退款权限。

同样,OpenAPI 中的 Security Scheme Object 描述的是 API 调用采用什么认证机制,它不会自动保护承载这份 OpenAPI 文档的 URL,更不会替业务系统完成租户隔离和最终授权。

因此,本文讨论的是第一道门:谁能读取能力目录。

后面的工具调用验签、可信主体、审批、幂等和最终业务授权仍然要独立设计。

四、为什么“中枢带了签名”仍然不能证明清单受到保护

假设控制面每次拉取工具清单时都附带正确签名,并且业务端返回了 200

很多系统会据此得出结论:

签名请求成功,所以这个地址已经受到签名保护。

这个结论并不成立。

业务端可能只是收到了签名,但从未校验;也可能校验失败后仍继续返回正文;甚至可能由 CDN、Nginx 静态规则或错误路由直接绕过应用层,把同一份文件公开返回。

下面三组结果都能让“带正确签名的请求”成功:

正确签名 未签名 错误签名 实际结论
200 401 401 可以证明端点拒绝了两类负向请求
200 200 200 端点实际上是公开的,签名没有形成访问边界
200 500 超时 无法可靠判断,不能假装已经验证

所以验证一项安全控制不能只测“合法请求能不能通过”,还要测“非法请求是不是确实被拒绝”。

这也是为什么我们最终没有只给 BailingHub 增加一个“请求时带 HMAC”的开关,而是加入了正向请求和两类负向探针。

五、BailingHub v0.2.0:把公开意图和实际结果分开

BailingHub 是一个采用 Apache 2.0 协议开源、可以自托管的 Agent-to-Business(A2B)控制面。它把业务触发、路由、工具源、可信主体、审批、任务状态和审计放进一条可运行链路,同时让业务系统继续保留最终授权。

在开发 v0.2.0 时,我们把 URL 工具清单的访问策略收敛成两个正式选项:

策略 含义 默认行为
signed_required 只允许能够生成正确 HMAC 的受信方读取 新建 URL 工具源的默认值
public_allowed 管理员明确接受匿名访问者读取工具清单 必须显式选择并再次确认

这里没有“自动猜测”。

如果管理员选择 signed_required,中枢不能因为自己发出了一次带签名请求就显示“已保护”;它还必须通过负向探针观察业务端是否真的拒绝未签名和错误签名。

控制台因此分别显示两类信息:

期望:signed_required
实测:protected / public / inconclusive

“期望”来自管理员配置,“实测”来自 HTTP 证据。两者不一致时必须暴露问题,不能用配置值冒充已经验证的事实。

反过来,如果管理员选择 public_allowed,BailingHub 只发送未签名请求。公开模式失败时不会偷偷改用签名再拉一次,因为那会让系统表面上显示“公开可用”,实际却依赖一个没有被声明的秘密通道。

这项能力完全属于 BailingHub 的工具源发现面。v0.2.0 没有修改 Agent Capability Contract(ACC,Agent 能力契约)、Client API、工具调用签名或业务授权语义,也没有把“能读取清单”写成“有权调用工具”。

六、三次请求怎样形成可审查的保护证据

signed_required 下,一次刷新包含三类请求:

请求 预期结果 用途
正确签名 2xx 证明受信控制面可以取得有效清单
完全未签名 401 / 403 / 404 证明匿名读取被拒绝
使用错误签名 401 / 403 / 404 证明端点不是“只检查头是否存在”

只有三项同时满足,并且返回内容能被正确解析为工具清单时,BailingHub 才把实测状态记录为 protected 并替换缓存。

其他结果需要明确分类:

  • 任一负向请求返回 2xx:记为 public,说明配置与实际暴露面不一致;
  • 重定向、429、5xx、网络失败或超时:记为 inconclusive,说明现有证据不足;
  • 正确签名请求返回 404:这是主请求失败,不是“拒绝未授权访问”的证据;
  • public_allowed 下未签名请求成功:记为 public,与管理员意图一致;
  • public_allowed 下端点实际要求签名:刷新失败,不使用秘密签名兜底。

这里最重要的工程原则是:

不能确认保护有效时,不把未知状态包装成安全状态。

七、签名到底覆盖什么

BailingHub 的工具调用和工具清单拉取共用同一套 HMAC-SHA256 构造。HMAC 的基本定义可以参考 RFC 2104

签名材料为:

<timestamp>.<METHOD>.<path?query>.<sha256(body)>.<On-Behalf-Of>.<Job-Id>

最终请求头形如:

X-Bailing-Timestamp: <unix-seconds>
X-Bailing-Signature: sha256=<hmac-sha256-hex>

工具清单通常使用 GET,请求体为空,也没有操作主体和任务 ID,因此对应字段按空串进入同一套签名材料。

这套签名解决的是请求来源和关键请求内容的完整性校验,不提供内容加密。清单传输仍然应该使用 HTTPS;签名也不能替代业务工具接口自身的身份、权限和审批校验。

如果团队采用其他 HTTP 消息签名方案,也应至少覆盖时间、方法、目标路径和内容摘要,并明确重放窗口、密钥轮换和失败语义。不要只对请求体做签名,却把方法、路径和重要上下文留在签名之外。

八、ThinkPHP 业务系统怎样发布受保护的工具清单

BailingHub 的 PHP SDK 可以从注解或 Builder 生成 OpenAPI 工具清单,也可以帮助业务端验证清单拉取签名。

下面是一个最小 ThinkPHP 路由示例:

use think\facade\Route;
use Bailing\Connect\SpecBuilder;
use Bailing\Connect\SpecServer;

Route::get('.well-known/bailing/tools.json', function () {
   
    $secret = config('bailing.tool_secret');

    $spec = (new SpecBuilder(title: '订单业务系统'))
        ->addClass(OrderToolController::class);

    [$status, $body] = SpecServer::handle(
        $spec,
        $secret,
        request()->method(),
        request()->url(),
        request()->header()
    );

    $response = response($body, $status);
    $response->header(SpecServer::responseHeaders($secret));
    return $response;
});

这里有三个关键点:

  1. $secret 必须与 BailingHub 控制台中该工具源配置的独立密钥一致;
  2. SpecServer::handle() 会校验时间戳和签名,SDK 验签失败默认返回 401;自定义端点也可以用 403 或 404 拒绝负向请求;
  3. responseHeaders($secret) 会为受保护响应增加 Cache-Control: private, no-store,避免代理或 CDN 缓存后旁路公开。

如果这份清单本来就希望公开,代码和控制台都应当明确表达公开意图:

[$status, $body] = SpecServer::handlePublic(
    $spec,
    request()->method(),
    request()->url(),
    request()->header()
);

同时在 BailingHub 中选择 public_allowed。旧版 SDK 使用 null 表达公开的写法仍然兼容,但新代码使用带 Public 的显式方法,更容易让代码审查发现这一项安全决定。

中枢侧的配置步骤是:

工具源
  -> 新建或编辑
  -> 清单来源选择“从 URL 拉取”
  -> 填写 spec_url
  -> 填写独立签名密钥
  -> 选择“签名保护(推荐)”
  -> 保存并刷新

刷新后不要只看“成功”,还要确认控制台出现类似证据:

签名 200 / 未签名 401 / 错误签名 401

这三个结果比一个“已开启安全模式”的开关更有价值,因为它们直接说明业务端实际做了什么。

九、为什么刷新失败时应该继续使用旧清单

工具清单会随着业务系统部署而变化。如果一次自动刷新遇到网关故障、错误重定向、临时限流或不完整响应,控制面有两种选择:

选择 A:清空现有工具,整条 Agent 业务链立即不可用
选择 B:拒绝新清单,继续使用上一份已经验证的缓存

BailingHub 选择第二种。

只有访问策略验证和清单解析全部通过后,系统才替换缓存。失败时记录脱敏证据和审计,同时继续服务上一份可用清单。这能避免业务侧一次发布故障直接拖垮所有依赖该工具源的 Agent 会话。

为了避免这项兼容策略变成无限等待,v0.2.0 还增加了几条边界:

  • 正签、未签名和错误签名请求各自最多等待 10 秒;
  • 两个负向探针并发执行,不把最坏等待叠加成 20 秒;
  • 正向清单响应最多读取 5 MiB;
  • 正式策略不跟随重定向,避免对跳转后的错误目标形成结论;
  • 告警中不回显完整 spec_url,减少内部地址再次泄露。

继续使用旧缓存不是忽略故障,而是在“工具源暂时刷新失败”和“现有 Agent 能否继续工作”之间建立明确的降级边界。管理员仍然能够从体检、审计和探针状态中看到失败并处理。

十、历史工具源升级时,为什么不能替开发者猜

给已有系统增加访问策略时,最容易出现两个极端:

  • 把所有历史地址默认标成公开,可能掩盖原本依赖签名的保护意图;
  • 把所有历史地址默认标成已保护,又会把未经负向验证的端点包装成安全状态。

BailingHub v0.2.0 使用增量迁移 053_tool_spec_access_policy.sql 增加访问策略和探针字段。升级前已有的 URL 工具源在读取时显示为“待确认(历史配置)”。

这个状态内部称为 legacy_unverified,但它不是第三个公开策略:

  • 控制台没有第三个可选按钮;
  • API 和公开 Schema 不允许写入这个值;
  • 现有缓存继续服务,刷新沿用旧的签名读取行为;
  • 修改说明等非清单字段时,不强迫管理员立刻选择;
  • 修改 spec_url、密钥、自动刷新、重新启用等会改变读取面的操作前,必须明确选择 signed_requiredpublic_allowed

这样既避免升级断流,也避免系统替历史配置编造一个从未被声明、从未被验证的安全结论。

新建 URL 工具源则没有这项历史包袱,默认直接使用 signed_required

十一、最容易踩的五个坑

1. 只验证正确签名,不验证未签名和错误签名

正向成功只能证明“合法请求可以用”,不能证明“非法请求被拒绝”。至少要同时覆盖未签名和错误签名两条负向路径。

2. 应用层受保护,CDN 却缓存了合法响应

如果一次带签名的响应被共享缓存保存,后续匿名请求可能直接命中缓存,不再进入应用验签。受保护清单应返回:

Cache-Control: private, no-store

同时检查 CDN 和反向代理是否覆盖了源站缓存头。

3. /.well-known/ 被宝塔或 Nginx 静态规则截获

一些面板会为证书验证预置 .well-known 规则。动态路由可能因此直接 404,静态文件也可能绕过 PHP 验签被公开直出。

约定路径不是强制的。无法安全调整 Nginx 时,可以把 spec_url 改为固定的非点路径,例如:

https://business.example.com/bailing/tools.json

4. 网关重写了路径或 query,业务端重新拼接后再验签

签名覆盖的是实际请求的 path?query。如果 CDN、网关或框架重写、解码、重排 query,业务端自己重组出来的字符串可能与中枢签出的内容不一致,最终持续返回 401。

优先使用原始请求 URI;存在 base_url 路径前缀时,按 SDK 文档显式传入 spec 中声明的固定路径。

5. 把 public_allowed 理解成“工具也可以公开调用”

public_allowed 只允许匿名读取工具目录。真实工具调用仍然必须校验签名、可信主体、任务身份和业务权限;高风险动作仍应进入审批和执行前校验。

十二、上线前检查清单

如果你的业务系统已经向 Agent 发布 OpenAPI 工具清单,可以按下面顺序检查:

  1. 清单是否包含内部路径、敏感描述或不应被发现的操作?
  2. 公开或受保护是否由管理员显式选择,而不是由空密钥、默认路由或历史行为决定?
  3. 受保护模式下,正确签名是否能够稳定返回有效清单?
  4. 完全未签名和使用错误签名时,业务端是否分别拒绝?
  5. 受保护响应是否带 Cache-Control: private, no-store
  6. 全链路是否使用 HTTPS,代理是否保留签名所覆盖的原始路径?
  7. 重定向、429、5xx、超时和超大响应是否被判定为无法确认,而不是自动放行?
  8. 刷新失败时是否保留上一份已验证清单,并留下可排查证据?
  9. 清单访问、工具暴露、工具请求认证和业务最终授权是否分别实现?
  10. 历史来源是否要求管理员明确确认,而不是被批量猜成公开或已保护?

其中最简单也最有效的一次现场验证,是直接观察三种请求:

正确签名:?
未签名:?
错误签名:?

如果三个答案都是 200,系统拥有的是“附带了签名的公开请求”,而不是“受到签名保护的工具清单”。

结语

AI Agent 接入业务系统以后,工具清单会逐渐从一次性配置文件变成持续更新的机器接口。谁能读取它、系统如何验证实际暴露面、失败时怎样降级,都应该成为正式设计,而不是依赖一个没人记得的 Nginx 规则或默认参数。

工具清单公开并不天然错误,受保护也不代表完整安全。

更可信的做法是:开发者明确选择公开或签名保护,控制面用正向和负向请求验证真实行为,业务系统再分别守住工具调用认证、可信主体、审批和最终授权。

BailingHub 把这套边界做进了免费开源、可自托管的 v0.2.0。你可以直接使用,也可以审查源码并在自己的网关或控制面实现相同原则。公开项目的价值,不只是给出一个开关,而是把配置、失败语义、兼容迁移、SDK 和验证证据一起交给使用者。

如果你们已经在给 Dify、MCP 客户端、企业 AI 助手或自研 Agent 发布工具目录,最值得先回答的问题是:

你们希望它被公开发现,还是只允许受信控制面读取?这个选择现在是明确配置,还是一个从未验证过的默认行为?

延伸阅读与实际入口

本文中的工具清单访问策略属于 BailingHub 开源实现,不是 ACC Core 字段,也不要求其他 Agent 平台采用相同配置名。无论使用哪种实现,“清单可读”与“业务动作已授权”都不应被混为一谈。

相关文章
|
8天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
1826 118
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
|
9天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
1380 11
|
15天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1962 9
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
9天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
550 113
|
6天前
|
编解码 弹性计算 云计算
MiniMax-H3 视频生成模型 — 一键部署与使用指南
MiniMax-H3是MiniMax开源的33B全模态视频生成模型,支持文生视频、图生视频、参考生视频三种模式,原生输出2K/15秒带立体声音频视频,已原生适配ComfyUI,并可通过阿里云计算巢一键部署。(239字)
|
21天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
3219 5
|
9天前
|
人工智能 JSON Shell
2026AI漫剧本地全开源方案(附各个软件模型链接),8G显卡也能流畅运行
这是一套完全本地化部署的AI漫剧生成技术链路:涵盖LLM剧本分镜生成、FLUX文生图(IP-Adapter人脸锁定)、StoryDiffusion时序连贯控制、LTX-2.3唇形同步视频生成,及ComfyUI全流程调度。零云端费用,仅耗硬件算力,单集2–4小时可产出竖屏短视频,适配抖音/B站分发。
|
7天前
|
人工智能 API 开发工具
2026 零基础本地 AI 漫剧完整实操教程(8G 笔记本显卡可用|附可直接复制命令与代码)
本方案提供完全离线、本地运行的漫剧全自动制作流程:RTX3060/4050 8G显卡即可驱动,涵盖Qwen写分镜→ComfyUI统一角色绘图→LTX2.3图生微动画→Qwen3-TTS本地配音→FFmpeg自动合成,全程无水印、免API、不限次。专为低显存优化,解决变脸、闪烁、爆内存三大痛点。(239字)

热门文章

最新文章