← Back to PRs

#7953: feat(security): encrypt credentials at rest with AES-256-GCM

by TGambit65 open 2026-02-03 11:17 View on GitHub →
commands agents stale
## Summary Add **two-layer credential security** for OpenClaw: encryption at rest (AES-256-GCM) + OS keychain integration for the master key. Together, these layers protect credentials across multiple threat models. ## The Problem OpenClaw stores API keys and OAuth tokens in plaintext JSON files. This creates risk from: - Accidental git commits or backup leaks - Casual file browsing (someone glancing at your screen) - Log/error message exposure - File-level access by other processes ## The Solution: Defense in Depth ### Layer 1: Encryption at Rest (AES-256-GCM) All credential files (`auth-profiles.json`, etc.) are encrypted using AES-256-GCM with a per-installation master key. Plaintext files are automatically migrated on first access. **Protects against:** backup leaks, git commits, log exposure, screenshots, casual file browsing. ### Layer 2: OS Keychain Integration The master key is stored in the operating system's secure credential store rather than as a file on disk: | Platform | Backend | Security Model | |----------|---------|----------------| | **macOS** | Keychain (`security` CLI) | Protected by user login + optional Touch ID | | **Linux** | libsecret (`secret-tool`) | gnome-keyring / kwallet, session-locked | | **Windows** | DPAPI (PowerShell) | Tied to Windows user login, hardware-backed | | **Headless/Docker** | File fallback (`master.key`) | Graceful degradation | **Protects against:** same-user file access, key co-location (addresses review feedback from @HenryLoenwind). ### Combined Protection Matrix | Threat | Layer 1 (Encryption) | Layer 2 (Keychain) | Combined | |--------|---------------------|-------------------|----------| | Config in git/backup | ✅ Ciphertext only | — | ✅ | | Casual file browsing | ✅ Not human-readable | — | ✅ | | File-level attacker | ❌ Key on disk too | ✅ Key in OS vault | ✅ | | Process with user access | ❌ | ⚠️ Session-dependent | ⚠️ | | Root/admin access | ❌ | ❌ | ❌ (out of scope) | ## Migration Fully automatic, zero user action required: 1. **Plaintext → Encrypted**: On first access, plaintext JSON files are encrypted in-place with atomic backup/recovery 2. **File key → Keychain**: If `master.key` exists on disk and OS keychain is available, the key is moved to the keychain and the file is deleted 3. **Graceful fallback**: If no keychain is available (headless servers, Docker, CI), file-based key storage continues working exactly as before ## Implementation Details - **Zero new npm dependencies** — uses only Node.js built-ins (`crypto`, `child_process`, `fs`) - **Encrypted envelope format** (v1): `{ version, algorithm, keyId, nonce, ciphertext, tag }` - **Key tracking**: Short hash (`keyId`) in each envelope enables detection of key mismatches without exposing the key - **Crash safety**: Atomic write with `.tmp`/`.bak` recovery for interrupted migrations - **Logging**: One-time info messages for migration events and fallback decisions ## Files Changed - `src/infra/crypto-store.ts` — Core encryption/decryption with keychain-first key management - `src/infra/keychain.ts` — Cross-platform OS keychain abstraction (new) - `src/infra/keychain.test.ts` — Keychain backend tests (new) - `src/agents/auth-profiles/` — Updated to use encrypted storage throughout ## Complementary Approaches This PR is complementary to Docker-based credential isolation (useful for server deployments). The encryption + keychain approach is designed for the common case: developers and users running OpenClaw on their own machines. ## Acknowledgments Thanks to @HenryLoenwind for the review feedback that inspired the keychain integration. The observation that co-locating the key with encrypted data limits security was exactly right — and pushed us to build a more complete solution.

Most Similar PRs