1334 字
7 分钟
脱敏挡住的是明文,不是凭证

详情接口把凭证脱敏成六个星号,前端编辑弹窗把这份出参原样填回表单再提交。用户只改了个名字,库里的真 token 就被覆盖成字面量 ******,这个工具再也连不上。我的判断是:星号只挡住了前端看到的明文,凭证还在库里。脱敏过的值一定会原样提交回来,换回真值只能服务端做;而服务端换回去的那一下,等于替提交的人把真 token 填到他指定的地址上,所以得先确认这次是填给谁。

一份提交在三种写回策略下的不同结局

改了个名字,这个工具就连不上了#

一个工作区服务管用户自配的 MCP:填上游地址和 header,绑到 Agent 上,沙箱带着这份配置去连第三方。

出参对 config.headers 的全部 key 和顶层敏感键做掩码,值统一换成 ******。列表和详情都脱敏,前端拿不到明文。

报障是:改完名字保存,这个 MCP 不通了。

先怀疑上游挂了或者 token 过期。查库里那条记录,headers.Authorization 存的就是六个星号本身——这两种猜测要是成立,库里存的都该是一个真 token。

从出参到入库,中间没有一步会拦下它。详情接口返回 ******,编辑弹窗把整个 config 填进表单,用户改了 name,提交回来的是完整 config,写入时把 ****** 当成用户填的新值存了进去。

沙箱随后带着这六个星号去请求第三方,401。而保存那一步返回的是 200,用户这边看不出哪里不对。

还原按 key 逐个比,有一种情形补不回来#

修在服务端。update_server 先做一次还原再往下走:提交上来的 headers 按 key 逐个和库里比,值等于占位符的取库里原值,否则当成用户显式输入。

提交内容结果
{"Authorization": "******"}取库里原值
{"Authorization": "Bearer NEW"}覆盖为新值
新增 key,值不是占位符按新值写入
原有 key 没出现在提交里删除,不复活
新增 key,值是占位符原样存 ******

最后一行是认了的:库里没有这个 key 的原值,还原不出来。它只在前端自己造一个假 key 时出现。

和前端的约定是照常提交完整 config,不需要判断用户动没动过某一行。这个判断放在前端,等于每个调用方各写一遍,漏一个就退回覆盖真值。

还原排在提取和校验之前,因为校验要看最终落库的那个值,表单里那份不作数。

真 token 是服务端替提交的人填上去的#

还原补上之后又出来一个问题,比原来那个严重。

这个接口对任意登录用户开放,同组织成员可以改别人建的记录。这是既有的权限设计,本次没动。

一个组织成员打开编辑弹窗,看到的 headers 是 ******。他把 url 改成自己的服务器,占位符一个字不动,保存。还原逻辑按 key 比对,发现是占位符,把库里的真 token 填回去写进这条记录。之后沙箱带着真凭证去请求他的服务器。

他全程没见过明文,凭证到手了。

补的约束是:提交里仍含占位符、且 scheme/host/port 与库中不一致时拒绝保存,要求重新填凭证。

两个条件必须同时成立。只判「换了 host」会误拒一个正常操作——用户换上游的同时重新填了凭证,这时提交里根本没有占位符,服务端也就没有东西可以填回去。改 path、只改名、以及没有 url 的 internal 类型都不受影响。

没修的#

库里的 config.headers 是明文 JSON。服务有一套 enc: 前缀的 AES 解密,但它只作用于启动期的环境变量,DB 里的 config 不走那条路。全部保护就是出参层这一道掩码。

绑定到 Agent 之后,这份 config 会原样写进下发给沙箱的清单文件,明文落到宿主机,再由沙箱直发第三方。直连 MCP 的三种做法只有两种能用:

做法凭证可见能不能用
走网关托管否,下发的是占位符,真地址和凭证留在网关侧能
直连带真凭证是能
直连带 ******否不能,沙箱带星号去请求直接 401

需求方确认接受第二行。

还有三条记下来没修。一是域名创建时解析到公网、事后改指内网,堵它要在连接时二次校验并锁定 IP,改造点在沙箱侧的 HTTP 客户端,跨仓库。二是上游返回 302 指向内网,同样在沙箱侧。三是测试连接接口会真实发起连接,可以当内网探测用,没做限速。

测到的是还原那一层的逻辑用例和上游变更拒绝。「同组织成员改 url 拿凭证」这条路在真实环境里走通一次的验证没做——它要两个租户、两套凭证和一台能收请求的外部服务器。