加强错误报告凭据脱敏
使用 Unicode 空白归一化匹配凭据标记 补充 NBSP 和 EM SPACE 回归测试并同步技术文档
This commit is contained in:
@@ -19,7 +19,7 @@ AI Game Creator Shell 采用 IDEA 风格的当前进程错误报告:错误事
|
||||
## HTTP 与存储
|
||||
|
||||
- 登录态客户端使用 `POST /api/error-reports`,请求 DTO 位于 `shared-contracts::error_reports`。
|
||||
- api-server 对请求体设置 24 MiB 上限,并校验 schemaVersion、submissionId、事件/日志数量和 20 MiB 压缩包上限;事件字段、用户说明和日志名/内容均做长度限制与凭据脱敏,归档使用 `events.jsonl`(每行一个事件)。结构化事件只保存在当前进程内,用户提交时才生成 `events.jsonl`,不在磁盘单独持久化。submissionId 提供重放幂等;客户端对同一批事件复用稳定 submissionId,服务端提交成功后立即反馈成功,本地事件 ack 独立重试,不因 ack 失败误报提交失败;SpacetimeDB procedure 使用服务 identity 门禁,`user_id` 由 api-server 注入并在 module 校验,时间戳仍使用 `ctx.timestamp`。创建 procedure 额外限制单 identity 每小时 100 次提交。
|
||||
- api-server 对请求体设置 24 MiB 上限,并校验 schemaVersion、submissionId、事件/日志数量和 20 MiB 压缩包上限;事件字段、用户说明和日志名/内容均做长度限制与凭据脱敏(匹配归一化覆盖 Unicode 空白字符),归档使用 `events.jsonl`(每行一个事件)。结构化事件只保存在当前进程内,用户提交时才生成 `events.jsonl`,不在磁盘单独持久化。submissionId 提供重放幂等;客户端对同一批事件复用稳定 submissionId,服务端提交成功后立即反馈成功,本地事件 ack 独立重试,不因 ack 失败误报提交失败;SpacetimeDB procedure 使用服务 identity 门禁,`user_id` 由 api-server 注入并在 module 校验,时间戳仍使用 `ctx.timestamp`。创建 procedure 额外限制单 identity 每小时 100 次提交。
|
||||
- 归档构建只在请求生命周期内使用受 20 MiB 上限约束的内存 `Vec<u8>`,随后直接 PUT 到私有 OSS;服务端不写本地报告文件,也不保留本地索引。OSS 上传失败不写入数据库,调用方可稍后重新提交。
|
||||
- 归档对象使用固定私有 OSS key:`agc/error-reports/v1/{batchId}.zip`;key 只由报告 UUID 决定,不包含时间戳。上传成功后才写入 SpacetimeDB `error_report` 元数据表;`userId + submissionId` 由唯一幂等键保证重放返回已有记录。完整事件、说明和日志只存在 OSS ZIP。
|
||||
- `agc` 是服务端专用私有前缀;公共直传票据、通用 object-key 规范化和 legacy 公开路径均拒绝该前缀。归档内同名日志会自动加数字后缀,读取本机诊断日志时拒绝符号链接/非普通文件。
|
||||
|
||||
@@ -645,10 +645,10 @@ fn sanitize_report_text(v: &str) -> String {
|
||||
sanitize_report_text_with_limit(v, MAX_EVENT_FIELD_CHARS)
|
||||
}
|
||||
fn sanitize_report_text_with_limit(v: &str, max: usize) -> String {
|
||||
let l = v.to_ascii_lowercase();
|
||||
let normalized = l
|
||||
let normalized = v
|
||||
.to_lowercase()
|
||||
.chars()
|
||||
.filter(|c| !c.is_ascii_whitespace())
|
||||
.filter(|c| !c.is_whitespace())
|
||||
.collect::<String>();
|
||||
if [
|
||||
"authorization:",
|
||||
@@ -679,6 +679,23 @@ fn sanitize_report_text_with_limit(v: &str, max: usize) -> String {
|
||||
.take(max)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::sanitize_report_text_with_limit;
|
||||
|
||||
#[test]
|
||||
fn redacts_credentials_separated_by_unicode_whitespace() {
|
||||
for value in [
|
||||
"token\u{00a0}=\u{2003}secret",
|
||||
"Authorization:\u{2003}Bearer\u{00a0}secret",
|
||||
"api-key\u{00a0}:\u{2003}secret",
|
||||
] {
|
||||
assert_eq!(sanitize_report_text_with_limit(value, 512), "[REDACTED]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sanitize_log_name(v: &str) -> String {
|
||||
let s = v
|
||||
.rsplit(['/', '\\'])
|
||||
|
||||
Reference in New Issue
Block a user