脱敏 VIAPI 临时桶上传错误体,避免泄漏 STS 临时凭证
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 在正文任何位置的出现替换为 ***,保留 <Code> 供诊断后再记录。 - 新增 redact_xml_element(无正则依赖)与单测覆盖脱敏效果。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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 临时凭证在正文任何位置的出现替换为 `***`,保留 `<Code>` 等可诊断信息后再记录。
|
||||
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)
|
||||
}
|
||||
|
||||
/// 把 `<tag>…</tag>` 的内容替换成 `[redacted]`(tag 内容可跨行);只有开标签无闭标签时丢弃其后全部内容。
|
||||
fn redact_xml_element(text: &str, tag: &str) -> String {
|
||||
let open = format!("<{tag}>");
|
||||
let close = format!("</{tag}>");
|
||||
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!(
|
||||
"<Error><Code>SignatureDoesNotMatch</Code>\
|
||||
<Message>mismatch for {token}</Message>\
|
||||
<SignatureProvided>sigSECRET==</SignatureProvided>\
|
||||
<StringToSignBytes>50 55 54 0a</StringToSignBytes>\
|
||||
<StringToSign>PUT\nx-oss-security-token:{token}\n/bucket/key</StringToSign></Error>"
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user