Encryption

Audience: Customer β€” this page documents how PII fields are encrypted locally.

Rulecatch uses zero-knowledge encryption for all PII (personally identifiable information). Data is encrypted on the developer's machine before being sent to the API. Rulecatch servers never see plaintext PII.


Overview

Property Value
Algorithm AES-256-GCM
Key derivation PBKDF2 with 100,000 iterations
Key length 256 bits (32 bytes)
IV length 128 bits (16 bytes, random per field)
Auth tag 128 bits (GCM authentication tag)
Hash algorithm SHA-256 (truncated to 16 hex chars)
Salt 256 bits (random, generated during init)

What Gets Encrypted

Encrypted Fields (PII)

These fields contain personally identifiable information and are encrypted:

Field Description Stored As
gitEmail Developer's git email gitEmail_encrypted + gitEmail_hash
gitUsername Developer's git username gitUsername_encrypted + gitUsername_hash
filePath File paths filePath_encrypted + filePath_hash
accountEmail Account email accountEmail_encrypted + accountEmail_hash
cwd Working directory cwd_encrypted + cwd_hash
projectId Project identifier projectId_encrypted + projectId_hash
filesModified Array of file paths filesModified_encrypted + filesModified_hashes

Hashed Fields

For each encrypted field, a truncated SHA-256 hash is also stored:

SHA-256(plaintext + salt) β†’ first 16 hex characters

Hashes allow:

  • Grouping by the same identifier (same input = same hash)
  • Deduplication
  • Indexing and querying

Hashes cannot be reversed (one-way function), and truncation to 16 chars prevents rainbow table attacks.

Plaintext Fields (Not Encrypted)

These fields do not contain PII and are stored in plaintext:

  • Token counts (inputTokens, outputTokens, etc.)
  • Cost estimates
  • Timestamps
  • Tool names and success/failure
  • Severity levels
  • Line counts
  • Language identifiers

Encryption Process

On the Client (Flush Script)

1. Load encryption key and salt from config
2. Derive AES key: PBKDF2(password, salt, 100000, 32, 'sha256')
3. For each PII field in each event:
   a. Generate random IV (16 bytes)
   b. Encrypt: AES-256-GCM(key, iv, plaintext)
   c. Store: field_encrypted (ciphertext), field_iv, field_tag
   d. Hash: SHA-256(plaintext + salt)[0:16]
   e. Delete: original plaintext field
4. Send encrypted payload to API

On the Server (API)

The API stores encrypted fields as-is. It cannot decrypt them. The server sees:

{
  "gitEmail_encrypted": "base64-ciphertext",
  "gitEmail_iv": "base64-iv",
  "gitEmail_tag": "base64-auth-tag",
  "gitEmail_hash": "a1b2c3d4e5f6g7h8",
  "inputTokens": 50000,
  "toolName": "Write"
}

On the Dashboard (Browser)

When the user enters their encryption key in the dashboard:

1. User enters encryption key in "Decrypt Data" modal
2. Browser derives AES key: PBKDF2(key, salt, 100000, 32, 'sha256')
3. Browser decrypts each field:
   a. Read field_encrypted, field_iv, field_tag
   b. Decrypt: AES-256-GCM(key, iv, ciphertext)
   c. Display plaintext in UI
4. Key is stored in browser session (Pro) or localStorage (Enterprise)

Key Management

Automatic Key Generation

If you don't provide an encryption key during init, a cryptographically secure key is automatically generated and saved to your config. This ensures PII is always encrypted β€” even if you skip the --encryption-key flag.

Setting a Custom Key

During init:

npx @rulecatch/ai-pooler init --encryption-key=my-secret-passphrase

Viewing Your Key

npx @rulecatch/ai-pooler config --show-key

Key Storage by Plan

Plan Browser Storage Persistence
Starter Not available N/A
Pro Session storage Lost on tab close
Enterprise Local storage Persists across sessions

Lost Keys

If you lose your encryption key, Rulecatch cannot recover it. Encrypted data will remain unreadable. You can:

  1. Set a new encryption key going forward
  2. Old data remains encrypted with the old key
  3. New data will use the new key

Salt

The salt is generated during init and stored in the config file:

{
  "salt": "base64-encoded-32-byte-random-salt"
}

The salt is also sent to the API with each ingest batch (in encryptionSalt) so the server can store it for the dashboard's decryption UI.

The salt is not secret β€” it's a public parameter that prevents rainbow table attacks on the hashes.


Security Properties

Property Guarantee
Confidentiality AES-256-GCM provides authenticated encryption
Integrity GCM auth tag detects tampering
Forward secrecy Random IV per field per encryption
Brute-force resistance PBKDF2 with 100K iterations
Hash collision resistance SHA-256 with salt
Zero-knowledge Server never sees plaintext PII

Verifying Encryption

To verify a key is correct, the system can encrypt a known test value and compare:

const testCiphertext = encryptPII('test-value', key);
const isCorrect = verifyPrivacyKey(testCiphertext, 'test-value', key);

See Also