Compliance Overview
After reading this page you will know which tables to audit, which compliance tags to apply, and how to generate evidence exports for SOC 2, HIPAA, GDPR, and PCI DSS assessments.
Framework requirements at a glance
Section titled “Framework requirements at a glance”| Requirement | SOC 2 | HIPAA | GDPR | PCI DSS | Better Audit feature |
|---|---|---|---|---|---|
| Record every data mutation | CC7.2 | §164.312(b) | Art. 30 | 10.2 | ORM auto-capture |
| Track the acting user | CC6.1 | §164.312(a)(1) | Art. 5(2) | 10.1 | Actor context |
| Capture before/after state | CC7.2 | §164.312(b) | Art. 17 | 10.3 | Before/after snapshots |
| Protect sensitive fields | CC6.5 | §164.312(a)(2)(iv) | Art. 25 | 3.4 | Field redaction |
| Tag entries by framework | CC7.1 | §164.530(j) | Art. 30 | 10.2 | Compliance tags |
| Query and export evidence | CC7.3 | §164.530(j) | Art. 15 | 10.7 | Query & export |
| Retain logs for required period | CC7.4 | §164.530(j)(2) | Art. 5(1)(e) | 10.7 | Retention policy |
| Alert on critical events | CC7.3 | §164.308(a)(6)(ii) | — | 10.6 | Notify flag |
What to capture and why
Section titled “What to capture and why”Not every table needs the same level of detail. Focus your audit surface on data that matters to your compliance posture. The snippets below assume an audit instance created with betterAudit() — see the full example at the bottom for a complete setup.
Identity and access
Section titled “Identity and access”Tables: users, roles, permissions, sessions
Every compliance framework requires tracking who has access and how that access changes. Capture account creation, role assignments, permission grants/revokes, and session events.
import { betterAudit } from "@usebetterdev/audit";import { drizzleAuditAdapter } from "@usebetterdev/audit/drizzle";
const audit = betterAudit({ database: drizzleAuditAdapter(db), auditTables: ["users", "roles", "permissions", "sessions"],});
audit.enrich("users", "DELETE", { label: "User account deleted", severity: "critical", compliance: ["gdpr", "soc2", "hipaa"], notify: true,});
audit.enrich("roles", "*", { severity: "high", compliance: ["soc2"],});
audit.enrich("permissions", "*", { severity: "high", compliance: ["soc2"],});Protected health information (HIPAA)
Section titled “Protected health information (HIPAA)”Tables: patients, medical_records, prescriptions, appointments
HIPAA requires tracking every access and modification to PHI. Combine compliance tags with field redaction to log the event without storing the sensitive data itself.
audit.enrich("patients", "*", { severity: "high", compliance: ["hipaa"], redact: ["ssn", "insurance_id"],});
audit.enrich("medical_records", "*", { severity: "critical", compliance: ["hipaa"], redact: ["diagnosis", "treatment_notes"], notify: true,});Financial and payment data (PCI DSS)
Section titled “Financial and payment data (PCI DSS)”Tables: payments, invoices, subscriptions, refunds
PCI DSS demands a record of all access to cardholder data environments. Redact card numbers and use compliance tags so you can filter and export PCI-specific entries during assessments.
audit.enrich("payments", "*", { severity: "high", compliance: ["pci"], redact: ["card_number", "cvv", "card_expiry"],});
audit.enrich("refunds", "*", { severity: "high", compliance: ["pci", "soc2"],});Personal data (GDPR)
Section titled “Personal data (GDPR)”Tables: users, profiles, consent_records, data_exports
GDPR requires demonstrating lawful processing and honoring data subject rights. Audit logs provide evidence that deletions, consent changes, and data exports happened as requested.
audit.enrich("consent_records", "*", { severity: "high", compliance: ["gdpr"],});
audit.enrich("profiles", "UPDATE", { label: "Personal data updated", compliance: ["gdpr"], description: ({ diff }) => { const fields = diff?.changedFields.join(", ") ?? "unknown fields"; return `Profile fields changed: ${fields}`; },});
audit.enrich("profiles", "DELETE", { label: "Personal data erased", severity: "critical", compliance: ["gdpr"], notify: true,});Generating evidence
Section titled “Generating evidence”When an auditor requests evidence, you need to produce filtered, time-bounded exports that match the scope of the control being assessed. Better Audit’s query builder and export engine handle this without custom SQL.
Compliance-scoped queries
Section titled “Compliance-scoped queries”Use .compliance() to pull entries tagged with a specific framework. Tags use AND semantics, so .compliance("gdpr", "hipaa") returns only entries tagged with both:
// All GDPR-relevant events in the last quarterconst gdprQuery = audit.query() .compliance("gdpr") .since("90d");
// SOC 2 critical events this yearconst soc2Query = audit.query() .compliance("soc2") .severity("critical") .since("1y");Exporting for auditors
Section titled “Exporting for auditors”Generate downloadable reports scoped to a framework, time range, and severity:
// CSV export for SOC 2 assessment — last 12 months, high and critical onlyconst soc2Evidence = audit.query() .compliance("soc2") .severity("high", "critical") .since("1y");
const response = audit.exportResponse({ format: "csv", query: soc2Evidence, filename: "soc2-evidence-2025",});For HIPAA audits, export PHI access logs with redacted fields — the redactedFields column in each entry proves sensitive data was excluded from snapshots:
const hipaaEvidence = audit.query() .compliance("hipaa") .since("1y");
const result = await audit.export({ format: "json", jsonStyle: "array", query: hipaaEvidence, output: "string",});Retention alignment
Section titled “Retention alignment”Compliance frameworks impose minimum retention periods:
| Framework | Minimum retention |
|---|---|
| SOC 2 | 1 year (typical) |
| HIPAA | 6 years |
| GDPR | As short as necessary (data minimization) |
| PCI DSS | 1 year (3 months immediately accessible) |
Configure Better Audit’s retention policy to match your strictest requirement, then use the CLI purge command with date filters for framework-specific cleanup:
const audit = betterAudit({ database: drizzleAuditAdapter(db), auditTables: ["users", "payments", "patients"], retention: { days: 2190, // ~6 years — HIPAA, the strictest requirement },});Putting it together
Section titled “Putting it together”Full example: multi-framework compliance setup
A typical compliance setup combines global defaults with table-specific overrides:
import { betterAudit } from "@usebetterdev/audit";import { drizzleAuditAdapter } from "@usebetterdev/audit/drizzle";
const audit = betterAudit({ database: drizzleAuditAdapter(db), auditTables: ["users", "roles", "payments", "patients", "consent_records"], retention: { days: 2190 },});
// Global baseline — every entry gets SOC 2 tagging and low severityaudit.enrich("*", "*", { severity: "low", compliance: ["soc2"],});
// Destructive operations are high severity by defaultaudit.enrich("*", "DELETE", { severity: "high",});
// Identity tables — SOC 2 cares about access control changesaudit.enrich("roles", "*", { severity: "high" });audit.enrich("users", "DELETE", { severity: "critical", compliance: ["gdpr"], notify: true,});
// Healthcare tables — HIPAA with redactionaudit.enrich("patients", "*", { compliance: ["hipaa"], redact: ["ssn", "insurance_id"],});
// Payment tables — PCI DSS with redactionaudit.enrich("payments", "*", { severity: "high", compliance: ["pci"], redact: ["card_number", "cvv"],});
// Consent — GDPR trackingaudit.enrich("consent_records", "*", { severity: "high", compliance: ["gdpr"],});With this configuration, a users DELETE entry resolves to severity: "critical", compliance: ["soc2", "gdpr"], notify: true — ready for both SOC 2 and GDPR evidence pulls.