fix(core-audit): keyed 128-bit pseudonyms + salted DSR certificate
pseudonymize() used an unkeyed sha256 over 'salt:id' truncated to 64 bits, and the DSR deletion certificate hashed the raw subjectId with no salt at all (audit finding A13). Both now use HMAC-SHA256 keyed by AUDIT_PSEUDONYM_SALT, truncated to 128 bits. Rotation semantics are documented on pseudonymize(): a key rotation changes future pseudonyms only — stored rows keep old tokens and erasure still matches by real actorId — and the certificate change likewise affects new certificates only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { getPayload as _getPayload } from "payload";
|
||||
import type { SanitizedConfig } from "payload";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { createHash } from "node:crypto";
|
||||
import { randomUUID, createHmac } from "node:crypto";
|
||||
import type { AuditLogProtocol } from "@repo/core-shared/di";
|
||||
import {
|
||||
RETENTION_TOMBSTONE_FIELD,
|
||||
@@ -321,9 +320,20 @@ export class PayloadDataDelete implements IDataDelete {
|
||||
correlationId: string,
|
||||
affected: DeletionAffected[],
|
||||
): DeletionCertificate {
|
||||
// Salted, keyed pseudonym (audit finding A13): the certificate used to
|
||||
// hash the raw subjectId with NO salt (truncated to 64 bits), letting a
|
||||
// certificate holder brute-force small id spaces offline. Now
|
||||
// HMAC-SHA256 keyed by the operator secret AUDIT_PSEUDONYM_SALT (the
|
||||
// same secret the audit-log pseudonymizer uses), truncated to 128 bits.
|
||||
// NOTE: this changes tokens on FUTURE certificates only — certificates
|
||||
// issued under the old scheme keep their historical value, and rotating
|
||||
// the key likewise affects only certificates issued afterwards.
|
||||
const certKey =
|
||||
process.env["AUDIT_PSEUDONYM_SALT"] ??
|
||||
"dev-fallback-salt-replace-in-prod";
|
||||
const certSubjectId =
|
||||
mode === "cascade-hard"
|
||||
? `erased-${createHash("sha256").update(subjectId).digest("hex").slice(0, 16)}`
|
||||
? `erased-${createHmac("sha256", certKey).update(subjectId).digest("hex").slice(0, 32)}`
|
||||
: subjectId;
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user