项目概述
本项目实现防篡改审计日志:以仅追加方式记录事件,每条记录链接前一条的哈希,使事后任何修改或删除都能被校验精确定位。威胁模型是拥有数据库权限的内部人,因此目标不是阻止写入,而是让静默篡改无处藏身。
架构与关键决策
每个事件保存操作者、动作、目标、时间戳、前一事件哈希与自身哈希,哈希覆盖规范化字节序列与前一哈希,因此改动任一较早字段都会改变其后整条链。校验从最近的检查点重放并逐条重算哈希,在首个不匹配处停止;检查点对哈希签名并投递到外部仅追加存储,使旧区间可裁剪仍可验证。按主体分流,使链内有序而跨流无关。
实施与测试
追加已知序列并断言校验通过,直接在数据库中改动一个字段并断言校验精确报出首个损坏记录 id;并发追加断言链不分叉;另以黄金向量测试固定规范化后的摘要,防止序列化被无意改动。
限制与后续
- 哈希链按流分区,跨流不保证全序。
- 检查点签名密钥轮换为手动。
- 载荷列以明文存储,依赖字段级脱敏而非逐值加密。
代码示例
pnpm init
pnpm add pg express
pnpm add -D typescript tsx vitest
audit-log-service/
├── src/append.ts
├── src/hash-chain.ts
├── src/verify.ts
├── src/checkpoint.ts
├── tests/audit.test.ts
└── package.json
const canonical = JSON.stringify([prev, actor, action, target, at]);
await db.tx(async (t) => {
const head = await t.getHead(streamId);
const hash = sha256(canonicalOf(head, event));
await t.insert({ ...event, prev: head.hash, hash });
});
await db.insertCheckpoint({ streamId, at: head.id, hash: head.hash, signature: sign(head.hash) });
if (sha256(canonicalOf(prev, event)) !== event.hash) return { valid: false, at: event.id };
app.get('/audit', limitAndFilter(handler)); // no PUT/DELETE routes
pnpm vitest run