关于 Keeper Secrets Manager
Keeper Secrets Manager 是 Keeper 官方 MCP 服务的多租户 Streamable HTTP 桥接器,面向需要按租户传递凭据的网关部署。它为每组凭据启动独立的 stdio 子进程,并通过经过审核的只读工具允许列表限制可用功能。
主要功能
- 01通过 stdio 会话提供 Streamable HTTP 桥接
- 02每组租户凭据使用独立子进程
- 03允许列表开放九个只读工具
- 04参数允许列表遇到变更时默认拒绝
- 05空闲 15 分钟后回收子进程
适用场景
- 01通过 HTTP 网关提供 Keeper MCP 工具
- 02使用独立 Keeper 凭据服务多个租户
- 03将集成限制为获准的读取操作
- 04从内存中移除空闲租户会话
MCP 相关
该桥接器作为 MCP 客户端连接 Keeper 的 stdio 服务,并在 `/mcp` 上通过 Streamable HTTP 转发获准的工具。它要求网关认证,并在每个请求中接收 Keeper 配置凭据。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
docker run -i --rm -e PORT -e KSM_MCP_BIN -e CHILD_HOME -e IDLE_EVICT_MS -e SPAWN_TIMEOUT_MS ghcr.io/wyre-ai/keeper-mcp:v1.0.4PORTPublic listen port for the bridge (POST /mcp, GET /health)KSM_MCP_BINPath to the upstream ksm-mcp binary the bridge spawns per tenantCHILD_HOMEWritable HOME for upstream child processesIDLE_EVICT_MSIdle tenant timeout before an upstream child is evicted (ms)SPAWN_TIMEOUT_MSHow long to wait for a spawned child to answer the MCP handshake (ms)本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
ghcr.io/wyre-ai/keeper-mcp:v1.0.4stdio工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
keeper-mcp
基于 Keeper-Security/keeper-mcp-golang-docker 构建的多租户 Streamable HTTP 桥接服务。该项目是 Keeper 官方的 Secrets Manager MCP 服务,旨在让 WYRE conduit 网关通过 HTTP 标头转发每个租户的 Keeper 凭据。
上游固定版本:
v2.5.0(MIT),基于源代码构建。请参阅更新上游固定版本。v1 版本为只读。 请参阅 v1 版本只读——对于此供应商而言,这是安全边界,而非范围界定决策。
原因
Keeper 的服务仅支持 stdio。其源代码中没有任何 HTTP 或 SSE 监听器——其 Dockerfile 中的 EXPOSE 8080 已无实际作用,而其 docker-entrypoint.sh 则无法执行,因为发布的镜像基于 FROM scratch,不包含 shell。它在进程启动时从 KSM_CONFIG_BASE64 读取凭据,因此每个进程只能服务一个租户。
我们的网关是多租户的:每个请求都会通过 HTTP 标头携带调用方组织的凭据,而供应商容器必须将这些标头转换为上游能够理解的内容。
由于上游没有可供代理的 HTTP 模式,此桥接服务会通过 stdio 为每个租户子进程维持一个 MCP 客户端会话,并通过 Streamable HTTP 重新提供服务:
- 在
:8080上监听POST /mcp和GET /health。除非CONDUIT_S2S_SECRET是有效密钥,否则拒绝启动。 - 对每个
/mcp请求先通过X-Gateway-S2S进行 401 验证,再检查X-Keeper-Config-Base64,包括验证其是否可解码为可用的 KSM 配置。缺失或无效的凭据绝不会回退到环境凭据;否则会造成跨租户泄露。此桥接服务仅用于网关模式。 - 按凭据哈希进行惰性启动:每个凭据启动一个
ksm-mcp serve --batch子进程,设置该租户的KSM_CONFIG_BASE64,并通过 stdio 将 MCP 客户端连接到该子进程。 - 通过 v2 SDK 的
createMcpHandler(factory, { legacy: 'stateless' })在/mcp上同时提供两代协议:2025 年客户端(例如当前的 conduit 网关)所用的initialize握手协议,以及现代的 2026-07-28 信封协议。 - 通过只读允许列表,在双向过滤子进程可访问的功能。
- 15 分钟后逐出空闲子进程(
IDLE_EVICT_MS)。
工具名称保持不变。
v1 版本只读——这是刻意设计且至关重要的
边界 1(最可靠的边界):只读 KSM 应用
请为 Keeper 应用配置不可编辑的共享权限。 KSM 应用的共享文件夹授权默认为只读,除非明确设置为可编辑(secrets-manager share add --app <APP> --secret <FOLDER_UID>;Vault 的 Application Access 面板会显示 Read-Only 或 Editable)。授权不可编辑时,无论此桥接服务、允许列表、批处理模式还是提示词注入如何操作,所有写入都会在 Keeper 自身的服务端失败。
这是凭据约定的配置前提条件,而非锦上添花。此桥接服务无法验证这一点:Keeper SDK 会按记录携带 IsEditable,但 ksm-mcp 从不读取它,因此这里无法检测授权范围过大的应用。请将应用范围限定为用例所需的最小文件夹集合,并设为只读;这才是真正的控制措施。
边界 2:此桥接层的允许列表
Keeper 上游没有只读模式——已根据 v2.5.0 固定版本核实:serve 有六个标志,且没有一个限制可用范围;getAvailableTools() 返回一个硬编码的 19 项切片,不会查询任何配置;Confirmation.DefaultDeny 被硬编码为 false,且没有任何接线逻辑。上游没有任何机制可以取消注册工具。
关于批处理模式。 Keeper 自己的 Docker 文档告诉你设置
KSM_MCP_BATCH_MODE=true,此桥接层也确实这样设置了。它实际会带来以下变化:
| 批处理模式 | 行为 |
|---|---|
| 关闭 | get_field + unmask 会硬性报错(上游唯一的 Confirmer 调用点)。另外八个需要确认的工具会返回成功的 {status: "confirmation_required"} 结果,指向 ksm_confirm_action 提示词,并要求分两阶段完成。 |
| 开启 | 所有确认都会自动获批,包括 delete_secret。 |
在这里关闭它并不能建立确认边界,因为从结构上看,此桥接层无法触及这个两阶段流程:该流程由 MCP 提示词驱动,而此桥接层从未声明 prompts 能力;此外,完成该流程的工具 ksm_execute_confirmed_action 也会在下文被屏蔽。因此,“关闭”只会产生此处没有客户端能够满足的占位结果,并不安全。我们强制开启它,并在 src/tools.ts 中设置边界。
允许列表独有的作用(只读授权做不到): 屏蔽 get_all_secrets_unmasked 和 ksm_execute_confirmed_action。只读授权会允许这两个属于读取或更危险操作的工具。
已提供(9 个)
| 工具 | Conduit 层级 | 说明 |
|---|---|---|
list_secrets | read | 仅返回记录元数据 |
list_folders | read | 文件夹元数据 |
health_check | read | |
get_server_version | read | |
search_secrets | admin | 返回的结果采用元数据形式,但会匹配备注以及 login/url/hostname/address 值——相当于可用于探测机密的确认预言机(CWE-200) |
get_secret | admin | 返回一条记录;遵循 unmask 设置 |
get_field | admin | KSM 记法查询——范围最窄的读取 |
get_totp_code | admin | 实时第二因素 |
generate_password | admin | 本地生成;已移除创建记录所需的参数 |
Conduit 将凭据读取归类为 admin(src/access/tool-classification.ts),其级别高于 write。Keeper 读取按定义就是凭据读取。
已屏蔽(10 个)
create_secret、update_secret、delete_secret、create_folder、delete_folder、
upload_file、ksm_execute_confirmed_action、get_all_secrets_unmasked、
get_record_type_schema、download_file
其中有四个值得说明:
ksm_execute_confirmed_action— 它并非与其他工具同类,而是关键枢纽。它接收original_tool_name和由调用方提供的user_decision布尔值,既没有随机数,也不会关联任何提示词,然后直接分派到已确认的执行器。允许它使用,就会让此处的所有其他规则形同虚设。永久屏蔽。get_all_secrets_unmasked— 一次调用即可将应用程序范围内的所有机密以未遮蔽形式返回到模型的上下文中。永久屏蔽。get_record_type_schema— 在 v2.5.0 的上游中无法正常工作:LoadRecordTemplates()没有调用方,且该包没有init(),因此每次调用都会返回“未加载记录模板”。download_file— 无法通过 MCP 工作。上游的DownloadFile(uid, fileUID, savePath)只返回一个error,并将字节写入服务端路径;处理器返回的是{uid, file_uid, path, message},而不是文件。遵循save_path会允许租户将攻击者指定的路径写入容器文件系统,而该文件系统由所有其他租户的子进程共享;移除它则会导致无处可写。
参数策略:允许列表,而非禁止列表
每个已提供的工具都会声明其接受的确切参数(src/tools.ts 中的 allowArgs),
并对公开的 inputSchema 和传入的 tools/call 强制执行。因此,使用过时或被忽略的架构的客户端
无法仅凭传入参数触达写入路径。
默认策略比当前条目更重要:当上游更新为已提供的工具添加参数时,会默认拒绝——该参数会从接口中消失,直到有人将其审核后加入表格——而不是悄然放行。目前唯一移除的参数是
generate_password 的 save_to_secret 和 folder_uid,否则会把本地实用工具变成记录创建工具。
启用写入需要对 src/tools.ts 进行经过审核、带版本控制的更改,并同步更新 conduit 中对应的
VENDOR_TOOL_CONFIG 行。这不是切换配置,也不是设置环境变量。
凭据约定
网关会在每个 /mcp 请求中转发此标头。conduit 的 vendor-config 必须完全匹配。
| 标头 | 子进程环境变量 | 必需 |
|---|---|---|
X-Keeper-Config-Base64 | KSM_CONFIG_BASE64 | 是 |
该值是来自 Keeper Vault → Secrets Manager → 你的应用程序 → Devices → Add Device 的 base64 设备配置(以 ewog... 开头)。
前提条件:此设备所属的应用程序必须持有只读 (不可编辑)共享项。请参阅边界 1 ——这是系统中最强的控制措施,桥接程序无法替你验证。
**有效性规则:**标准 base64,解码后必须是一个扁平 JSON 对象,包含字符串值 clientId、privateKey 和 appKey。hostname 为可选项——已根据上游 internal/ksm/client.go 中的 InitializeWithConfig 验证,该函数恰好要求这三个字段。任何其他情况 → HTTP 401,并返回一个绝不会回显所提供值的 JSON-RPC 错误正文。
桥接程序还会对每个子进程强制设置:
KSM_MCP_BATCH_MODE=true # no TTY in a container (see above)
KSM_MCP_LOG_LEVEL=error
argv: ["serve", "--batch"]
特意不设置 KSM_MCP_PROFILE:只要存在 KSM_CONFIG_BASE64,上游就会在内存中构建配置文件,完全不会访问磁盘上的配置文件存储,从而避免租户通过文件系统共享状态。
配置
| 环境变量 | 默认值 | 说明 |
|---|---|---|
CONDUIT_S2S_SECRET | 空(拒绝启动) | 服务间密钥。此项为空或为已知占位值(changeme、secret、replace-me、your-s2s-secret 及其他示例值)时,HTTP 会记录错误并以非零状态退出。使用 openssl rand -hex 32 生成密钥。未设置此项,Compose 将无法启动。此值绝不会被记录。 |
MCP_ALLOW_INSECURE_DEV | 未设置 | 仅在本地开发时设为 1,即可在未设置 CONDUIT_S2S_SECRET 的情况下启动 HTTP。进程会记录警告,不会强制检查 X-Gateway-S2S,且只绑定到 127.0.0.1。非环回地址的 MCP_HTTP_HOST 将被忽略,并记录此覆盖行为。已知的占位密钥仍会导致启动失败。 |
MCP_HTTP_HOST | 设置了真实密钥时为 0.0.0.0 | 绑定地址。设置了密钥时会使用此值,包括 0.0.0.0。未设置时会保留原有的容器绑定地址。镜像和 Compose 会在容器内将其设为 0.0.0.0。未设置密钥时,开发环境绕过选项会强制使用 127.0.0.1,且不采用此变量的值。 |
PORT | 8080 | 公共监听端口。若未设置 PORT,则使用 MCP_HTTP_PORT 作为回退值。 |
KSM_MCP_BIN | /usr/local/bin/ksm-mcp | 桥接服务启动的上游二进制文件。 |
CHILD_HOME | /tmp/ksm-mcp-home | 根目录;每个租户都会在其下获得自己的 HOME 子目录,并在启动时创建。 |
IDLE_EVICT_MS | 900000 | 租户空闲超时时间(15 分钟)。特意设得比 nutanix-mcp 的 60 分钟短:Go 子进程可在毫秒内重启,而每多保持一分钟运行,就会让一个已认证的 KSM 会话多在内存中保留一分钟。 |
SPAWN_TIMEOUT_MS | 30000 | 等待子进程响应 MCP 握手的最长时间。 |
复制 .env.example。在示例文件中将 CONDUIT_S2S_SECRET 留空;仅将真实值放在未纳入版本控制的 .env 文件或进程环境中。
HTTP 身份验证
设置 CONDUIT_S2S_SECRET 后,每个 /mcp 请求都必须包含 X-Gateway-S2S。检查逻辑位于 src/s2s-verify.ts 中的 verifyS2sHeader:标头值格式为 t=<unix seconds>,v1=<hex>,其中 hex 是以 CONDUIT_S2S_SECRET 为密钥、对字面字符串 t=<unix seconds> 计算出的 HMAC-SHA256(64 个小写十六进制字符)。时间戳与服务器时钟的差值不得超过 300 秒。缺少标头或标头无效时,会在读取 Keeper 凭据标头之前返回 HTTP 401。
TS=$(date +%s)
SIG=$(printf 't=%s' "$TS" | openssl dgst -sha256 -hmac "$CONDUIT_S2S_SECRET" -hex | awk '{print $NF}')
curl -sS http://127.0.0.1:8080/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "X-Gateway-S2S: t=${TS},v1=${SIG}" \
-H "X-Keeper-Config-Base64: $KSM_CONFIG_BASE64" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
仅支持网关模式。/mcp 还要求提供 X-Keeper-Config-Base64。该标头是此请求唯一的凭据来源。进程环境中的 KSM_CONFIG_BASE64 不会作为回退值。
/health 和 /healthz 无需身份验证,也不会读取凭据。
Docker
镜像在容器内监听 0.0.0.0(MCP_HTTP_HOST)。请将端口发布到环回地址,并传入真实的 S2S 密钥。Keeper 凭据会随每个请求通过标头传入,不会从环境中读取。
docker build -t keeper-mcp .
docker run \
-e CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" \
-e MCP_HTTP_HOST=0.0.0.0 \
-p 127.0.0.1:8080:8080 \
keeper-mcp
若未设置 CONDUIT_S2S_SECRET,Compose 将拒绝启动。网关服务不会设置 KSM_CONFIG_BASE64 或 env_file,并将端口发布为 127.0.0.1:8080:8080:
CONDUIT_S2S_SECRET="$CONDUIT_S2S_SECRET" docker compose up
不使用密钥启动本地 HTTP 仅适用于开发环境。它会绑定到 127.0.0.1 并记录警告。非环回地址的 MCP_HTTP_HOST(包括 0.0.0.0)会被忽略:
MCP_ALLOW_INSECURE_DEV=1 npm start
端点
POST /mcp— 双时代的 Streamable HTTP MCP。需要有效的X-Gateway-S2S标头和X-Keeper-Config-Base64。GET /health和GET /healthz— 无需身份验证的存活状态检查。报告mode、已提供的工具数量和活动租户数量。绝不读取凭据,也绝不启动子进程。
开发
npm install
npm run lint # tsc --noEmit
npm test # vitest
npm run build
HTTP 在设置 CONDUIT_S2S_SECRET 之前不会启动。若要在本地监听且不使用密钥:
MCP_ALLOW_INSECURE_DEV=1 npm start
测试套件涵盖允许列表策略、凭据约定,以及真实 HTTP 协议栈中的 401 拦截机制。测试不需要 Keeper 账户或运行中的子进程——线程池指向一个不存在的二进制文件,因此任何意外启动都会立即报错。
更新上游固定版本
- 修改
Dockerfile中的KSM_MCP_REF。 - 重新构建。
scripts/check-upstream-tools.mjs会在 Docker 构建过程中运行,根据固定版本的 Go 源码重新推导工具范围,并在出现任何偏差时使构建失败——例如新增工具、工具重命名,或已提供的工具新增参数。这是强制检查,不是待办清单中的一项。 - 将报告的所有问题分类处理,并更新
src/tools.ts和src/__tests__/fixtures.ts。新增工具默认会被允许列表拒绝,但仍需明确记录处理决定。 - 重新运行构建;构建时冒烟测试和偏差检查都必须通过。
- 发布新版本,然后在 conduit 的
azure/vendor-fleet.conduit-prod.bicepparam中重新固定摘要。
绝不要跟踪 main,也不要拉取 keeper/keeper-mcp-server:latest——固定版本是经过审核的 Git 标签,我们会自行构建。
许可证
Apache-2.0(本桥接项目)。上游 Keeper 服务采用 MIT 许可证,并在构建镜像时从源码构建。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。