#21554: feat(security): encrypt workspace files and config at rest
docs
gateway
cli
commands
agents
size: XL
Cluster:
OpenClaw Plugin Enhancements
## Summary
Encrypt sensitive workspace files at rest using AES-256-GCM with keys stored in the macOS Keychain. Zero new dependencies — uses only Node.js built-in `node:crypto` and macOS `security` CLI.
Related: [Discussion #8177 — Encrypt workspace files and config at rest](https://github.com/openclaw/openclaw/discussions/8177)
## Two-Key Architecture
A single master password derives two independent 256-bit keys via scrypt with domain separation:
| Key | Protects | Purpose |
|-----|----------|---------|
| **Workspace Key** | `MEMORY.md`, `USER.md`, `IDENTITY.md`, `TOOLS.md`, `HEARTBEAT.md`, `memory/*.md` | Sensitive agent context and personal info |
| **Config Key** | `config.yaml` | API keys, tokens, service credentials |
Separate keys limit blast radius — compromising one does not expose the other.
## How It Works
### Encryption
- **Algorithm:** AES-256-GCM (authenticated encryption)
- **Key derivation:** scrypt (N=131072, r=8, p=1) — memory-hard, built into Node.js
- **Key storage:** macOS Keychain (`ai.openclaw.encryption` service)
- **File format:** `[6B magic "OCENC\x01"][12B nonce][ciphertext][16B auth tag]`
- **Detection:** Magic header allows instant encrypted-vs-plaintext detection without attempting decryption
### Read Path
Files are transparently decrypted when read. Both encrypted and plaintext files can coexist (migration-friendly):
1. Check for `OCENC` magic header
2. If encrypted → decrypt with appropriate key from memory
3. If plaintext → read as-is (backward compatible)
### Write Path
Agent tools write files in plaintext (no changes to tool libraries needed). On each gateway startup, `bootstrapEncryption()` re-encrypts any files that were written in plaintext since the last startup, maintaining at-rest protection.
## CLI Commands
```bash
openclaw security init # Set up encryption (password → keys → encrypt)
openclaw security status [--json] # Show encryption status
openclaw security change-password # Re-derive keys, re-encrypt everything
openclaw security disable # Decrypt everything, remove keys
```
## Integration Points
| File | Change |
|------|--------|
| `agents/workspace.ts` | Bootstrap files (SOUL.md, USER.md, etc.) read through `readFileAutoDecrypt` |
| `memory/manager.ts` | `memory_get` tool reads through encrypted middleware |
| `memory/internal.ts` | Memory indexing/search reads through encrypted middleware |
| `gateway/server.impl.ts` | `bootstrapEncryption()` on startup, `shutdownEncryption()` on close |
| `cli/program/routes.ts` | `openclaw security` CLI route |
## New Files
```
src/security/encryption/
├── crypto.ts # AES-256-GCM encrypt/decrypt with OCENC magic header
├── key-derivation.ts # scrypt two-key derivation (zero new deps)
├── keychain.ts # macOS Keychain integration via security CLI
├── workspace-fs.ts # Encrypted file read/write with migration support
├── metadata.ts # .encryption-meta.json state tracking
├── setup.ts # High-level orchestrator (init/change-password/disable)
├── fs-middleware.ts # Transparent auto-decrypt middleware (async + sync)
├── integration.ts # Gateway bootstrap hook with re-encryption
├── index.ts # Barrel exports
src/commands/security.ts # CLI commands
docs/workspace-encryption.md # Full documentation
```
## Test Coverage
**96 tests across 11 test files**, all passing:
| Test File | Tests | Coverage |
|-----------|-------|----------|
| `crypto.test.ts` | 20 | Round-trip, tamper detection, key validation, edge cases |
| `key-derivation.test.ts` | 8 | Determinism, domain separation, salt uniqueness |
| `keychain.test.ts` | 6 | Mocked execFileSync, store/get/delete lifecycle |
| `workspace-fs.test.ts` | 15 | Encrypted read/write, migration, round-trip |
| `metadata.test.ts` | 6 | Create, read/write, encryption state |
| `setup.test.ts` | 2 | Init flow with mocked keychain |
| `fs-middleware.test.ts` | 13 | Auto-decrypt async+sync, key management |
| `e2e.test.ts` | 6 | Full lifecycle: init → read → write → re-encrypt → change-password → disable |
| `routes.test.ts` | 1 | Security route matching |
| Existing tests | 19 | `internal.test.ts` + `workspace.test.ts` still passing |
## Security Considerations
**Protects against:** disk theft, backup exposure, unauthorized file access by other users.
**Does not protect against:** root/admin access (process memory), keyloggers, active compromise with code execution.
**Intentionally unencrypted:** `AGENTS.md`, `SOUL.md`, `BOOTSTRAP.md` (instructions, not secrets), `.encryption-meta.json` (contains only the non-secret salt).
## Platform Support
| Platform | Status |
|----------|--------|
| macOS | ✅ Supported (Keychain) |
| Linux | 🔜 Phase 3 (libsecret/secret-service) |
| Windows | 🔜 Phase 3 (Credential Manager) |
Most Similar PRs
#7953: feat(security): encrypt credentials at rest with AES-256-GCM
by TGambit65 · 2026-02-03
81.4%
#21053: security(infra): OS keychain storage for device private keys
by richvincent · 2026-02-19
70.5%
#16663: feat: GCP Secret Manager integration for external secrets management
by amor71 · 2026-02-15
65.5%
#11787: Feat/openclaw defender
by nightfullstar · 2026-02-08
65.4%
#20772: fix(security): OC-15 encrypt Nostr private keys to prevent plaintex...
by aether-ai-agent · 2026-02-19
65.3%
#19543: security: add encrypted localStorage wrapper using Web Crypto API
by Mozzzaic · 2026-02-17
65.1%
#21530: feat: Native MCP (Model Context Protocol) client support
by amor71 · 2026-02-20
64.9%
#23574: security: P0 critical remediation — plugin sandbox, password hashin...
by lumeleopard001 · 2026-02-22
64.5%
#10514: Security: harden AGENTS.md with gateway, prompt injection, and supp...
by catpilothq · 2026-02-06
64.2%
#15757: feat(security): add hardening gap audit checks
by saurabhsh5 · 2026-02-13
63.6%