Skip to main content
IronClaw ensures that secrets are never exposed to WASM tools. Credentials are encrypted at rest, injected at the host boundary during HTTP requests, and all outputs are scanned for leakage.

Security Model

Three core principles:
  1. Secrets never enter WASM memory - Tools can check existence, not read values
  2. Encryption at rest - AES-256-GCM with per-secret key derivation
  3. 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:
From src/secrets/crypto.rs:129-140:
This means:
  • 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:
Stored in database:
  • 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:
Decryption automatically verifies the authentication tag. Tampered ciphertext is rejected.

Master Key Storage

Auto-generated during onboarding:
Implementation in src/secrets/keychain.rs.

Option 2: Environment Variable

For CI/Docker deployments:
Key requirements:
  • 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:
  1. Check existence:
  1. Trigger injection (implicitly via HTTP capability):
Tools cannot:
  • ❌ 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

Injects as:

Authorization Basic

Injects as:

Custom Header

Injects as:

Query Parameter

Injects as:

Example: OpenAI API

Capabilities file:
Flow:
  1. WASM calls: http_request("https://api.openai.com/v1/chat/completions", ...)
  2. Host checks: ✓ Allowlist allows this endpoint
  3. Host finds: Credential mapping for api.openai.com
  4. Host decrypts: openai_api_key secret
  5. Host injects: Authorization: Bearer sk-proj-...
  6. Host executes: HTTP POST with injected header
  7. WASM receives: Response (after leak scanning)

Leak Detection

All data crossing the WASM boundary is scanned for secrets.

Scan Points

  1. Outbound HTTP requests (before execution)
    • URL
    • Headers
    • Request body
  2. Inbound HTTP responses (before returning to WASM)
    • Response body
    • Response headers (optional)
  3. Tool outputs (before showing to user)
    • All tool result text
  4. 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 matching
Phase 2: Full regex validation
This hybrid approach is orders of magnitude faster than checking all regex patterns.

Leak Actions

From src/safety/leak_detector.rs:46-65:
Block: Critical secrets (API keys, private keys)
Redact: Less critical patterns (bearer tokens)
Warn: Low-confidence matches (high-entropy hex)

Secret Masking

Secrets in logs/errors are partially masked:
Examples:
  • sk-proj-abc123def456ghi789 → sk-p********i789
  • AKIAIOSFODNN7EXAMPLE → AKIA********MPLE
  • short → *****

HTTP Request Scanning

From src/safety/leak_detector.rs:294-326:
This catches exfiltration attempts like:

Database Schema

Secrets table (PostgreSQL):
Key points:
  • encrypted_value and key_salt stored as binary blobs
  • name is case-insensitive (normalized to lowercase)
  • usage_count incremented on each injection (audit trail)
  • No plaintext values stored anywhere

Secret Lifecycle

1. Creation

Flow:
  1. User provides plaintext secret
  2. Generate random 32-byte salt
  3. Derive encryption key via HKDF(master_key, salt)
  4. Generate random 12-byte nonce
  5. Encrypt with AES-256-GCM
  6. Store encrypted_value and key_salt in database
  7. Zero plaintext memory

2. Existence Check (WASM)

3. Injection (Host Only)

4. Rotation

Old secret is overwritten, new salt/nonce generated.

5. Deletion

Database row deleted. Encrypted value is lost (irreversible).

Security Audit

Threat: WASM Tool Tries to Read Secrets

Attack: WASM calls get_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/body
Defense: Leak detector blocks request before execution.

Threat: Secret Appears in Tool Output

Attack: WASM echoes secret in response
Defense:
  1. WASM can’t access secret (no plaintext in memory)
  2. If somehow leaked, output sanitizer redacts it

Threat: Binary Body Exfiltration

Attack: Prepend invalid UTF-8 byte to evade string scanning
Defense: Leak detector uses lossy UTF-8 conversion

Threat: 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 steals SECRETS_MASTER_KEY env var Defense:
  1. Use OS keychain (harder to extract)
  2. Rotate master key + re-encrypt all secrets
  3. Audit logs for suspicious decryption activity

Best Practices

For Developers

  1. Never log decrypted secrets
  1. Minimize plaintext lifetime
  1. Check leak detection results

For Users

  1. Use short-lived secrets when possible
  1. Rotate secrets regularly
  1. Monitor usage
  1. Secure master key
  • OS keychain: Backed up with system backups
  • Env var: Store in secure vault (AWS Secrets Manager, etc.)

Source Code References

See Also