Nucleus provides defense-in-depth security: row-level security (RLS), tenant-aware encryption, and immutable audit logging.

Row-Level Security (RLS)

Control which rows each user can see and modify.

Enable RLS

ENABLE RLS ON patients;

Create Policies

-- Doctors can see all patients
CREATE POLICY doctor_read ON patients
    FOR SELECT
    TO doctor
    USING (true);

-- Patients can only see their own records
CREATE POLICY patient_read ON patients
    FOR SELECT
    TO patient
    USING (patient_id = current_user_id());

-- Nurses can only see patients in their department
CREATE POLICY nurse_read ON patients
    FOR SELECT
    TO nurse
    USING (department = current_tenant());

Policy Types

| Type | Behavior | |------|----------| | Permissive (default) | At least one permissive policy must pass | | Restrictive | All restrictive policies must pass |

Policies combine: (any permissive passes) AND (all restrictive pass).

Predicate Types

| Predicate | Description | |-----------|-------------| | column = 'value' | Column equals constant | | column = current_user() | Column matches session user | | column = current_tenant() | Column matches session tenant | | has_role('admin') | Session must have role | | Combined with AND / OR | Compose predicates |

Superuser Bypass

Users with the superuser role bypass all RLS policies.

Column-Level Data Masking

Mask a column's value for a role, without changing what is stored:

CREATE MASKING POLICY ON people (ssn)   TO analyst USING REDACT '***';
CREATE MASKING POLICY ON people (email) TO analyst USING EMAIL;
CREATE MASKING POLICY ON cards  (pan)   TO support USING PARTIAL (0, 4, '*');
CREATE MASKING POLICY ON people (name)  TO analyst USING HASH;

SHOW MASKING POLICIES;
DROP MASKING POLICY ON people (ssn) TO analyst;

| Rule | Effect | |------|--------| | REDACT '<text>' | Replace the value with a constant | | EMAIL | ada@example.com → a**@example.com | | PARTIAL (first, last [, 'char']) | Keep the first and last N characters, mask the middle | | HASH | Replace with a stable digest, for pseudonymisation | | NONE | Pass through |

A policy is identified by (table, column, role); creating one for the same triple replaces it. Masking DDL requires superuser authority, participates in transactions with the rest of the security catalog, and survives restart.

Masks apply to a role the session has, including through role membership, and they apply on every path a row leaves by — not just plain SELECT. Superusers are not masked.

Two limits worth knowing. Masking is not a substitute for RLS: it changes the value returned, so a masked column can still be used in a WHERE clause to narrow rows. And there is no column-level GRANT — to remove a column from a role's reach entirely, expose a view that omits it.

Encryption

Tenant-Aware Key Management

Nucleus supports per-tenant encryption keys with rotation:

-- Register a tenant key
SELECT register_tenant_key('tenant_42', decode('...base64key...', 'base64'));

-- Rotate to a new key (old key archived for decryption)
SELECT rotate_key('tenant_42', decode('...newkey...', 'base64'));

Key Rotation

Key rotation is a managed process:

  1. Begin — New key registered, old key marked for archival
  2. Re-encrypt — Pages re-encrypted in background (progress tracked)
  3. Complete — Old key archived, new key active

Old keys are retained for decrypting data written before rotation.

Encrypted Indexes

Encrypted index modes are unavailable. CREATE INDEX ... USING ENCRYPTED, ENCRYPTED_OPE, ENCRYPTED_RANDOM, and ENCRYPTED_LOOKUP refuse with SQLSTATE 0A000. The retired prototype did not provide secure cryptography. Recovery keeps historical catalog definitions and base rows but does not rebuild those sidecars. Base rows remain plaintext; this boundary is separate from page-level encryption at rest.

Audit Logging

Immutable, append-only audit log for compliance and forensics.

What's Logged

Every query execution records:

| Field | Description | |-------|-------------| | timestamp | When the query ran | | user | Who ran it | | action | Query type (SELECT, INSERT, UPDATE, DELETE, DDL) | | table | Affected table | | query | Full SQL text | | rows_affected | Number of rows modified | | success | Whether the query succeeded |

Querying the Audit Log

-- Recent activity by user
SELECT * FROM nucleus_audit_log WHERE user = 'alice' ORDER BY timestamp DESC LIMIT 100;

-- All changes to a table
SELECT * FROM nucleus_audit_log WHERE table_name = 'patients' AND action IN ('INSERT', 'UPDATE', 'DELETE');

Audit entries are immutable — they cannot be updated or deleted.

Session Context

Security features use session context to evaluate policies:

-- Set session properties
SET SESSION user = 'alice';
SET SESSION role = 'doctor';
SET SESSION tenant_id = 'hospital_42';

Context properties available to RLS predicates and masking rules:

| Function | Returns | |----------|---------| | current_user() | Session user | | current_tenant() | Session tenant ID | | has_role('name') | Whether session has role |

Cluster Security

Inter-Node TLS

In clustered deployments, node-to-node traffic — Raft and replication — uses mutual TLS: each node presents its certificate and requires its peer to present one signed by the cluster CA, in both directions. A node without a valid certificate cannot join, and a node will not talk to a peer whose certificate the CA did not sign.

Configure it with environment variables (there is no [cluster.tls] TOML section — this page previously showed one that does not exist):

NUCLEUS_INTERNAL_TLS=1
NUCLEUS_INTERNAL_TLS_CERT=/path/to/node.crt
NUCLEUS_INTERNAL_TLS_KEY=/path/to/node.key
NUCLEUS_INTERNAL_TLS_CA=/path/to/ca.crt
NUCLEUS_INTERNAL_TLS_SERVER_NAME=nucleus.internal   # optional, default "localhost"

NUCLEUS_CLUSTER_TOKEN is still enforced at connection setup and is a second factor rather than a replacement. Node identity within an authenticated cluster is not yet bound to the certificate: a peer that completes the handshake is trusted for the node id it claims.

All Raft messages, WAL streaming, and gossip use encrypted channels.