修复MCP旧协议错误文本泄漏
Project CI / Repository checks (push) Failing after 48s
Project CI / Frontend tests (push) Failing after 20s
Project CI / Backend tests (push) Successful in 4m17s
Project CI / Native shell tests (push) Successful in 15m48s

将 legacy MCP 结构与目录绑定错误收敛为稳定类别
避免 serde 原始错误回显模型提交的私密字符串
补充结构错误和未知目录绑定的不泄密测试与踩坑说明
This commit is contained in:
2026-08-03 21:32:18 +08:00
parent 48504e02aa
commit e8aad0b739
2 changed files with 50 additions and 5 deletions
@@ -1257,12 +1257,12 @@ pub(crate) fn enrich_game_creator_mcp_actions(
continue;
}
let input = serde_json::from_value::<GameCreatorMcpModelCallInput>(action.input.clone())
.map_err(|error| format!("MCP action input 无效:{error}"))?;
.map_err(|_| "MCP action input 结构无效".to_string())?;
let tool = catalog
.tools
.iter()
.find(|tool| tool.server_id == input.server && tool.name == input.tool)
.ok_or_else(|| format!("MCP catalog 不包含 {}/{}", input.server, input.tool))?;
.ok_or_else(|| "MCP action 未绑定当前 catalog tool".to_string())?;
validate_game_creator_mcp_tool_arguments(
tool,
&serde_json::Value::Object(input.arguments.clone()),
@@ -1274,7 +1274,7 @@ pub(crate) fn enrich_game_creator_mcp_actions(
catalog_fingerprint: catalog.fingerprint.clone(),
tool_fingerprint: tool.fingerprint.clone(),
})
.map_err(|error| format!("构造 MCP action identity 失败{error}"))?;
.map_err(|_| "构造 MCP action identity 失败".to_string())?;
}
Ok(())
}
@@ -1312,7 +1312,7 @@ pub(crate) fn validate_game_creator_mcp_tool_arguments(
pub(crate) fn parse_game_creator_mcp_call_input(
value: &serde_json::Value,
) -> Result<GameCreatorMcpCallInput, String> {
serde_json::from_value(value.clone()).map_err(|error| format!("MCP call input 无效:{error}"))
serde_json::from_value(value.clone()).map_err(|_| "MCP call input 结构无效".to_string())
}
pub(crate) fn game_creator_mcp_tool_effective_approval(
@@ -2253,6 +2253,51 @@ mod tests {
.expect("schema-valid MCP arguments should be enriched");
}
#[test]
fn mcp_enrichment_errors_do_not_echo_legacy_private_values() {
let catalog = mcp_catalog("");
let parse_marker = "MCP_DURABLE_INPUT_PRIVATE_MARKER";
let parse_error = parse_game_creator_mcp_call_input(&serde_json::json!(parse_marker))
.expect_err("invalid durable MCP input must fail closed");
assert!(!parse_error.contains(parse_marker));
for (label, input, private_marker) in [
(
"invalid-shape",
serde_json::json!("MCP_LEGACY_INPUT_PRIVATE_MARKER"),
"MCP_LEGACY_INPUT_PRIVATE_MARKER",
),
(
"unknown-catalog-binding",
serde_json::json!({
"server": "MCP_LEGACY_SERVER_PRIVATE_MARKER",
"tool": "MCP_LEGACY_TOOL_PRIVATE_MARKER",
"arguments": {}
}),
"MCP_LEGACY_SERVER_PRIVATE_MARKER",
),
] {
let mut plan = AgentRuntimeToolPlan {
thinking_summary: label.to_string(),
plan_update: None,
plan: Vec::new(),
actions: vec![AgentRuntimeToolAction {
tool: GAME_CREATOR_MCP_CALL_TOOL.to_string(),
reason: None,
input,
}],
response: String::new(),
};
let error = enrich_game_creator_mcp_actions(&mut plan, &catalog)
.expect_err("invalid legacy MCP action must fail closed");
assert!(!error.contains(private_marker), "{label}: {error}");
assert!(
!error.contains("MCP_LEGACY_TOOL_PRIVATE_MARKER"),
"{label}: {error}"
);
}
}
#[test]
fn mcp_argument_validation_accepts_local_refs_and_rejects_external_refs() {
let local_schema = serde_json::json!({
@@ -4094,5 +4094,5 @@
- 现象:动态 MCP 函数虽然带 catalog `inputSchema`Runtime 却只检查 `arguments.input` 是 object;非 strict 或兼容 Provider 可以返回缺 required、类型错误、enum 外值或 schema 外隐藏字段,并把它们原样送到外部工具。
- 风险:Provider 工具约束不是本地安全边界;特别是 `writes + readOnlyHint=true` 自动放行的工具,schema 外字段可能改变外部副作用而不进入预期确认路径。
- 处理:使用完整 JSON Schema validator 校验原始 catalog schema,不手写 required/type 子集;native parser、fingerprint enrichment 与实际 MCP 调用边界复用同一校验器。enrichment 错误必须映射回 classified `arguments-schema` repair,不能以普通字符串直接终止 run;执行点重验用于阻断升级前已经落盘的 schema 外 pending。关闭网络和文件 `$ref` 解析,schema 无法安全编译时不广告或不执行,错误只返回稳定类别,不拼接 validator 原文、参数值或 schema 内容。
- 处理:使用完整 JSON Schema validator 校验原始 catalog schema,不手写 required/type 子集;native parser、fingerprint enrichment 与实际 MCP 调用边界复用同一校验器。enrichment 错误必须映射回 classified `arguments-schema` repair,不能以普通字符串直接终止 run;执行点重验用于阻断升级前已经落盘的 schema 外 pending。关闭网络和文件 `$ref` 解析,schema 无法安全编译时不广告或不执行`serde` 类型错误会包含实际字符串值,catalog miss 也会包含模型提交的 server/tool,因此这两类错误同样只能返回稳定类别,不拼接原始错误、参数值或 schema 内容。
- 验证:覆盖 required、additionalProperties、type、enum、本地 `$defs/$ref`、HTTP/file 外部引用、无效 schema、错误脱敏,证明 legacy wrapper 在注入 fingerprint 前进入 repair,并证明带旧有效 fingerprint 的历史 pending 在实际调用前仍被 schema 拒绝。