LocalMode
Core

Security

Encryption, PII redaction, and security best practices.

LocalMode provides built-in security utilities for encryption, key management, and PII redaction.

Zero Telemetry

LocalMode has zero telemetry. No data ever leaves your device. All processing happens locally in the browser.

Encryption

Encrypt sensitive data using Web Crypto API:

import { encrypt, decrypt, deriveKey } from '@localmode/core';

// Derive a key from a password
const key = await deriveKey('user-password', 'unique-salt');

// Encrypt data
const { ciphertext, iv } = await encrypt(key, 'sensitive data');

// Decrypt data
const decrypted = await decrypt(key, ciphertext, iv);
console.log(decrypted); // 'sensitive data'

Key Derivation

Use PBKDF2 to derive keys from passwords:

import { deriveKey } from '@localmode/core';

const key = await deriveKey(password, salt, {
  iterations: 100000, // Higher = more secure, slower
  keyLength: 256, // AES-256
});

Always use at least 100,000 iterations for PBKDF2. Lower values make brute-force attacks easier.

Encryption Options

const { ciphertext, iv } = await encrypt(key, data, {
  algorithm: 'AES-GCM', // Default, recommended
});

AES-GCM provides authenticated encryption—it protects both confidentiality and integrity. Use AES-CBC only for compatibility with legacy systems.

Key Management

Store keys securely:

import { KeyStore } from '@localmode/core';

const keyStore = new KeyStore({
  name: 'my-app-keys',
});

// Store a key
await keyStore.set('encryption-key', key);

// Retrieve a key
const storedKey = await keyStore.get('encryption-key');

// Delete a key
await keyStore.delete('encryption-key');

Key Storage

Keys stored in IndexedDB are accessible to JavaScript. For sensitive applications, consider using hardware-backed keys via WebAuthn.

Encrypting Embeddings

Encrypt embeddings before storage:

import { wrapEmbeddingModel, encryptionMiddleware, deriveKey } from '@localmode/core';

const key = await deriveKey('user-password', 'salt');

const model = wrapEmbeddingModel(baseModel, [encryptionMiddleware({ key })]);

// Embeddings are automatically encrypted
const { embedding } = await embed({ model, value: 'sensitive text' });

PII Redaction

Remove personally identifiable information before processing:

import { redactPII } from '@localmode/core';

const text = 'Contact John at john@example.com or call 555-123-4567';

const redacted = redactPII(text, {
  patterns: ['email', 'phone'],
  replacement: '[REDACTED]',
});

console.log(redacted);
// 'Contact John at [REDACTED] or call [REDACTED]'

Available Patterns

PatternDescriptionExample
emailEmail addressesjohn@example.com
phonePhone numbers555-123-4567
ssnSocial Security numbers123-45-6789
creditCardCredit card numbers4111-1111-1111-1111
ipIP addresses192.168.1.1
addressStreet addresses123 Main St

Custom Patterns

const redacted = redactPII(text, {
  patterns: ['email', 'phone'],
  custom: [
    {
      name: 'employeeId',
      regex: /EMP-\d{6}/g,
    },
  ],
  replacement: (match, pattern) => `[${pattern.toUpperCase()}]`,
});

PII Middleware

Automatically redact PII before embedding:

import { wrapEmbeddingModel, piiRedactionMiddleware } from '@localmode/core';

const model = wrapEmbeddingModel(baseModel, [
  piiRedactionMiddleware({
    patterns: ['email', 'phone', 'ssn'],
    replacement: '[REDACTED]',
  }),
]);

// PII is automatically redacted before embedding
const { embedding } = await embed({
  model,
  value: 'Email me at john@example.com',
});
// Actually embeds: 'Email me at [REDACTED]'

Feature Detection

Check security feature availability:

import { isCryptoSupported, isCrossOriginIsolated } from '@localmode/core';

if (!isCryptoSupported()) {
  console.warn('Web Crypto API not available');
}

if (!isCrossOriginIsolated()) {
  console.warn('SharedArrayBuffer not available');
}

Security Best Practices

Security Checklist

  1. Never store passwords - Use key derivation
  2. Unique salts - Generate random salts for each key
  3. High iterations - Use at least 100,000 PBKDF2 iterations
  4. Redact PII - Always redact before processing user data
  5. Zero telemetry - LocalMode never phones home

Secure RAG Pipeline

import {
  wrapEmbeddingModel,
  piiRedactionMiddleware,
  encryptionMiddleware,
  deriveKey,
} from '@localmode/core';

// Setup secure model
const key = await deriveKey(userPassword, uniqueSalt);

const secureModel = wrapEmbeddingModel(baseModel, [
  piiRedactionMiddleware({
    patterns: ['email', 'phone', 'ssn', 'creditCard'],
  }),
  encryptionMiddleware({ key }),
]);

// All embeddings are PII-redacted and encrypted
const { embedding } = await embed({
  model: secureModel,
  value: userInput,
});

Content Security Policy

For maximum security, configure CSP headers:

// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: [
      "default-src 'self'",
      "script-src 'self' 'wasm-unsafe-eval'", // Required for WASM
      "worker-src 'self' blob:", // Required for workers
      "connect-src 'self' https://huggingface.co https://cdn-lfs.huggingface.co",
    ].join('; '),
  },
];

Cross-Origin Isolation

Some features require cross-origin isolation:

// Check if isolated
if (crossOriginIsolated) {
  // SharedArrayBuffer available
  // Better performance for workers
}

// Enable via headers:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp

Audit Logging

Log security-relevant events:

import { wrapEmbeddingModel, loggingMiddleware } from '@localmode/core';

const model = wrapEmbeddingModel(baseModel, [
  loggingMiddleware({
    logger: (event) => {
      // Log to secure audit trail
      auditLog.log({
        timestamp: new Date().toISOString(),
        action: 'embedding',
        model: event.modelId,
        inputCount: event.inputCount,
        // Don't log actual input values!
      });
    },
  }),
]);

Next Steps

On this page