rxdb-adapter-encrypted
Local field-level AES-GCM-256 envelope encryption for @aiao/rxdb.
Plugs into the SQLite-core / PGlite / wa-sqlite / sqliteai adapters with
zero-plaintext-at-rest guarantees on the structural database files,
change log, query cache and history snapshots.
Install
pnpm add @aiao/rxdb-adapter-encrypted
Peer of any local SQLite-family adapter
(@aiao/rxdb-adapter-wa-sqlite, @aiao/rxdb-adapter-pglite,
@aiao/rxdb-adapter-sqlite-wasm, @aiao/rxdb-adapter-sqliteai).
Not needed for Supabase or remote-only adapters.
Quickstart
import { Entity, Property, PropertyType } from '@aiao/rxdb';
import { WaSqliteAdapter } from '@aiao/rxdb-adapter-wa-sqlite';
import { RxDB } from '@aiao/rxdb';
@Entity({ tableName: 'users' })
class User {
@Property({ primaryKey: true }) id!: string;
@Property({ propertyType: PropertyType.STRING }) displayName!: string;
@Property({ propertyType: PropertyType.STRING, encrypted: true })
email!: string;
}
const adapter = await WaSqliteAdapter.create({ name: 'app.db' });
const db = await RxDB.create({ adapter, entities: [User] });
await adapter.encryption.unlock({
passphrase: 'correct horse battery staple'
// idleTimeoutMs: 10 * 60_000 // override default 5-minute auto-lock
// idleTimeoutMs: 0 // disable auto-lock
});
await db.repository(User).create({
id: 'u1',
displayName: 'Ada',
email: 'ada@example.com' // encrypted at rest
});
Public API
import {
// Keyring lifecycle
Keyring,
createKeyring,
type UnlockOptions,
type PassphraseUnlockOptions,
type KeyBytesUnlockOptions,
type CryptoKeyUnlockOptions,
type KeyProviderUnlockOptions,
// Envelope codec
encodeEnvelope,
decodeEnvelope,
isEnvelope,
buildAAD,
ENVELOPE_VERSION,
ENVELOPE_ALG,
type CryptoEnvelope,
type EnvelopeVersion,
// Schema + query validators
validateEncryptedPropertyMetadata,
validateFTSRegistrationAgainstEncryptedColumns,
validateQueryAgainstEncryptedColumns,
type EncryptedAwareEntity,
// Typed errors
EncryptedError,
EncryptedConfigurationError,
EncryptedDecryptError,
EncryptedLockedError,
EncryptedQueryError,
EncryptedUnlockError,
type EncryptedErrorCode,
type EncryptedErrorInit,
// Keyring persistence binding
type KeyringRow,
type KeyringStorageBinding,
// Verifier sentinel constant
VERIFIER_SENTINEL
} from '@aiao/rxdb-adapter-encrypted';
import { scanForPlaintext, type ScanHit } from '@aiao/rxdb-adapter-encrypted/testing';
Refer to the TSDoc on each export for its security and lifecycle contract.
Guarantees
| Spec | Guarantee |
|---|---|
| FR-001 | Schema validation rejects encrypted PK / FK / index / unique / sortable / FTS / computed columns |
| FR-002 | Envelope text form v|alg|kid|iv|ct|tag (6 base64url segments) |
| FR-003 | AES-GCM-256, unique 96-bit IV per write, AAD joins namespace, tableName, columnName, primaryKey, and kid with byte 0x1F |
| FR-004 | unlock() accepts exactly one of passphrase / keyBytes / key / keyProvider |
| FR-005 | All encrypted columns are emitted as TEXT regardless of logical type |
| FR-006 | Zero plaintext in DB files, rxdb_change patches, query cache, history snapshots |
| FR-007 | Filter / order / group / FTS over encrypted column throws EncryptedQueryError before SQL gen |
| FR-008 | Locked-state reads throw EncryptedLockedError; idle auto-lock after 5 min (configurable, 0 disables) |
| FR-009 | unlock() verifies passphrase against persisted verifier probe; wrong passphrase never retains key |
What this package does NOT do (MVP)
- No full-database encryption — only declared columns are sealed.
- No native keychain / passkey / WebAuthn — bring your own passphrase or key bytes.
- No searchable encryption —
where/order/group/ FTS on encrypted columns is rejected. - No key rotation — single
kidper database for the MVP. - No audit log — decrypt failures throw, not persisted.
- No automatic relock on tab visibility — subscribe to
document.visibilitychangeyourself.
License
MIT
Classes
| Class | Description |
|---|---|
| EncryptedConfigurationError | Schema / initialisation / lifecycle misconfiguration. |
| EncryptedDecryptError | Envelope-level failure on read. |
| EncryptedError | Abstract base of every error thrown by @aiao/rxdb-adapter-encrypted. |
| EncryptedLockedError | Raised when an encrypt / decrypt path is reached while Keyring.isLocked. |
| EncryptedQueryError | Query references an encrypted column. |
| EncryptedUnlockError | unlock() failed key validation or provider dispatch. |
| Keyring | - |
Interfaces
| Interface | Description |
|---|---|
| CryptoEnvelope | - |
| CryptoKeyUnlockOptions | - |
| DecryptArgs | - |
| EncryptArgs | - |
| EncryptedAwareEntity | - |
| EncryptedErrorInit | - |
| KeyBytesUnlockOptions | - |
| KeyProviderUnlockOptions | - |
| KeyringRow | The single row persisted in the rxdb_db_keyring table. |
| KeyringStorageBinding | Implemented by each adapter (sqlite-core, pglite, …) to read / write the keyring singleton row. |
| - | |
| PassphraseUnlockOptions | - |
| PatchWalkArgs | - |
Type Aliases
| Type Alias | Description |
|---|---|
| EncryptedEntityResolver | - |
| EncryptedErrorCode | Union of every code literal across the concrete subclasses. |
| EnvelopeVersion | - |
| UnlockOptions | - |
Variables
| Variable | Description |
|---|---|
| ENVELOPE_ALG | Frozen current algorithm tag. |
| ENVELOPE_VERSION | Frozen current envelope version. |
| VERIFIER_SENTINEL | Fixed plaintext sentinel encrypted in the keyring verifier probe. |
Functions
| Function | Description |
|---|---|
| buildAAD | - |
| createKeyring | - |
| decodeEnvelope | - |
| deserializeFromEnvelope | - |
| encodeEnvelope | - |
| envelopePlaintextPatches | Walk top-level keys of a plaintext patch; for each key present in entity.encryptedPropertyMap, replace the value with an envelope string produced by keyring.encrypt. Non-encrypted keys are copied through. |
| isEnvelope | - |
| serializeForEnvelope | - |
| unenvelopePlaintextPatches | Inverse of envelopePlaintextPatches: decrypt top-level envelope strings back to plaintext. Used when applying undo/redo inversePatch. |
| validateEncryptedPropertyMetadata | - |
| validateEncryptedQuery | - |
| validateFTSRegistrationAgainstEncryptedColumns | - |
| validateQueryAgainstEncryptedColumns | - |