# OmniRoute Provider Credentials Encryption at Rest: AES-256-GCM Security Implementation

> Discover how OmniRoute secures provider credentials at rest with robust AES-256-GCM encryption. Learn about its advanced security implementation and data protection.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: security-implementation
- Published: 2026-08-04

---

**OmniRoute encrypts all provider credentials using AES-256-GCM with a master key derived from `STORAGE_ENCRYPTION_KEY`, storing ciphertext in SQLite with automatic legacy migration and field-level protection.**

This guide examines how [diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute) implements encryption of sensitive provider data at rest. The security architecture centers on a dedicated encryption module with deterministic key derivation, versioned payloads, and transparent migration from older schemes.

## Encryption Architecture and Key Management

All credential encryption flows through **[`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts)**, a focused module designed for auditability and minimal attack surface. The system relies on environment variable configuration with graceful degradation.

### Required Environment Variables

| Variable | Purpose | Behavior if Missing |
|----------|---------|---------------------|
| `STORAGE_ENCRYPTION_KEY` | Primary AES-256-GCM master key source | Encryption disabled; plaintext storage |
| `STORAGE_ENCRYPTION_KEY_VERSION` | Algorithm version label (e.g., `v1`) | Defaults to `v1` |
| `OMNIROUTE_CRYPT_KEY` / `OMNIROUTE_API_KEY_BASE64` | Legacy aliases for backward compatibility | Checked if primary variable unset |

Loss of the configured key renders encrypted columns **permanently unrecoverable**—the design explicitly favors security over recoverability.

### Key Derivation with scrypt

The master key is derived from the raw environment variable using **scrypt** with a **static salt** of `"omniroute-field-encryption-v1"`:

```typescript
// src/lib/db/encryption.ts#L41
// Deterministic derivation ensures consistent keys for deduplication
const key = scryptSync(
  Buffer.from(masterKey, 'utf-8'),
  Buffer.from('omniroute-field-encryption-v1'),
  32
);

```

The static salt choice is intentional: identical secrets produce identical encryption keys, simplifying data migration and field deduplication across the database.

## Encryption Flow: From Plaintext to Ciphertext

The **`encrypt()`** function implements authenticated encryption with associated data (AEAD) via AES-256-GCM.

### Serialization Format

Encrypted values follow a structured, versioned string format:

```

enc:v1:<iv-hex>:<cipher-hex>:<authTag-hex>

```

- **`enc:v1`** — Algorithm identifier for future-proof upgrades
- **IV (12 bytes random)** — Ensures unique ciphertexts for identical plaintexts
- **Ciphertext** — AES-256-GCM encrypted payload
- **Auth tag (16 bytes)** — Integrity verification preventing tampering

Implementation at lines 135-141 of [`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts) handles the construction:

```typescript
// Actual usage pattern from the codebase
import { encrypt, decrypt } from '@/lib/db/encryption';

const secret = 'sk-my-super-private-api-key';
const encrypted = encrypt(secret);     // → "enc:v1:a3f2...:9e8b...:c4d1..."
const recovered = decrypt(encrypted);  // → "sk-my-super-private-api-key"

```

## Decryption and Fail-Safe Handling

The **`decrypt()`** function (lines 181-192) enforces defensive failure modes:

- **Missing key** — Returns `null`, logs: `"Found encrypted data but STORAGE_ENCRYPTION_KEY is not set"`
- **Malformed payload** — Returns `null` after parsing failure
- **Auth tag mismatch** — Cryptographic failure returns `null`

This design **never exposes ciphertext or throws exceptions** that could leak implementation details. Callers must explicitly handle `null` returns, preventing accidental plaintext processing of encrypted material.

```typescript
// src/lib/db/encryption.ts#L181-L192
// Decrypt returns null on any failure path
const decrypted = decrypt(storedValue);
if (decrypted === null) {
  // Handle missing key or corrupted data appropriately
}

```

## Automatic Legacy Migration

OmniRoute's encryption module transparently upgrades data from earlier releases that used **XOR-based obfuscation**:

1. **Detection** — `decrypt()` recognizes legacy values by absence of `enc:v1` prefix
2. **Decryption** — XOR scheme decoded using legacy key
3. **Re-encryption** — Immediate AES-256-GCM encryption with current static-salt key
4. **Persistence** — Updated ciphertext stored via `encryptConnectionFields()`

This migration triggers at lines 224-231 and runs automatically during normal database operations without downtime.

### Database-Wide Migration at Startup

For comprehensive coverage, **[`src/lib/db/core.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/core.ts)** (lines 875-896) implements a startup migration loop:

```typescript
// src/lib/db/core.ts#L875-L896
// Scans provider_connections table for legacy fields
// Creates backup before any mutation
// Re-encrypts to canonical format

```

This guarantees **single canonical encryption format** across the entire schema, eliminating security downgrade risks from partially migrated deployments.

## Field-Level Integration in Database Layer

High-level helpers ensure **all credential columns** are protected consistently. The **`encryptConnectionFields()`** function (referenced at `src/lib/db/providers.ts#L108-L112` and `L437-L445`) processes:

- `apiKey`
- `accessToken`
- `refreshToken`
- `idToken`

### Practical Usage Examples

**Storing a new provider connection:**

```typescript
import { encryptConnectionFields } from '@/lib/db/encryption';
import { insertProviderConnection } from '@/lib/db/providers';

async function addConnection(raw: {
  providerId: string;
  apiKey: string;
  refreshToken?: string;
}) {
  // All credential fields automatically encrypted
  const encrypted = encryptConnectionFields({
    apiKey: raw.apiKey,
    refreshRefreshToken: raw.refreshToken,
  });
  
  await insertProviderConnection({
    providerId: raw.providerId,
    ...encrypted,
  });
}

```

**Retrieving and decrypting credentials:**

```typescript
import { decrypt } from '@/lib/db/encryption';
import { getProviderConnection } from '@/lib/db/providers';

async function getApiKey(connectionId: number) {
  const conn = await getProviderConnection(connectionId);
  // decrypt returns plaintext or null; never throws
  return conn.apiKey ? decrypt(conn.apiKey) : null;
}

```

## Security Guarantees and Operational Considerations

### Cryptographic Strengths

- **AES-256-GCM** — NIST-approved, provides both confidentiality and integrity
- **Random per-value IVs** — Prevents pattern analysis and replay attacks
- **Authenticated encryption** — Auth tag detects any ciphertext tampering

### Operational Security

- **Key isolation** — Master key exists only in process environment, never in source control
- **Versioned payloads** — `enc:v1` prefix enables algorithm agility for post-quantum or rotated schemes
- **Audit logging** — Operations logged without secret exposure; failure modes are explicit
- **Backup discipline** — Startup migration creates pre-mutation backups automatically

### Critical Warnings

- **Key loss = data loss**: No key escrow or recovery mechanism exists by design
- **Static salt trade-off**: Enables deduplication but means identical keys across all fields for a given master secret
- **Legacy XOR exposure**: Pre-migration backups may contain weakly protected data; secure deletion recommended

## Key Implementation Files

| File | Security Role |
|------|---------------|
| [`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts) | Core AES-256-GCM engine (`encrypt`, `decrypt`, `encryptConnectionFields`, legacy migration) |
| [`src/lib/db/providers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/providers.ts) | Integration layer enforcing field-level encryption on all credential writes |
| [`src/lib/db/core.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/core.ts) | Database bootstrap with bulk legacy migration at startup |
| [`docs/reference/ENVIRONMENT.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/reference/ENVIRONMENT.md) | Variable documentation and key generation guidance |
| [`docs/ops/DATABASE_GUIDE.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/ops/DATABASE_GUIDE.md) | Operational procedures for encrypted backups and rotation |

## Summary

- **AES-256-GCM with scrypt key derivation** protects all provider credentials at rest in OmniRoute's SQLite database
- **`STORAGE_ENCRYPTION_KEY`** drives encryption; loss renders data permanently inaccessible
- **Versioned payload format** (`enc:v1`) enables future algorithm upgrades without breaking changes
- **Automatic legacy migration** transparently upgrades XOR-obfuscated data to modern cryptography
- **Fail-safe decryption** returns `null` on any failure, preventing accidental exposure
- **Field-level integration** ensures consistent protection across `apiKey`, `accessToken`, `refreshToken`, and `idToken` columns

## Frequently Asked Questions

### What encryption algorithm does OmniRoute use for provider credentials?

OmniRoute uses **AES-256-GCM** authenticated encryption for all field-level protection of provider credentials at rest. The implementation in [`src/lib/db/encryption.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/encryption.ts) generates random 12-byte IVs, encrypts UTF-8 plaintext, and appends 16-byte authentication tags for integrity verification. The versioned `enc:v1` prefix allows future algorithm agility.

### How is the encryption key derived and stored?

The encryption key is **derived at runtime** using **scrypt** from the `STORAGE_ENCRYPTION_KEY` environment variable with a static salt (`"omniroute-field-encryption-v1"`). The key **never persists to disk**—it exists only in process memory during operations. This design requires operators to manage the environment variable separately from the codebase, typically via secrets management systems.

### What happens if the encryption key is lost or corrupted?

**Encrypted data becomes permanently unrecoverable.** OmniRoute implements no key escrow, backup key mechanisms, or recovery procedures by explicit security design. The `decrypt()` function returns `null` when keys are missing, and documentation in [`docs/ops/DATABASE_GUIDE.md`](https://github.com/diegosouzapw/OmniRoute/blob/main/docs/ops/DATABASE_GUIDE.md) warns operators to implement independent backup strategies for the master key itself.

### How does OmniRoute handle credentials encrypted with older versions?

Automatic transparent migration occurs through two mechanisms: (1) the `decrypt()` function detects legacy XOR-obfuscated values and immediately re-encrypts them with AES-256-GCM after successful decryption, and (2) startup routines in [`src/lib/db/core.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/db/core.ts) scan the entire `provider_connections` table to migrate any remaining legacy fields before normal operation begins.