From cf7f75cb6f4121069c28abec9d035977a61c6b60 Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 10 Jul 2026 02:49:30 +0000 Subject: [PATCH] =?UTF-8?q?=E8=84=B1=E6=95=8F=20VIAPI=20=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E6=A1=B6=E4=B8=8A=E4=BC=A0=E9=94=99=E8=AF=AF=E4=BD=93=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E6=B3=84=E6=BC=8F=20STS=20=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E5=87=AD=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OSS 签名类错误(如 SignatureDoesNotMatch)会在错误体里回显 StringToSign (含 x-oss-security-token 明文)、StringToSignBytes(十六进制)和 SignatureProvided。 原实现把该错误体裸拼进 MattingError 消息,随后经 aliyun_matting 进入 external_api_call_failure 审计的 errorMessage/rawExcerpt 与 OTLP 日志, 泄漏 STS 临时凭证。 - 上传非 2xx 分支改用 sanitize_oss_upload_error_body:剥掉 StringToSign / StringToSignBytes / SignatureProvided 三个元素,并把已知 security_token 在正文任何位置的出现替换为 ***,保留 供诊断后再记录。 - 新增 redact_xml_element(无正则依赖)与单测覆盖脱敏效果。 Co-Authored-By: Claude Opus 4.8 --- server-rs/crates/platform-matting/src/lib.rs | 68 +++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/server-rs/crates/platform-matting/src/lib.rs b/server-rs/crates/platform-matting/src/lib.rs index 20c4b499b..82f5b0535 100644 --- a/server-rs/crates/platform-matting/src/lib.rs +++ b/server-rs/crates/platform-matting/src/lib.rs @@ -448,10 +448,12 @@ impl MattingClient { let status = response.status(); if !status.is_success() { let body = response.text().await.unwrap_or_default(); + // OSS 签名类错误体会回显 StringToSign(含 x-oss-security-token 明文)、StringToSignBytes + // 和 SignatureProvided;脱敏后再记录,避免 STS 临时凭证进审计元数据与日志。 return Err(MattingError::Upstream(format!( "上传 VIAPI 临时桶失败(HTTP {}):{}", status.as_u16(), - truncate_for_log(&body) + sanitize_oss_upload_error_body(&body, &sts.security_token) ))); } @@ -685,6 +687,47 @@ fn sanitize_result_download_error_message(message: String) -> String { message } +/// 脱敏 OSS 上传错误体:OSS 签名类错误会回显 StringToSign(含 `x-oss-security-token` 明文)、 +/// StringToSignBytes(其十六进制)和 SignatureProvided(签名串)。剥掉这三个元素,并把已知的 +/// STS 临时凭证在正文任何位置的出现替换为 `***`,保留 `` 等可诊断信息后再记录。 +fn sanitize_oss_upload_error_body(body: &str, security_token: &str) -> String { + let mut sanitized = body.to_string(); + for tag in ["StringToSign", "StringToSignBytes", "SignatureProvided"] { + sanitized = redact_xml_element(&sanitized, tag); + } + let security_token = security_token.trim(); + if !security_token.is_empty() { + sanitized = sanitized.replace(security_token, "***"); + } + truncate_for_log(&sanitized) +} + +/// 把 `` 的内容替换成 `[redacted]`(tag 内容可跨行);只有开标签无闭标签时丢弃其后全部内容。 +fn redact_xml_element(text: &str, tag: &str) -> String { + let open = format!("<{tag}>"); + let close = format!(""); + let mut result = String::with_capacity(text.len()); + let mut rest = text; + while let Some(start) = rest.find(&open) { + result.push_str(&rest[..start]); + result.push_str(&open); + result.push_str("[redacted]"); + let after_open = start + open.len(); + match rest[after_open..].find(&close) { + Some(rel_end) => { + result.push_str(&close); + rest = &rest[after_open + rel_end + close.len()..]; + } + None => { + rest = ""; + break; + } + } + } + result.push_str(rest); + result +} + fn insert_header( headers: &mut reqwest::header::HeaderMap, name: &'static str, @@ -839,6 +882,29 @@ mod tests { ); } + #[test] + fn oss_upload_error_body_redacts_signature_material_and_token() { + let token = "CAISabcSECRETtoken123"; + let body = format!( + "SignatureDoesNotMatch\ + mismatch for {token}\ + sigSECRET==\ + 50 55 54 0a\ + PUT\nx-oss-security-token:{token}\n/bucket/key" + ); + + let sanitized = sanitize_oss_upload_error_body(&body, token); + + // 敏感串全部消失:StringToSign 明文、其中的 token、十六进制、签名串、以及 Message 里的 token 明文。 + assert!(!sanitized.contains(token), "STS token 不能残留(含元素外的明文)"); + assert!(!sanitized.contains("x-oss-security-token:CAIS"), "StringToSign 明文不能残留"); + assert!(!sanitized.contains("sigSECRET"), "SignatureProvided 不能残留"); + assert!(!sanitized.contains("50 55 54 0a"), "StringToSignBytes 不能残留"); + // 可诊断信息保留。 + assert!(sanitized.contains("SignatureDoesNotMatch"), "OSS Code 应保留供诊断"); + assert!(sanitized.contains("[redacted]"), "签名材料元素应被脱敏为 [redacted]"); + } + fn noise_image(width: u32, height: u32) -> image::DynamicImage { // 不易压缩的伪随机噪声,保证 PNG 体积随像素数量增长,可触发降尺寸循环。 let mut img = image::RgbaImage::new(width, height);