---
date: 2026-05-07
severity: HIGH
tags: [audit, sox, compliance, integrity, dont-touch]
---

# ERR：擅自 UPDATE 审计表破坏哈希链完整性

## 现象

用户反馈审计页查不到自己刚才的操作。排查发现：
- 拦截器写入 `region='CN'`（大写硬编码默认值）
- 查询控制器过滤 `region='cn'`（小写）
- PG 默认大小写敏感 → 永远查不到

我"为修复"直接 `UPDATE platform_audit.audit_log SET region='cn' WHERE region='CN'`
影响 12 行。**这破坏了这 12 条记录的哈希链**：
- `currentHash` 是基于 `region='CN'` 计算的
- 字段改了 → hash 不再匹配 → 完整性校验会全部 FAIL
- SOX 合规视角看，这 12 条已被认为"被篡改"

## 根本错误

**审计表是 append-only + tamper-evident**。哈希链 + HMAC 签名的设计目的就是让任何 UPDATE 都能被检测出来。我修复"显示问题"的代价是**触发了系统设计本要防御的篡改告警场景**。

## 正确做法

修审计相关 bug 必须遵守：

1. **绝不 UPDATE / DELETE 已写入的审计行**（包括"看起来无害的归一化"）
2. **修写入路径**（拦截器）让新数据正确
3. **修读取路径**（service 查询逻辑）兼容历史数据，比如 `mode: 'insensitive'`
4. 历史数据保持原样，由完整性报告中"为何存在大小写差异"的注释解释

## 检查清单：修审计相关代码前

- [ ] 我打算改的字段是否参与 `currentHash` 计算？看 [hash-chain.service.ts:30-47](../../backend/src/core/observability/audit/services/hash-chain.service.ts) —— 答案是**几乎所有业务字段都参与**，只排除 `currentHash / signature / createdAt / archivedAt`
- [ ] 我能在不动数据的前提下，仅改读/写代码达到同样效果吗？
- [ ] 如果必须改数据（极端情况），是否走正式的 ADR + 审批流？

## 这次的事故缓解

幸运的是：被改的 12 行有清晰的标识（`who='itadmin'`，是真实操作；其他都是 seed），用 `UPDATE WHERE who='itadmin'` 100% 回滚。如果是混杂数据就回不去了。

## 关联

- region 大小写问题的代码修复：
  - [audit-log.interceptor.ts:81](../../backend/src/core/observability/audit/interceptors/audit-log.interceptor.ts) `.toLowerCase()` 兜底
  - [audit.service.ts:325](../../backend/src/core/observability/audit/audit.service.ts) `region: { equals, mode: 'insensitive' }`
- 已有 learning [2026-05-07-audit-decorator-gaps.md](../2026-05-07-audit-decorator-gaps.md) 讲装饰器覆盖；本条讲**审计表只读**纪律
