Compliance & data protection
Read this page in the documentation
Compliance & data protection src/compliance layers privacy and security tooling on the ORM core: field encryption, masking, retention, right-to-erasure, consent, a SQL firewall, tamper-evident storage and more. Each piece is a narrow, single-purpose class or decorator built on the ORM's own hooks and decorators, plus one composite @Security decorator that switches several of them on per regulatory framework. Read this first. This module is a scaffold you finish wiring, not a certified compliance implementation. Several controls are partially implemented or simulated. compliance documents the status of every file, including a Known issues section. Read it before you depend on any of these controls in a regulated environment, and check the specific file you are relying on. What is here Area | Modules | --- | --- | Subject rights | DSARWorkflow, RightToErasure, DataPortabilityExporter | Privacy & consent | ConsentRecord, ConsentVersioning, PrivacyImpactAssessment, PseudonymizationService, DataClassifier, SensitiveDataDiscovery | Security controls | FieldEncryption, DataMasker, RowLevelSecurity, SessionIsolation, TLSEnforcer, QueryFirewall, RateLimiter | Audit & integrity | ComplianceAuditTrail, ImmutableRecord, WORMStorage, BackupVerification, BreachDetector, SecurityMonitor, DataLineage, CrossBorderLog | Lifecycle | DataRetentionPolicy | Composite | @Security, @Table, @Database, setupSecurity, setupAllSecurity | Everything, including DataLineage / @LineageTrack, is re-exported from the package root. Note that data-lineage's hook installer is exported as applyLineageHooks, matching applyMaskingHooks and applyPseudonymizationHooks. Field encryption Real AES-256-GCM, applied through model hooks. Values are stored as enc:v1:<base64(iv‖tag‖ciphertext)>. Writes are encrypted, reads decrypted, and a value that is already prefixed is not encrypted twice — so an existing plaintext column can be migrated in place. A 64-character key is read as hex; anything else is SHA-256'd into 32 bytes. encryptValue() / decryptValue() are exported if you need them outside a model. Limits worth knowing before you commit: one global in-process key, no KDF salt, no rotation, no per-tenant keys, and string-typed fields only. Encrypted columns are also opaque to the database — you cannot index, sort or LIKE them. Masking Redacts values on read, per role. Pattern | Output | --- | --- | ssn | --1234 | card-last4 | 4242 | card-first6 | first six digits kept | email | a@example.com | phone | last digits kept | full | entirely masked | custom | your customMasker function | showFirst / showLast / maskChar tune the generic patterns. mask(instance, role) and maskMany(rows, role) apply the rules on demand for one response rather than globally. Masking is a display control — the real value is still in the database and still in the query result before the hook runs. Combine it with encryption if the value must not be readable at rest. Retention maxAge accepts years / months / days / hours, or an exact ms override. Note the arithmetic is approximate — a month is 30 days and a year 365 — so use ms when the boundary has to be exact. There is no scheduler. purge() runs when you call it; put it on a cron or job queue you own. Right to erasure 'delete' hard-destroys the rows; 'anonymize' nulls the PII fields in place, which is usually what you want when other records must keep referring to the row. cascade walks associated models recursively. RightToErasure.exists() checks whether anything matches before you promise a subject an outcome. Consent The log is append-only: a withdrawal adds a row rather than editing the grant, so the history is intact. ConsentVersioning implements an overlapping concept in a second, uncoordinated table. Pick one and use it consistently. Query firewall A regex allow/block engine over raw SQL, installed by monkey-patching prorm.query. It ships per-dialect rule sets (PostgreSQL, MySQL, MariaDB, SQL Server, Oracle, SQLite) and per-framework bundles (OWASP, CIS, PCI DSS, HIPAA, SOX, NIST). Two limits define what it is: it sees only queries routed through prorm.query, and it is regex matching, not SQL parsing. Treat it as defence in depth behind parameterised queries, never as your protection against injection. Parameterised queries — which the ORM uses everywhere — are that protection. Immutability and WORM WORMStorage adds an active → archived → frozen state machine with a tamper-log chain. Its hash is a 32-bit rolling hash, not a cryptographic one, and verifyIntegrity() checks the chain for gaps without re-verifying the underlying records. It demonstrates the shape; swap in SHA-256 before it carries weight. The @Security composite The intended front door: name your frameworks, and the decorator applies the controls each one requires alongside anything you pass explicitly. setupSecurity() is where the deferred hooks are actually wired — encryption, masking and audit registration happen there, not at decoration time. Skipping it leaves the decorators as metadata. Read back with getSecurityFrameworks, getTableSecurity, getDatabaseSecurity, hasAudit, isImmutableModel. Several @Security options (sensitiveDataDiscovery, consentVersioning, dsar, pia, backupVerification, queryFirewall) only set a security flag on the class without wiring the underlying subsystem. They type-check, but passing one does not switch the control on — wire those subsystems directly instead. Choosing what to use Requirement | Reach for | --- | --- | Column must be unreadable at rest | FieldEncryption | Column must be hidden from some readers | DataMasker | Delete a subject's data on request | RightToErasure | Export a subject's data on request | DataPortabilityExporter | Prove who changed a row | AuditLogger / ComplianceAuditTrail | Drop data past its retention window | DataRetentionPolicy + your scheduler | Enforce tenant isolation | database row-level security first; RowLevelSecurity second | Track lawful basis for processing | ConsentRecord | Where the database itself offers the control — RLS policies, GRANTs, TLS — prefer it. A database-enforced rule holds for every client; an application-enforced rule holds only for code that goes through this ORM. Related reading compliance — per-file status and known issues Audit logging Schema objects User management — database-level privileges Decorators