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:
- Set a new encryption key going forward
- Old data remains encrypted with the old key
- 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
- Installation β Setting up encryption during init
- CLI Reference β
config --show-keycommand - Settings β Dashboard decryption UI
- AI-Pooler Overview β Where encryption fits in the data flow