详情接口把凭证脱敏成六个星号,前端编辑弹窗把这份出参原样填回表单再提交。用户只改了个名字,库里的真 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 拿凭证」这条路在真实环境里走通一次的验证没做——它要两个租户、两套凭证和一台能收请求的外部服务器。