关于 mcp-bastion
mcp-bastion 是位于 MCP 客户端与上游服务之间的代理,面向需要连接恢复、工具安全控制和审计的 MCP 部署。它聚合多个服务,并通过标准协议提供状态检查与重新连接能力。
主要功能
- 01健康检查与自动重连
- 02聚合并命名空间化工具
- 03检查工具定义与安全风险
- 04按调用者和工具授权
- 05生成带哈希链的审计记录
适用场景
- 01降低长会话中的服务断连影响
- 02检查工具定义是否发生变化
- 03按调用者限制工具访问
- 04为 MCP 活动保留审计证据
MCP 相关
它对客户端表现为 MCP 服务,同时作为 MCP 客户端连接上游服务。它将多个上游工具聚合并向客户端提供状态、重连、安全和合规控制工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
mcp-bastion 位于你的 MCP 客户端(Claude Code、Cursor、Cline、Windsurf、Zed、Claude
Desktop,或任何符合 MCP 标准的 Agent)与 MCP 服务之间。它与客户端无关——仅通过配置即可与任何兼容客户端配合使用,无需编写任何客户端专属代码;同时无侵入性:
你的服务无需改动即可运行,移除 Bastion 只需恢复一行配置。
📦 软件包: npm 上的 mcp-bastion · 🗂️ 官方 MCP Registry: io.github.Gowthaman90/mcp-bastion
🔒 **以实测数据衡量的安全性:**在开放、供应商中立的 mcp-defense-bench v0.9.0 基准测试中,Bastion 在开发测试样本上覆盖 66% 的 MCP 攻击面(24.6/37 个向量;23 个得到强制执行),在预先注册的留出集上覆盖 53%,且针对匹配对照组的误报率为零——在受测代理中覆盖范围最广。
👤 由 Gowthaman Arumugam 创建并维护——独立研究员。配套基准测试:mcp-defense-bench。
目录
原因
当 MCP 服务在会话中途断开时,Agent 只会看到通用的 “没有此工具” 错误——无法与从未存在过的工具区分开来——而且无法重新连接;只有人工才能做到。漫长的 Agent 会话会在不知不觉中失去能力,并以令人困惑的方式失败。
Bastion 弥补了这一缺口。它会对每个服务进行健康检查,采用退避策略自动重新连接;更重要的是,它会提供控制工具,让 Agent 自身 检查连接健康状况,并在无需人工干预的情况下恢复已断开的服务。
Bastion 现已覆盖三个层面:可靠性(v0.1)、运行时安全(v0.2 — 工具固定 / 拉地毯与投毒检测)以及审计与合规(v0.3 — 可插拔的接收端,映射至 NIST AI RMF / OWASP LLM Top 10)。请参阅路线图。
工作原理
如今,客户端会直接连接到每个服务。使用 Bastion 后,客户端会连接到 Bastion,再由 Bastion 代表你连接到这些相同的服务——因此它处于工具调用路径中, 可以透明地增加可靠性(以及未来的安全性)。
Before: Client ─▶ server A / server B / server C
After: Client ─▶ mcp-bastion ─▶ server A
─▶ server B
─▶ server C
对于客户端而言,Bastion 是一个标准的 MCP 服务;对于每个上游服务而言,它是一个标准的 MCP 客户端。 由于它忠实地遵循协议,因此会自动兼容所有符合规范的客户端——不同客户端之间唯一的差别是配置几行内容的位置。
安全模型
Bastion 的安全性基于 三种相互独立的机制。启发式方法为这三种机制提供信息,但不能替代其中任何一种:启发式发现只是风险信号,绝不能证明某个工具是安全的。
| 机制 | 提供的保障 | 检查内容 |
|---|---|---|
| 定义完整性 | 调用的工具、服务和配置与获批内容一致 | 工具固定(拉地毯)、服务身份、配置漂移、参数架构 |
| 运行时授权 | 每个调用方只能访问其策略授权的工具和参数值,并且会在联系任何上游之前作出决定 | authorization 规则、委派检查(谁代表谁行事)、MRTR 延续托管 |
| 可信审计证据 | 记录每项决策且不泄露机密,并且可以检测日志是否被更改或截短 | 审计事件、哈希链、签名检查点、接收端健康状况、脱敏 |
每项发现都带有一个 kind:确定性(针对固定值、架构、策略或令牌声明进行精确比较)或启发式(模式匹配,可能会对无害文本误报,也可能被新型载荷绕过)。各执行配置文件对这些发现的处理方式如下:
| 检查项 | 类型 | observe | balanced(默认) | strict |
|---|---|---|---|---|
| 工具固定(恶意替换),服务身份 | 确定性 | 警告 | 阻止 | 阻止 |
| 运行时授权 | 确定性 | 警告 | 阻止 | 阻止 |
| 委派令牌检查 | 确定性 | 警告 | 阻止 | 阻止 |
| 委派链深度 / 循环 | 确定性 | 警告 | 警告 | 阻止 |
| 跨服务数据流 | 确定性 | 警告 | 阻止 | 阻止 |
| 参数检查:架构,以及命令注入和注入指令模式 | 混合 | 警告 | 阻止 | 阻止 |
MRTR(input_required)门控 | 混合 | 警告 | 阻止 | 阻止 |
| 描述投毒 | 启发式 | 警告 | 警告 | 阻止 |
| 响应启发式(注入、引导) | 启发式 | 警告 | 警告 | 阻止 |
尽管参数检查中有一部分属于启发式,balanced 仍会阻止它:误报会让 Agent 重试,而漏报则可能导致命令运行。所有配置档案都会启用结果中的内联密钥脱敏(redactResponseSecrets)。在 observe 模式下不会阻止任何操作,并且每条审计事件都会记录 wouldBlock:balanced 本会采取的操作。
已知限制。 投毒和响应启发式可能被此前未见过的载荷绕过。脱敏仅涵盖已知的密钥格式,并非所有密钥。Bastion 不会对上游服务本身进行沙箱隔离。授权会信任提供给它的身份:它会自行验证 bearer 客户端,而委派声明仅在签名模式下验证(参见 trustStructuralClaims)。
功能
- 🔌 与客户端无关 — 一个二进制文件,仅需配置即可集成;无需为每个客户端安装插件。
- ♻️ 自我修复 — 健康检查 + 有上限的指数退避自动重连,适用于 stdio 服务。
- 🧭 可由 Agent 恢复 —
bastion__status和bastion__reconnect可让 Agent 自行检测并修复连接中断, 无需面对不透明的“没有此工具”障碍。 - 🧩 透明聚合 — 将多个服务合并为一个,并为每个服务的工具单独命名空间,以防止名称冲突和工具遮蔽。
- 💬 清晰的故障提示 — 服务断开时会提供可执行的消息,而不是导致崩溃。
- 🛡️ 运行时安全 (v0.2 新增) — 固定每个工具的定义并阻止“暗度陈仓”(服务在获批后更改工具);通过启发式方法检查描述是否存在投毒;检测跨服务遮蔽。参见运行时安全。
- 🔐 运行时授权 (v1.4 新增,可选启用) — 在联系任何上游服务前,按调用方和工具制定策略:哪些客户端、用户和 Agent 可以使用哪些参数值调用哪些工具;拒绝优先、默认拒绝,并提供可预览策略的观察模式。参见 运行时授权。
- 📝 审计与合规 (可选启用) — 将结构化、哈希链式审计事件发送到可插拔的目标(控制台 / 文件 / webhook / OTLP),并映射到 NIST AI RMF 和 OWASP LLM Top 10;从 v1.4 起支持仅限 HTTPS 的远程目标、重试投递、按目标脱敏,以及可检测日志是否被截短的 Ed25519 签名检查点。参见审计与合规。
- 🪶 无侵入且可逆 — 你的服务无需修改即可运行;卸载时只需还原配置。
- 🧱 企业级代码库 — 严格的 TypeScript、分层架构、ESLint + Prettier,以及单元测试和端到端测试。
快速开始
Bastion 已发布到 npm,包名为 mcp-bastion — 下方的
npx 命令会自动获取它,因此无需预先安装任何内容。
1. 将 Bastion 添加到你的客户端,并指定配置文件:
// your client's mcpServers config
{
"mcpServers": {
"bastion": {
"command": "npx",
"args": ["-y", "mcp-bastion", "--config", "bastion.config.json"],
},
},
}
2. 在 bastion.config.json 中列出实际使用的服务(从客户端原样移入):
{
"servers": {
"github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] },
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
},
},
"reconnect": { "auto": true },
"healthCheck": { "enabled": true },
}
3. 重启客户端。 你的工具现在会以命名空间形式显示(例如 github__create_issue),同时还会显示
Bastion 的控制工具。完整选项请参阅 bastion.config.example.json。
🔒 默认启用安全防护。 开箱即用时,Bastion 会采用
balanced强制执行配置: 它会阻止高置信度攻击(暗度陈仓、参数 / 命令注入、跨服务数据泄露),并对启发式检测到的攻击 (描述 / 响应投毒)发出警告,同时会从工具结果中删去泄露的机密信息。将security.enforcementProfile设为observe(仅警告)或strict(全部阻止),也可以调整任何单项控制措施 — 参见 运行时安全。
演示
观看完整演示 — 服务在会话中途崩溃并自行恢复:
npm run demo
它会在一个收到命令便会崩溃的服务前启动 Bastion,展示 Agent 收到可执行的“重新连接”消息,而不是晦涩的错误,随后连接会在没有人工介入的情况下自动恢复。要将演示录制为 GIF:asciinema rec demo.cast -c "npm run demo" && agg demo.cast assets/demo.gif。
控制工具
Bastion 注入控制工具,让 Agent 仅使用标准 MCP 调用即可自行管理连接并检查安全性:
| 工具 | 用途 |
|---|---|
bastion__status | 每个代理服务器的健康状态:已连接/已断开/正在重新连接/失败、工具数量、上次错误。 |
bastion__reconnect | 重新连接指定服务器(参数:{ "server": "<name>" }),无需人工干预。 |
bastion__security | 按工具生成的安全报告:固定状态(已批准还是已更改)、投毒发现和遮蔽情况。 |
bastion__compliance | 将近期活动映射到 NIST AI RMF/OWASP LLM Top 10 的审计摘要(需要 audit.enabled)。 |
只有操作员可以重新批准。 清除供应链攻击拦截属于安全权限,因此这不是可由 Agent 调用的工具——遭到提示词注入的 Agent 不得重新批准自己被拦截的工具。更改后的工具会一直处于拦截状态,直到操作员通过带外方式解除拦截;系统不会公布
bastion__approve,客户端对它的调用也会被拒绝。
配置
| 键 | 类型 | 默认值 | 描述 |
|---|---|---|---|
servers | map | — | 要代理的上游服务(必填,至少一个)。 |
servers.<name>.command | string | — | 要启动的可执行文件(例如 npx、node)。 |
servers.<name>.args | string[] | [] | command 的参数。 |
servers.<name>.env | map | — | 与进程环境变量合并的环境变量覆盖项。 |
servers.<name>.cwd | string | — | 启动进程的工作目录。 |
reconnect.auto | boolean | true | 意外断开连接后自动重连。 |
reconnect.maxRetries | number | 10 | 放弃前的最大尝试次数(-1 = 不限次数)。 |
reconnect.initialBackoffMs | number | 500 | 初始退避时间,每次尝试都会翻倍。 |
reconnect.maxBackoffMs | number | 30000 | 退避时间上限。 |
healthCheck.enabled | boolean | true | 启用定期存活探测。 |
healthCheck.intervalMs | number | 30000 | 探测间隔。 |
healthCheck.timeoutMs | number | 5000 | 单次探测超时时间。 |
namespace.strategy | prefix | passthrough | prefix | 上游工具名称的公开方式。 |
namespace.separator | string | __ | prefix 策略使用的分隔符。 |
security.pinTools | boolean | true | 固定工具定义并检测后续变更。 |
security.onRugPull | block | warn | block | 固定的工具定义发生变更时采取的操作。 |
security.inspectDescriptions | boolean | true | 对工具描述运行投毒启发式检测。 |
security.onPoisoning | block | warn | warn | 发现高严重性投毒问题时采取的操作。 |
audit.enabled | boolean | false | 为每次工具调用记录审计事件。 |
audit.includeArgs | none|redacted|full | none | 工具参数的记录方式。 |
audit.tamperEvident | boolean | false | 对事件进行哈希链处理,以便检测篡改。 |
audit.sinks | array | console | 目标:console、file、webhook、otlp。 |
servers.<name>.transport | stdio | http | stdio | 本地子进程或远程端点。 |
servers.<name>.url | string | — | 远程 MCP URL(http 必填)。 |
servers.<name>.headers | map | — | http 上游的请求头(例如 Authorization)。 |
servers.<name>.forwardHeaders | map | — | 仅适用于 http。上游请求头 ← 传入请求头,按请求复制(网关模式)。需要 listen.mode: "http"。 |
servers.<name>.onMissingForwardedHeader | error | omit | error | 请求缺少转发请求头时:返回工具错误且不调用上游,或不带该请求头调用上游。 |
servers.<name>.connect | startup | onDemand | startup | onDemand:在首个需要此上游的请求到来时连接,并使用该请求的转发请求头。 |
servers.<name>.authHeaders | string[] | — | 在 bastion__status 中视为身份验证信息的额外请求头名称(forwardHeaders 中的键始终视为身份验证信息)。 |
servers.<name>.delegation | object | — | 仅适用于 http,需要 forwardHeaders。对转发请求头中的委托 JWT 执行无密钥检查(见下文)。 |
servers.<name>.delegation.verify | structural | signature | structural | signature 还会使用公钥(jwksUrl 或 jwks)验证 JWT,并检查 issuer 和 algorithms 允许列表。 |
delegation.gatewayAudiences | string[] | [] | 用于标识 Bastion 本身的受众;面向其中之一的令牌(或没有 aud 的令牌)会透传。 |
delegation.oneUserPerRequest | boolean | true | 单个请求中的所有委托令牌必须指向同一用户(iss + sub)。 |
delegation.recordSubject | hash | plain | hash | 审计主体:iss|sub 的 HMAC 假名,或原样记录 sub。 |
delegation.subjectKey | string (≥16) | 见下文 | 假名密钥;否则依次使用 MCP_BASTION_SUBJECT_KEY、审计完整性密钥,然后使用每个进程随机生成的密钥。 |
security.onDelegation | block | warn | block | 发生委托问题(链规则除外)时采取的操作。 |
security.onDelegationChain | block | warn | warn | 对 delegation-chain-depth / -chain-cycle 采取的操作(strict 下为 block)。 |
listen.mode | stdio | http | stdio | 通过 stdio 或 Streamable HTTP 提供 Bastion 服务。 |
listen.host / listen.port | string / number | 127.0.0.1 / 3000 | http 模式的绑定地址。 |
listen.validateRoutingHeaders | boolean | true | 拒绝与正文不一致的 Mcp-* 路由请求头(-32020,MCP 2026-07-28)。 |
security.maxCacheTtlMs | number (ms) | 3600000 | 限制转发到下游的上游 ttlMs 缓存提示上限(MCP 2026-07-28)。 |
servers.<name>.protocol | auto|legacy|2026-07-28 | auto | 按上游协商协议版本(探测 2026-07-28,协商失败时回退;或固定版本)。 |
listen.legacy | stateless | reject | stateless | 以无状态方式服务 2026-07-28 之前的客户端,或拒绝它们(-32022)。 |
security.inspectInputRequests | boolean | true | 对 input_required 轮次进行审查(凭据钓鱼、模型诱导)。 |
security.onInputRequired | block | warn | block | 发现高严重性的 MRTR 问题时采取的操作(warn 会移除该请求)。 |
security.requestStateKey | string (≥16) | 每个进程随机生成 | 用于封装 requestState 信封的 HMAC 密钥;也可使用 MCP_BASTION_REQUEST_STATE_KEY。 |
security.requestStateTtlSeconds | number | 300 | 封装后的 requestState 信封有效期。 |
security.enforcementProfile | observe | balanced | strict | balanced | 将所有 on* 操作设为一组;见安全模型。显式设置的 on* 优先。 |
security.onAuthorization | block | warn | block | authorization 拒绝调用时采取的操作(observe 下为 warn)。 |
security.redactResponseSecrets | boolean | true | 在 Agent 看到工具结果前,隐去其中的机密值。 |
authorization.enabled | boolean | false | 启用运行时授权;见运行时授权。 |
authorization.default | deny | allow | deny | 对没有任何规则允许的身份采取的决策。 |
authorization.rules | array | [] | { id, effect, principal, tools, args? } 规则;拒绝优先于允许。 |
authorization.trustStructuralClaims | boolean | false | 接受结构模式委托令牌中的 user/agent。 |
listen.authToken | string | — | 客户端 default 的 Bearer 令牌。除非设置了 listen.clients,否则非回环绑定时必填。 |
listen.clients | map | — | 命名客户端:{ "<name>": { "tokenEnv": "VAR" } },每个客户端一个 Bearer 令牌。 |
audit.integrityKey | string | — | 用于哈希链的 HMAC 密钥;也可使用 MCP_BASTION_AUDIT_KEY。 |
audit.onSinkFailure | continue | block | continue | 没有可用目标时,block 会拒绝调用。 |
audit.sinks[].includeArgs | none|redacted|full | audit.includeArgs | 按目标设置参数记录方式。 |
audit.sinks[].headersEnv | map | — | webhook/otlp:请求头名称 → 环境变量。 |
audit.sinks[].allowInsecureTransport | boolean | false | webhook/otlp:允许通过普通 HTTP 连接到非回环主机。 |
audit.sinks[].maxQueue | number | 10000 | 接收端不可用时暂存的事件数(webhook/otlp)。 |
audit.checkpoint | object | — | 已签名检查点:privateKeyFile、everyEvents(100)、everySeconds(60)、stateFile、sinks。 |
传输方式
Bastion 基于 MCP TypeScript SDK 2.0 运行,并在两端都支持两个协议时代——无状态的
2026-07-28 修订版和 2026 年之前的 initialize 握手;对于上游服务和客户端,两端均如此。当协议为无状态时,网关是唯一仍可持有安全状态的组件,因此 v1.0 增加了 requestState 托管和 input_required 同意门控(请参阅
CHANGELOG.md)。
Bastion 在两端都支持两种传输方式:
- stdio(默认)——客户端启动 Bastion,Bastion 启动本地服务。
- Streamable HTTP——连接到远程 MCP 服务(
servers.<name>配置transport: "http"、url和可选的身份验证headers),和/或通过 HTTP 向多个/远程客户端提供 Bastion 服务 (listen.mode: "http"或--http <port>)。
未配置身份验证标头的 HTTP 上游服务会在 bastion__status 中标记为(authenticated: false),并在连接时发出警告。自定义凭据标头在 authHeaders 中命名后即视为已配置;转发的标头始终视为已配置。
按用户身份验证(网关模式)
当 Bastion 位于 Agent 与必须以已登录用户身份执行操作的子 Agent 之间时,每次上游调用都必须携带该用户的身份,而不是使用一个固定令牌。forwardHeaders 可实现此功能:
{
"servers": {
"pfqa": {
"transport": "http",
"url": "http://localhost:8011/mcp",
"protocol": "legacy",
"connect": "onDemand",
"forwardHeaders": { "x-cso-identity": "x-cso-delegate-pfqa" },
},
},
"listen": { "mode": "http" },
}
此映射的含义是 上游标头 ← 传入标头。处理下游 HTTP 请求期间发出的每个上游请求(initialize、tools/list、tools/call、ping),Bastion 都会将传入的 x-cso-delegate-pfqa 标头值复制到上游请求的 x-cso-identity 标头中。
在此模式下,Bastion 不持有任何凭据,只会转发每个请求所携带的内容。
- 与请求绑定。 传入标头会存放在
AsyncLocalStorage中,生命周期恰好与一个下游请求相同。上游传输上的自定义fetch会在发送时读取这些标头。不同用户的并发请求可以共用一个上游连接,但彼此不会看到对方的标头。 - 仅转发列出的标头。 不会转发其他传入标头。在加载配置时,逐跳标头和传输层自有标头(
connection、mcp-session-id、content-type、……)会被拒绝作为转发目标。如果设置了listen.authToken,则无法转发传入的authorization标头(Bastion 自身的 bearer 令牌)。 - 转发的标头会覆盖同名的静态
headers条目。 - 缺少标头。 默认情况下(
onMissingForwardedHeader: "error"),不会调用上游。调用方会收到类似pfqa: the request carried no x-cso-delegate-pfqa header, so the call was not forwarded.的工具错误。使用"omit"可在缺少标头时仍调用上游,由上游自行决定如何处理。 - 按需连接。 使用
connect: "onDemand"时,启动时不会发送任何请求。首次需要上游的请求(tools/list,或直接调用pfqa__…的tools/call)会使用该请求的标头连接上游。随后会像往常一样缓存工具列表。健康检查会跳过按需连接的上游;在首次使用前,其状态显示为idle。连接失败后会恢复为idle,且不会设置重连计时器。使用forwardHeaders时,connect: "startup"会在启动时仅使用静态headers建立连接,因此,如果上游也会在initialize时进行身份验证,请使用onDemand。 - 401 表示身份验证失败。 工具结果和
bastion__status的lastError都会如实报告此问题,不会将其显示为崩溃或未知工具。一个用户收到的 401 不会拆除其他用户共用的连接。 - 不会记录标头值。 日志、审计接收器和
bastion__status均不包含转发的值。审计事件只会记录转发了哪些标头("forwardedHeaders": ["x-cso-identity"])。 - 固定规则由所有用户共享。 首次发现工具定义时就会将其固定;无论是哪个用户的请求发现定义发生变化,都会阻止该变化。只有身份信息是按请求区分的。
forwardHeaders、onMissingForwardedHeader、connect 和 authHeaders 仅适用于 transport: "http" 上游。forwardHeaders 还要求设置 listen.mode: "http"。加载配置时会拒绝其他组合。要了解一个 Bastion 后配置三个子 Agent 的示例,请参阅
examples/identity-forwarding.config.json。
委派检查
自 v1.2.0 起。 在网关模式下,调用方(例如代表已登录用户工作的主 Agent)会为每个子 Agent 发送一个短时有效的委派 JWT,每个 JWT 放在各自的标头中。每个子 Agent 只能看到自己的令牌。Bastion 能看到整个请求,因此可以检查子 Agent 无法检查的内容,而且无需持有任何密钥或密钥材料:
"servers": {
"pfqa": {
"transport": "http", "url": "http://localhost:8011/mcp", "protocol": "legacy", "connect": "onDemand",
"forwardHeaders": { "x-cso-identity": "x-cso-delegate-pfqa" },
"delegation": {
"audience": "pfqa", // required: what this upstream's tokens must carry in `aud`
"header": "x-cso-identity", // forwardHeaders key carrying the JWT (default: the only one)
"verify": "structural", // or "signature" (below)
"maxLifetimeSeconds": 120, // default 3600
"clockSkewSeconds": 30, // default 30
"allowedActors": ["master-quote-agent"],
"requireActor": false,
"maxChainDepth": 2
}
}
},
"delegation": { "gatewayAudiences": ["mcp-bastion"], "oneUserPerRequest": true, "recordSubject": "hash" }
| 规则 | 触发条件 | 默认值(balanced) |
|---|---|---|
delegation-malformed | 不是严格的 JWS 紧凑令牌(见下文),或声明的类型错误 | block |
delegation-audience-mismatch | 没有任何 aud 值与此上游的 audience 相等(即来自同级服务的令牌) | block |
delegation-passthrough | 缺少 aud,或任意 aud 值属于 gatewayAudiences | block |
delegation-subject-mixing | 请求中的委派令牌包含多个不同的(iss,sub) | block |
delegation-expired / -not-yet-valid | now > exp + skew / nbf > now + skew | block |
delegation-lifetime-excess | exp − iat > maxLifetimeSeconds,或缺少 exp | block |
delegation-actor-not-allowed / -actor-required | 当前 actor(act.sub,否则为 act.client_id)不在 allowedActors 中 / 缺少 act | block |
delegation-chain-depth / -chain-cycle | act 链超过 maxChainDepth / 同一 actor 出现两次 | warn |
delegation-alg-disallowed | 签名模式:alg 不在 algorithms 中(区分大小写;none 和 HMAC 始终不允许) | block |
delegation-key-unknown | 签名模式:没有可信密钥与 kid 匹配,或无法获取密钥集 | block |
delegation-signature-invalid | 签名模式:签名无法通过可信密钥验证 | block |
delegation-issuer-mismatch | 签名模式:iss 与配置的 issuer 不匹配 | block |
链深度和循环涉及先前的 actor。RFC 8693 §4.1 将其称为信息性内容,因此严重性为中等,除非
security.onDelegationChain(或 strict 配置)设为 block,否则只发出警告。其他所有情况均遵循
security.onDelegation(在 observe 配置下为 warn)。
- 严格解码。 必须恰好有三个 base64url 段、无填充、采用规范编码,且 JSON 对象的成员名(转义解码后)唯一,并包含字符串
alg;允许使用可选的Bearer前缀。其他任何情况都会安全失败,并返回delegation-malformed。 - 每个请求只判定一次。 首次需要时,Bastion 会解码请求携带的每个已配置委托的令牌,执行针对每个上游的检查和跨令牌主体检查,并在请求剩余时间内缓存判定结果。检查器导出为
checkDelegation(policy, carriedHeaders, now)(结构检查,同步)和checkDelegationAsync(policy, carriedHeaders, now)(增加签名验证)。 - 联系任何上游之前。 如果请求被阻止,Bastion 会在连接前拒绝
tools/call(包括按需连接),返回指出规则名称的工具错误,但绝不包含令牌或声明值,并记录决策为blocked_delegation的审计事件。tools/list会排除请求无权访问的上游。作为兜底措施,上游传输层会拒绝发送判定结果为阻止的令牌(包括请求期间发生的重连和其他发送)。 - 谁代表谁执行操作。 对已配置委托的上游发起调用时,审计记录会包含
delegation字段,记录subject、issuer、audience、tokenId(jti)、expiresAt、actors(当前项在前)、chainDepth、verified(structural、签名验证通过时为signature,或failed)以及检查结果。绝不记录令牌。在hash模式下,主体为"hmac:"+ HMAC-SHA256(key,iss|sub) 的前 16 个十六进制字符:持有密钥的操作员可以重新计算它,以查找某位用户的记录。密钥依次取自delegation.subjectKey、MCP_BASTION_SUBJECT_KEY、审计完整性密钥;若均不存在,则使用每个进程随机生成的密钥,其假名在进程重启后无法关联。 - 状态。
bastion__status会按上游显示delegation: { mode, audience, lastViolation },其中最近一次违规信息仅包含规则 ID 和时间。 - 配置检查。
delegation需要forwardHeaders;header必须是其键之一;两个上游不能共用同一个audience,且上游的audience不能与网关的audience相同;未知键会被拒绝。签名模式需要issuer、非空的algorithms列表以及且仅需一种密钥来源。
签名模式
当签发方使用非对称密钥(ES256、EdDSA、RS256、PS256,……)签署委托令牌时,Bastion 可以根据签发方的公钥验证这些令牌:
"delegation": {
"audience": "pfqa",
"verify": "signature",
"issuer": "https://spire.example/identity", // exact match on `iss`
"algorithms": ["ES256", "EdDSA"], // allow-list; HS256/384/512 and none are rejected
"jwksUrl": "https://spire.example/.well-known/jwks.json" // or "jwks": { "keys": [ … public JWKs … ] }
}
- 密钥仅来自已配置的来源。 不会跟随
jku、x5u、x5c和内嵌的jwk标头。配置加载时,如果内联jwks包含私有或机密成员(d、p、q、dp、dq、qi、oth、k),则会被拒绝。除环回主机外,jwksUrl必须使用 HTTPS。 - 算法允许列表会先运行,并区分大小写,之后才会查找密钥:
alg: none(无论大小写如何)以及使用公钥签名的 HS256 都会被拒绝,且不会触发获取操作。关键标头扩展(crit,例如未编码的负载)也会被拒绝。 - 远程密钥集会在首次使用时获取并缓存。
kid未知的令牌每 30 秒冷却期内最多触发一次重新获取。获取失败或超时 5 秒时会默认拒绝:调用会被阻止,并返回delegation-key-unknown;上游的lastError会说明密钥集不可用,后续获取操作会等待冷却期结束。 - 结构规则仍会运行,并应用于已验证的令牌,规则名称保持不变;审计记录会显示
verified: "signature"。
坦诚说明其局限。
- 结构模式不进行任何身份验证:能够伪造令牌的一方也能伪造一致的声明。它能阻止诚实但有误或受到提示词注入的调用方造成的路由错误、透传、用户混淆、过期中继和链式滥用。上游仍是验证方。若要在网关进行身份验证,请使用签名模式(非对称令牌)。
- 签名模式仅持有公钥,因此 Bastion 无需持有密钥:它可以验证令牌,但绝不会签发令牌。它需要签发方使用非对称算法签名;HS256 令牌只能进行结构验证,因为验证它们需要共享密钥。它信任配置的密钥集所声明的内容,因此
jwksUrl的安全性(HTTPS、签发方自己的主机)决定了验证的安全性。 - 每个请求一个用户的检查仅适用于配置了
delegation的令牌。 - 不会阻止令牌有效期内的重放(尚无
jti缓存;未来可能提供可选且有界的实现)。
有关三个子 Agent 的示例,请参阅 examples/delegation.config.json。
运行时授权
v1.4 新增,需选择启用。 在每次调用时、联系任何上游服务之前,决定谁可以调用哪个工具,以及可以使用哪些参数。除非启用 authorization.enabled,否则此功能处于非活动状态。
"listen": {
"mode": "http",
"clients": { // one bearer token per client, read from the environment
"docs-assistant": { "tokenEnv": "BASTION_TOKEN_DOCS" },
"release-pipeline": { "tokenEnv": "BASTION_TOKEN_RELEASE" },
"ops-console": { "tokenEnv": "BASTION_TOKEN_OPS" }
}
},
"security": { "enforcementProfile": "observe" }, // preview first: log, never block
"authorization": {
"enabled": true,
"default": "deny",
"rules": [
{ "id": "docs-read", "effect": "allow", "principal": { "client": "docs-assistant" },
"tools": ["files__read_file", "files__list_dir"],
"args": { "path": { "pathWithin": "/srv/docs" } } },
{ "id": "release-staging", "effect": "allow", "principal": { "client": "release-pipeline" },
"tools": ["deploy__*"], "args": { "env": { "in": ["staging"] } } },
{ "id": "no-prod-rollback", "effect": "deny", "principal": "*",
"tools": ["deploy__rollback"], "args": { "env": { "equals": "prod" } } },
{ "id": "ops-control", "effect": "allow", "principal": { "client": "ops-console" },
"tools": ["bastion__*"] }
]
}
调用者是谁。 一次调用会携带 Bastion 已验证的所有身份:客户端(具名的 listen.clients bearer token、listen.authToken 对应的 default,或 stdio),以及在存在委托令牌时,来自该令牌的用户和执行操作的Agent。忽略参数、标头、_meta 或输入响应中声明的身份:请求不能通过自称为其他人来提升自身权限。
如何判定一次调用。
- 每个身份都必须获得许可。 代表用户行事的 Agent 需要同时拥有用户和自身的授权,因此 Agent 的宽泛服务授权绝不会用于没有相应权限的用户(即困惑代理问题),而用户的权限也不会被未获授权的 Agent 使用。委托链中的较早 Agent 不授予任何权限;只有当前 Agent 有效。
- 拒绝优先于允许。 对于每个身份,任何匹配的
deny规则都会拒绝调用;否则,必须有一条allow规则,且其所有参数约束都成立。如果default: "deny",则没有匹配的允许规则就意味着无法访问。 - 规则。
principal可以是"*",也可以是client、user、agent之一(名称或列表)。tools是带命名空间的名称,可以是精确名称,也可以以一个*结尾。args使用equals、in、max、min和pathWithin约束顶层参数。匹配是精确的:不进行大小写折叠、Unicode 规范化或类型强制转换;max/min仅接受 JSON 数字;pathWithin会解析.和..,并在/处检查边界(因此/srv/docs-archive不属于/srv/docs)。 - 默认拒绝。 缺少受约束的参数会导致
allow规则不匹配。带有args的deny规则会生效,除非其约束能够被评估且不成立,因此缺失或类型错误的参数无法绕过拒绝规则。 - 控制工具(
bastion__*)与任何工具一样接受授权,服务名称为bastion。使用default: "deny"时,应像上面的ops-console一样,向运维客户端授予这些工具的权限。
执行位置。 在 tools/call 中,于委托检查之后、联系任何上游服务之前执行,因此被拒绝的调用绝不会建立携带调用者转发标头的连接。继续调用(input_required 重试)会重新进行授权。tools/list 会隐藏调用者永远无法获准使用的工具。
逐步启用。 先使用 enforcementProfile: "observe":此模式不会拒绝任何调用,每条审计事件都会带有 authorization 记录(包括 effect、用于判定的 rule、agent:argument 等 violations),并在强制执行时会拒绝调用的情况下附带 wouldBlock: "blocked_authorization"。当日志只显示你有意拒绝的调用后,切换到 balanced。在 balanced 模式下,被拒绝的调用会收到答复 Blocked by mcp-bastion: this caller is not authorized to call "<tool>" (rule <id>).;不会泄露其他策略内容。
信任用户和 Agent 身份。 这些身份只能来自受信任的委托令牌:在签名模式下经过验证的令牌,或在结构模式下启用了 trustStructuralClaims: true 的令牌。只有在上游也会验证同一个令牌时才设置此项,这样即使伪造的声明通过了 Bastion,上游仍会拒绝它。对于结构模式的上游,如果规则指定了 user 或 agent,则会导致配置错误;如果令牌存在但不受信任,则会拒绝调用(delegation:unverified)。
已有评测。 这组检查已在编写此代码之前,预先注册为 mcp-defense-bench 的测试用例(标签 authorization-prereg-2026-10-26)。其他网关也提供按调用者进行的 MCP 授权(例如 agentgateway 的 CEL 规则和 ToolHive 的 Cedar 策略),而该基准测试的预注册承诺将对它们进行并列评分。
运行时安全
在 v0.2 中新增。 Bastion 在工具调用路径中添加了一个安全层(拦截器管道),默认启用:
- 诱饵检测(工具固定)。 每个工具的定义都会在首次使用时固定。如果服务之后更改了该定义,工具就会被阻止(
onRugPull: "block"),直到操作员进行审核并通过带外方式重新批准(仅限操作员,不能由 Agent 调用)。这可以发现安装时看似无害、之后却变得恶意的服务。 - 投毒检查。 系统会扫描工具名称和描述,查找操纵行为特征(覆盖指令、访问机密、外泄数据、隐蔽指令、嵌入式指示、隐藏字符/零宽字符)。由于启发式检查可能产生误报,默认设置为
warn(记录并报告,但不阻止);设置onPoisoning: "block"可强制阻止。 - 名称冲突。 当两个服务公开同名工具时,报告会将其列出。
使用 bastion__security 工具查看所有检查结果。每项发现都会说明其属于 heuristic 还是 deterministic;请参阅安全模型。
审计与合规
可选启用。 启用 audit 后,系统会为每次工具调用记录结构化、带版本的事件,包括被安全层或授权拒绝的调用:
"audit": {
"enabled": true,
"includeArgs": "redacted", // none | redacted | full (default for every sink)
"tamperEvident": true, // hash chain; keyed with integrityKey / MCP_BASTION_AUDIT_KEY
"onSinkFailure": "continue", // or "block": refuse calls while no sink can deliver
"sinks": [
{ "type": "file", "path": "./bastion-audit.jsonl", "includeArgs": "full" },
{ "type": "otlp", "endpoint": "https://collector.example:4318",
"headersEnv": { "authorization": "OTEL_AUTH_HEADER" }, "includeArgs": "none" }
],
"checkpoint": { // signed checkpoints (needs tamperEvident)
"privateKeyFile": "/etc/bastion/checkpoint-private.pem",
"stateFile": "/var/lib/bastion/audit-state.json",
"sinks": [{ "type": "webhook", "url": "https://witness.example/checkpoints" }]
}
}
事件。 每个事件都会列出服务、工具、决策(allowed、blocked_rug_pull、blocked_authorization 等)、结果、带有 kind 的发现、存在时的 authorization 和委派记录、runId,以及与 NIST AI RMF 职能和 OWASP LLM Top 10 类别的映射。bastion__compliance 会返回汇总数据。
每个接收端都是独立的安全边界。
console(stderr)、file(JSON 行格式,以0600权限创建)、webhook和otlp(OpenTelemetry 日志)。- 除回环地址外,远程接收端必须使用 HTTPS。除非接收端设置了
allowInsecureTransport: true(此时会记录警告),否则向远程主机发送普通 HTTP 会导致配置错误。 - 凭据从环境变量(
headersEnv)获取,而不是从配置文件读取;缺少变量会导致启动失败。 - 投递失败时会采用指数退避重试,而不是直接丢弃。事件会在有界队列中等待(
maxQueue,默认值为 10 000);超过上限后,最旧的事件会被丢弃并计数。接收端还可以通过每个事件的seq检测缺失。 bastion__status会显示每个接收端的运行状况:healthy、delivered、dropped、queued、lastError。- 设置
onSinkFailure: "block"后,当没有任何接收端能够投递时,调用会被拒绝(blocked_audit_unavailable),适用于不能接受未审计调用的部署。Bastion 自身的控制工具仍可使用,以便检查并恢复接收端。
将脱敏作为核心功能。
- 默认不记录参数。每个接收端都可以单独设置记录级别(每个接收端的
includeArgs):例如,将完整参数写入本地文件,而不写入共享 SIEM。 redacted会保留结构,并对凭据键名下的值以及看起来像机密的值进行掩码:云服务和提供商密钥、GitHub、Slack、Stripe、Google 和 npm 令牌、JWT、Bearer凭据、URL 中的密码、私钥块和NAME=secret赋值。同样的模式也会在 Agent 看到工具结果之前对其中的机密进行脱敏(redactResponseSecrets,默认启用)。在一个包含 45 个案例的语料库(test/redaction-corpus.test.ts)中进行测试:29/29 个机密被捕获,0/16 个相似但无害的内容被更改。- 永远不会写入任何接收端的内容:工具结果、委派令牌值、转发的标头值(仅记录名称),以及当
delegation.recordSubject为hash时的原始用户sub。参数仅按includeArgs的设置记录,因此在full模式下,作为参数传入的机密仍会被保留。 - 脱敏基于模式。形态无法识别、且位于无法识别键名下的机密仍可能被记录,因此对于高敏感度接收端,建议使用
none。
防篡改能力及其局限。
- 使用
tamperEvident时,事件会构成哈希链。使用integrityKey时,哈希链采用 HMAC-SHA256: 没有密钥,篡改日志的一方就无法重新计算哈希链。未使用密钥时,它只能检测简单的 编辑。 - 当各个接收器以不同方式记录参数时,哈希链(v2)会覆盖每个事件(不含其
args视图), 并包含一个argsDigest(完整参数的 HMAC),这样每个接收器验证的都是同一条哈希链。 - 哈希链无法证明最新记录是否被截断。签名检查点可以做到这一点:
audit.checkpoint会每隔everyEvents(100)个事件、每隔everySeconds(60)秒以及关闭时, 写入一条使用 Ed25519 签名的链头声明;每次运行都会以run-start记录开头,并通过stateFile链接到上一次运行。将检查点发送到日志所在主机无法篡改的见证接收器。 mcp-bastion audit verify <log> --checkpoints <witness.jsonl> --public-key <pem>会检查哈希链、seq连续性、检查点签名、运行链接、日志是否被截断以及运行记录是否被删除。完整时退出码为0,遭到篡改时为1,无法验证时为2。mcp-bastion audit keygen --out <dir>会生成密钥对。- **限制:**只有当攻击者无法篡改的见证接收器收到检查点时,截断检测才能追溯到该检查点;如果没有 见证接收器,同时截断日志和检查点就无法被检测到。持有私钥的攻击者可以伪造检查点,因此请勿将 私钥存放在日志所在主机上。
影子模式。 在 security.enforcementProfile: "observe" 下,不会阻止任何操作;每个事件都会说明
执行强制策略时会采取什么措施:wouldBlock 保存 balanced 原本会记录的决策,findings 会将
rug pull 标记为 tool-definition-changed,而 responseFindings / redactedSecrets 会记录结果扫描
和内联 DLP 的发现(如果关闭了脱敏,则记录为 secret-in-result)。
标准对齐
每项安全检查都对应公认的框架——NIST AI 风险管理框架(美国联邦标准)、OWASP LLM (2025)与 Agentic(2026)应用十大风险以及 STRIDE。各项检查的完整映射(含检查所依据的 MCP 安全文献参考链接)见 docs/CHECKS-MAPPING.md。
覆盖度独立衡量——bastion 使用不受厂商限制的 mcp-defense-bench 基准测试,针对 22 项攻击面向量进行评分,并提供公开的 排行榜和框架映射。
客户端设置
所有客户端的步骤都相同——只有配置文件位置不同:
| 客户端 | 添加 bastion 条目的位置 |
|---|---|
| Claude Code | 项目 .mcp.json(或 claude mcp add) |
| Cursor | ~/.cursor/mcp.json 或项目 .cursor/mcp.json |
| Claude Desktop | claude_desktop_config.json |
| Cline | cline_mcp_settings.json |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
**逐步采用:**你不必将所有服务都通过 Bastion 路由——只需将不稳定或不受信任的服务置于其后, 其余服务仍可直接连接。
架构
Bastion 按清晰的层次组织,依赖关系单向流动,因此每项关注点都可独立测试,也易于演进:
src/
├── cli.ts # thin CLI entrypoint (parse → wire → serve)
├── index.ts # public library API
├── errors.ts # error hierarchy (BastionError, …)
├── config/ # schema (Zod) + loader
├── core/ # domain: upstream connection lifecycle, aggregation & routing
├── proxy/ # client-facing MCP server + control tools
├── observability/ # logging (audit sinks in v0.3)
└── internal/ # small cross-cutting utilities
设计细节——包括与客户端无关的设计理由、拦截器流水线和审计接收器策略——请参阅项目的设计文档。
开发
npm install
npm run check # format:check + lint + typecheck + test (the full gate)
npm test # unit + end-to-end (in-memory transport) tests
npm run build # bundle to dist/ (CLI + library)
npm run dev -- --config bastion.config.json
| 脚本 | 功能 |
|---|---|
build | 使用 tsup 打包 CLI 和库。 |
dev | 使用 tsx 从源代码运行 CLI。 |
typecheck | tsc --noEmit(严格模式)。 |
lint / lint:fix | ESLint(扁平配置)。 |
format / format:check | Prettier。 |
test / test:watch | Vitest。 |
check | 执行以上所有检查,作为统一门禁。 |
路线图
| 版本 | 主题 | 重点 |
|---|---|---|
| v0.1 ✅ | 可靠性 | 聚合代理、自动重连、bastion__status / __reconnect。 |
| v0.2 ✅ | 运行时安全 | 工具定义固定(检测 rug pull)、投毒检查、影子检测。 |
| v0.3 ✅ | 审计与合规 | 可插拔审计接收器(控制台 / 文件 / webhook)、NIST AI RMF / OWASP LLM Top 10 映射。 |
| v1.2 ✅ | 委派 | 对委派令牌进行无密钥检查:受众、主体混淆、过期时间、Actor 链、签名。 |
| v1.4 | 授权与证据 | 按调用方进行运行时授权;仅限 HTTPS、自动重试、按接收器脱敏的审计接收器;签名检查点和 audit verify。 |
支持 stdio 和 Streamable HTTP 传输方式(参见传输方式)。
参与贡献
非常欢迎大家贡献——本项目旨在由社区共同维护。请阅读 CONTRIBUTING.md,了解开发环境设置、项目结构和 PR 工作流,以及我们的 行为准则。
简而言之:对于重要变更,请先提出 issue;让 PR 聚焦并附带测试;并确保
npm run check 通过(CI 在 Node 18/20/22 上运行)。适合新手的领域包括:补充客户端配置示例、
增加上游测试样例,以及支持 Streamable HTTP 传输方式。
安全
mcp-bastion 属于安全相关软件,因此我们采用高标准。请私下报告
漏洞——不要公开创建 issue。披露流程请参阅 SECURITY.md。
许可证
Apache-2.0 © Gowthaman Arumugam 和 mcp-bastion 贡献者
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。