Security Model
Three core principles:- Secrets never enter WASM memory - Tools can check existence, not read values
- Encryption at rest - AES-256-GCM with per-secret key derivation
- Leak detection - All outbound requests and responses are scanned
Architecture
Encryption at Rest
Cryptographic Primitives
- Algorithm: AES-256-GCM (authenticated encryption)
- Key derivation: HKDF-SHA256 (per-secret keys from master key)
- Salt size: 32 bytes (random per secret)
- Nonce size: 12 bytes (random per encryption)
- Tag size: 16 bytes (authentication tag)
Key Derivation
Each secret gets its own encryption key derived from the master key:- Two secrets with the same plaintext have different ciphertexts
- Compromising one secret doesn’t compromise others
- Master key rotation requires re-encrypting all secrets
Encryption Process
From src/secrets/crypto.rs:66-93:encrypted_value: nonce (12 bytes) + ciphertext + tag (16 bytes)key_salt: 32-byte salt for key derivation
Decryption Process
From src/secrets/crypto.rs:99-126:Master Key Storage
Option 1: OS Keychain (Recommended)
Auto-generated during onboarding:Option 2: Environment Variable
For CI/Docker deployments:- Minimum length: 32 bytes
- Entropy: High-quality randomness (use
openssl rand, not keyboard mashing) - Storage: Secure vault (e.g., AWS Secrets Manager, HashiCorp Vault)
Credential Injection
WASM tools never receive plaintext secrets. Instead, the host injects credentials at the HTTP boundary.WASM Perspective
Tools can only:- Check existence:
- Trigger injection (implicitly via HTTP capability):
- ❌ Read secret values
- ❌ List available secrets
- ❌ Access secrets not in their
allowed_names
Host Injection Process
From src/tools/wasm/credential_injector.rs:Injection Locations
From src/secrets/types.rs:198-214:Authorization Bearer
Authorization Basic
Custom Header
Query Parameter
Example: OpenAI API
Capabilities file:- WASM calls:
http_request("https://api.openai.com/v1/chat/completions", ...) - Host checks: ✓ Allowlist allows this endpoint
- Host finds: Credential mapping for
api.openai.com - Host decrypts:
openai_api_keysecret - Host injects:
Authorization: Bearer sk-proj-... - Host executes: HTTP POST with injected header
- WASM receives: Response (after leak scanning)
Leak Detection
All data crossing the WASM boundary is scanned for secrets.Scan Points
-
Outbound HTTP requests (before execution)
- URL
- Headers
- Request body
-
Inbound HTTP responses (before returning to WASM)
- Response body
- Response headers (optional)
-
Tool outputs (before showing to user)
- All tool result text
-
User input (before sending to LLM)
- Detect accidentally pasted secrets
Detection Patterns
From src/safety/leak_detector.rs:414-531:Scan Algorithm
Two-phase matching for performance: Phase 1: Aho-Corasick prefix matchingLeak Actions
From src/safety/leak_detector.rs:46-65:Secret Masking
Secrets in logs/errors are partially masked:sk-proj-abc123def456ghi789→sk-p********i789AKIAIOSFODNN7EXAMPLE→AKIA********MPLEshort→*****
HTTP Request Scanning
From src/safety/leak_detector.rs:294-326:Database Schema
Secrets table (PostgreSQL):encrypted_valueandkey_saltstored as binary blobsnameis case-insensitive (normalized to lowercase)usage_countincremented on each injection (audit trail)- No plaintext values stored anywhere
Secret Lifecycle
1. Creation
- User provides plaintext secret
- Generate random 32-byte salt
- Derive encryption key via HKDF(master_key, salt)
- Generate random 12-byte nonce
- Encrypt with AES-256-GCM
- Store
encrypted_valueandkey_saltin database - Zero plaintext memory
2. Existence Check (WASM)
3. Injection (Host Only)
4. Rotation
5. Deletion
Security Audit
Threat: WASM Tool Tries to Read Secrets
Attack: WASM callsget_secret("openai_api_key")
Defense: Function not exposed. Only secret_exists() available.
Threat: WASM Tool Exfiltrates Secret via HTTP
Attack: WASM includes secret in URL/bodyThreat: Secret Appears in Tool Output
Attack: WASM echoes secret in response- WASM can’t access secret (no plaintext in memory)
- If somehow leaked, output sanitizer redacts it
Threat: Binary Body Exfiltration
Attack: Prepend invalid UTF-8 byte to evade string scanningThreat: Database Dump
Attack: Attacker gains read access to PostgreSQL Defense: All secrets are encrypted. Without master key, ciphertext is useless.Threat: Master Key Compromise
Attack: Attacker stealsSECRETS_MASTER_KEY env var
Defense:
- Use OS keychain (harder to extract)
- Rotate master key + re-encrypt all secrets
- Audit logs for suspicious decryption activity
Best Practices
For Developers
- Never log decrypted secrets
- Minimize plaintext lifetime
- Check leak detection results
For Users
- Use short-lived secrets when possible
- Rotate secrets regularly
- Monitor usage
- Secure master key
- OS keychain: Backed up with system backups
- Env var: Store in secure vault (AWS Secrets Manager, etc.)
Source Code References
- Encryption: src/secrets/crypto.rs:38-141
- Secret types: src/secrets/types.rs:1-285
- Leak detector: src/safety/leak_detector.rs:132-338
- Credential injection: src/tools/wasm/credential_injector.rs
- Keychain integration: src/secrets/keychain.rs
See Also
- Security Overview - Complete security architecture
- WASM Sandbox - How secrets capability works
- Network Security - Allowlisting for credential injection