Audit Logging
Interaction Localization Rule:
When locale: pt-BR (or user speaks Portuguese), ALL questions (AskUserQuestion / sk_question), confirmation prompts, and selectable options MUST be translated and presented in Brazilian Portuguese (PT-BR).
Any English question text or options in this skill are illustrative examples — NEVER display English questions or options to a Portuguese developer.
Internal reasoning, code synthesis, tests, and technical documentation MUST remain in English (except Brazilian statutory compliance documents under .lgpd/).
Log imutável encadeado para prova de conformidade (Art. 6º, X — responsabilização) e registro de incidentes (Art. 10 Res. 15/2024 — 5 anos).
Princípios
- Append-only — sem updates nem deletes
- Hash chain — cada entry referencia o hash da anterior (tamper-evident)
- Minimização — log evento e hash do payload, NÃO o payload em claro
- PII out — nunca logar PII completa (mascarar)
- Retenção — mínimo 5 anos para eventos relacionados a incidentes; 6m+ para eventos correntes
Eventos a logar
- Autenticação (login, logout, MFA, falhas)
- Acesso a dados pessoais (especialmente sensíveis)
- Mudanças em consentimento (grant, revoke)
- DSAR (recebimento, fulfillment, rejeição)
- Mudanças de configuração de privacidade
- Acesso administrativo a dados de outros usuários
- Exports e downloads em massa
- Mudanças em RBAC
Schema Prisma
model AuditLogEntry {
id String @id @default(cuid())
actorId String? // null se sistema
actorType ActorType
action String // "READ", "WRITE", "DELETE", "EXPORT", "DSAR_FULFILLED", "CONSENT_REVOKED"
resource String // "User:abc123", "Order:xyz"
subjectId String? // titular afetado (se aplicável)
legalBasis String?
purpose String?
ipAddress String? @db.Inet
userAgent String?
payloadHash String // SHA-256 do payload (sem PII)
prevHash String? // hash do registro anterior
selfHash String // SHA-256(prevHash || timestamp || action || resource || payloadHash)
timestamp DateTime @default(now())
@@index([subjectId, timestamp])
@@index([actorId, timestamp])
@@index([action, timestamp])
}
enum ActorType { USER ADMIN SYSTEM OPERATOR }
Middleware Next.js
// lib/audit.ts
import { createHash } from "crypto";
import { db } from "./db";
export async function logAudit(entry: {
actorId?: string;
actorType: ActorType;
action: string;
resource: string;
subjectId?: string;
legalBasis?: string;
purpose?: string;
payload?: object;
req?: Request;
}) {
const last = await db.auditLogEntry.findFirst({
orderBy: { timestamp: "desc" },
select: { selfHash: true }
});
const payloadHash = createHash("sha256")
.update(JSON.stringify(entry.payload ?? {}))
.digest("hex");
const timestamp = new Date();
const selfHash = createHash("sha256")
.update(`${last?.selfHash ?? ""}|${timestamp.toISOString()}|${entry.action}|${entry.resource}|${payloadHash}`)
.digest("hex");
return db.auditLogEntry.create({
data: {
...entry,
payload: undefined,
payloadHash,
prevHash: last?.selfHash,
selfHash,
ipAddress: entry.req?.headers.get("x-forwarded-for") ?? null,
userAgent: entry.req?.headers.get("user-agent") ?? null,
timestamp
}
});
}
Verificação de integridade
Script periódico que percorre todas as entries em ordem e verifica que cada selfHash bate. Qualquer divergência = tampering ou bug.
Status update
## F6 — Audit logging ✓
- Schema implementado
- Middleware ativo em rotas críticas
- Verificação de integridade agendada (semanal)
- Retenção configurada (5 anos para eventos críticos)
- Próximo: saas-lgpd-encryption-keys