合规框架(SOC2、HIPAA、GDPR、PCI)会对每一条审计日志提出同样的五个问题:谁、什么、何时、从哪里、以何种结果,以及我们如何知道它没有被篡改。evlog 通过组合现有的基础原语来回答每一个问题。
完整性
对审计日志进行哈希链处理,这样任何篡改都能被检测出来。每个事件的哈希都包含前一个哈希,因此删除某一行会破坏从该点开始向前的链。
auditOnly(
signed(createFsDrain({ dir: '.audit' }), { strategy: 'hash-chain' }),
{ await: true },
)
请每年轮换用于 HMAC 签名审计的
secret。 轮换时,在签名旁边嵌入一个密钥 ID(例如,通过 declare module 用 keyId 扩展 AuditFields),这样旧事件仍可使用之前的 secret 进行验证。验证器应根据 ID 查找密钥,而不是假设只有一个全局 secret。查看 排水器与完整性 以了解 HMAC 和哈希链之间的区别。
脱敏
审计事件会经过你现有的 RedactConfig。与严格的审计预设组合,以强化对 PII 的处理:
import { auditRedactPreset } from 'evlog'
initLogger({
redact: {
paths: [
...(auditRedactPreset.paths ?? []),
],
},
})
该预设会在任意嵌套深度脱敏 authorization、cookie、set-cookie 以及常见的凭据键名(password、token、apiKey、cardNumber、cvv、ssn),包括 audit.changes.before / audit.changes.after 内部的内容。
GDPR 与仅追加模式
仅追加的审计日志与 GDPR 的“被遗忘权”相冲突。当前推荐的模式:
- 保持审计行不可变。
- 使用每个参与者专属的密钥加密 PII 字段(密钥保存在审计存储之外)。
- 要“遗忘”某个用户,请删除其密钥。审计行仍然保留,链仍然有效,但 PII 将无法读取。
内置的 cryptoShredding 辅助工具已列入后续路线图。
保留
保留策略从设计上来说属于存储层关注点。evlog 的审计层不会强制执行保留窗口,因为每个受支持的排水器都已经有更强大且经过审计的机制来处理此事。请选择与排水器匹配的机制:
| 排水器 | 保留机制 |
|---|---|
| FS | 将 createFsDrain({ maxFiles }) 与每日压缩器结合使用。 |
| Postgres | 计划执行 DELETE FROM audit_events WHERE timestamp < now() - interval '7 years'。 |
| Axiom / Datadog / Loki | 在平台中设置数据集保留策略。 |
| S3 Object Lock | 配置生命周期规则 + Object Lock 保留期限。 |
在你的安全策略中记录所选窗口。审计人员关注的是书面规则,而不是执行该规则的组件。
常见陷阱
- 只记录成功操作。 审计人员最关注拒绝操作。每个授权检查的否定分支都应始终将
log.audit()与log.audit.deny()配对使用。 - 通过
changes泄露 PII。auditDiff()会经过你的RedactConfig,但前提是字段路径已列出。只需在全局配置中添加一次password、token、apiKey等字段,这样以后就无需再为此操心。 - 将审计视为可观测性。 不要对审计事件进行采样、降采样或汇总。默认启用强制保留。不要禁用它。
- 混淆
actor.id与会话 ID。actor.id是稳定的用户 ID(或系统身份)。通过context.requestId/context.traceId关联会话,绝不要通过 actor 关联。 - 忘记独立运行的作业。 Cron 任务、队列工作进程和 CLI 同样会触发值得审计的操作。使用
audit()(无请求)或withAudit(),以确保覆盖范围与 HTTP 路由一致。 - 在审计排水器上跳过
await: true。 没有它,审计就是即发即弃的。事件发出与排水器完成刷新之间如果发生崩溃,就会出现操作已经发生但不存在审计行的情况。