Security Model

Threat Model

Clauth assumes that skills and agents are untrusted code running alongside a human operator's credentials. The threat scenarios:

  1. Malicious skill installation: A compromised or intentionally malicious skill attempts to exfiltrate credentials
  2. Credential theft via environment: A skill reads environment variables, files, or process memory to extract tokens
  3. Scope escalation: A skill with limited access attempts to access unauthorized providers or actions
  4. Behavioral manipulation: A skill gradually changes its request patterns to mask data exfiltration
  5. Supply chain compromise: A provider's OAuth tokens are compromised via upstream vulnerability

Defense Layers

Layer 1: Credential Isolation

Problem: OpenClaw stores credentials in plaintext JSON (~/.openclaw/openclaw.json). Any skill can read every credential. Solution: Clauth encrypts all credentials at rest using AES-256-GCM. The vault is unlocked once at daemon start with a passphrase-derived key (Argon2id, 64 MiB memory, 3 iterations). Skills never see raw credentials — they reference handles like github-main, and the daemon injects auth headers at request-time. Why AES-256-GCM: Authenticated encryption prevents both reading and tampering. GCM mode provides integrity via authentication tags. Each vault write uses a fresh IV. Why Argon2id: Memory-hard KDF that resists GPU/ASIC attacks. The 64 MiB memory parameter makes brute-force expensive. Argon2id combines data-dependent (side-channel resistant) and data-independent (timing-attack resistant) memory access patterns.

Layer 2: Scope Enforcement

Problem: No least-privilege boundaries — any skill can use any credential for any purpose. Solution: Granular provider:action grants per skill. Each grant has:

Wildcards (github:, :read) allow flexibility while maintaining boundaries. Scope authorization happens before any credential is resolved.

Layer 3: Behavioral Firewall

Problem: Even with scope enforcement, a compromised skill could abuse its granted access (e.g., mass data exfiltration within allowed scope). Solution: Silent baseline learning with anomaly detection:

CheckSeverityAction

Burst threshold (>20 in 10s)CriticalBlock request
Rate spike (>3x baseline)WarningAlert
New endpoint after warmupWarningAlert
Off-hours activity (1-5 AM)WarningAlert
Scope creep (unauthorized scope)CriticalBlock + alert

The firewall learns per-skill baselines automatically. After a warmup period (10 requests), new patterns trigger alerts. Critical anomalies block the request immediately.

Layer 4: Endpoint Policy

Problem: A skill with valid scope could redirect requests to an attacker-controlled server to capture injected credentials. Solution: Provider endpoint allowlists. Known providers (GitHub, Slack, Twitter, etc.) have hardcoded allowed hostnames. Custom providers require explicit allowedHosts metadata on the credential. Every request's target URL is validated against the allowlist before credential injection.

Layer 5: OAuth Auto-Refresh

Problem: Expired tokens cause silent failures. Manual token rotation creates windows of vulnerability. Solution: Clauth manages refresh_token lifecycle automatically. On proxy 401 response:
  1. Refresh the access token via the provider's token endpoint
  2. Update the vault atomically
  3. Retry the original request with the new token
  4. If still failing, return the original 401

This ensures tokens are always fresh without operator intervention.

Layer 6: Advisory-Driven Revocation

Problem: When a provider is compromised (e.g., GitHub token leak), manual response is slow and error-prone. Solution: Advisory monitor polls security feeds (GitHub Advisory Database). On critical advisory matching a stored provider:
  1. Auto-revokes all scope grants for the affected provider
  2. Deletes affected credentials from the vault
  3. Dispatches critical alert to configured webhooks
  4. Logs the event in the audit chain

Layer 7: Tamper-Evident Audit

Problem: An attacker who gains access could cover their tracks by editing logs. Solution: Hash-chained NDJSON audit log. Each entry includes:

Any modification to any entry breaks the chain. Integrity is verifiable at any time via audit.verifyIntegrity().

Layer 8: Identity Verification

Problem: Proving identity ownership typically requires public posting (e.g., tweeting a verification code), which is both privacy-invasive and insecure. Solution: Three private verification methods:

Encryption Details

Vault Envelope

{

"version": 1,

"cipher": "aes-256-gcm",

"iv": "<base64url>",

"tag": "<base64url>",

"ciphertext": "<base64url>",

"kdf": {

"algorithm": "argon2id",

"params": {

"memory": 65536,

"parallelism": 1,

"iterations": 3,

"tagLength": 32

},

"salt": "<base64url>"

}

}

Session JWTs

Identity Proof Signatures

OAuth State Parameter

File Permissions

PathModePurpose

~/.clauth/0o700State directory (owner only)
~/.clauth/*.json0o600State files (owner read/write only)
~/.clauth/vault.enc0o600Encrypted vault
~/.clauth/audit.ndjson0o600Audit log

All writes are atomic (write to temp file, rename) to prevent corruption.

Comparison with Alternatives

vs. Environment Variables

Environment variables are readable by any process in the same session. Clauth isolates credentials in an encrypted vault accessible only through the daemon.

vs. HashiCorp Vault

HashiCorp Vault is a production-grade secrets management system, but it's a heavy dependency with its own infrastructure requirements. Clauth is zero-dependency, local-first, and purpose-built for the agent/skill trust model.

vs. OS Keychain

OS keychains (macOS Keychain, GNOME Keyring) store secrets but don't provide scope enforcement, behavioral monitoring, or brokered execution. Clauth adds the proxy and firewall layers on top of at-rest encryption.

vs. OAuth Token Files

Storing OAuth tokens in files requires each skill to handle refresh logic, expiry, and rotation. Clauth centralizes this in the daemon with automatic refresh on 401 responses.

Hardening Configuration

{

"hardening": {

"enforceHttps": true,

"maxRequestBodyBytes": 1048576,

"sessionTtlSeconds": 3600,

"challengeTtlSeconds": 600,

"requireAdminTokenForIdentity": false

}

}