I Let OpenClaw Migrate Its Own Secrets to Bitwarden
Here's something that should make you uncomfortable: my OpenClaw config file had 25+ API keys and tokens sitting in plaintext JSON.
LLM provider keys. Telegram bot tokens. TTS keys. Search APIs. The works. All in one file. No encryption. No access control. No audit trail.
Steal that file, and you get everything. No trace left behind.
I finally fixed it. The ironic part? OpenClaw did the migration itself.
The Problem
OpenClaw needs API keys for everything — 4 LLM providers, 3 Telegram bot tokens, a TTS engine, and 6 other API services.
The config looked like this:
{
"providers": {
"provider-1": {
"apiKey": "sk-abc123realkey456..."
},
"provider-2": {
"apiKey": "sk-ant-realkey789..."
}
},
"telegram": {
"token": "7123456789:AAF-realtoken..."
}
}
21 secrets. In a JSON file. On disk.
Every security person reading this just winced. I know.
The threat model is simple. Someone gets access to your machine — malware, compromised backup, stolen laptop, rogue app with file access — and they have every key. No authentication required. No logs generated. No way to know it happened.
The Solution: Bitwarden Secrets Manager + macOS Keychain
Bitwarden Secrets Manager (BWS) is Bitwarden's answer to HashiCorp Vault and AWS Secrets Manager, but open source and with a usable free tier. Zero-knowledge architecture. End-to-end encrypted. Your secrets are encrypted client-side before they ever touch Bitwarden's servers.
The plan was straightforward:
- Store all 21 secrets in BWS
- Replace every plaintext value in config with
${VAR_NAME}placeholders - Store the BWS access token in macOS Keychain (hardware-backed, biometric-gated)
- Let OpenClaw's env var substitution pull secrets at runtime
OpenClaw already supports ${VAR_NAME} interpolation in its JSON config. So the config just needed to reference environment variables instead of raw values.
The Migration
Installed the BWS CLI (v2.0.0 on Apple Silicon), created a project, and started migrating.
The free tier gives you 2 projects. I used 1 with a prefix naming convention — every secret starts with OC_. OC_LLM_PROVIDER_1_KEY, OC_TELEGRAM_BOT_TOKEN, OC_TTS_API_KEY. Clean and greppable.
The config went from this:
{
"providers": {
"provider-1": {
"apiKey": "sk-abc123realkey456..."
}
},
"telegram": {
"token": "7123456789:AAF-realtoken..."
},
"tts": {
"apiKey": "el-realkey..."
}
}
To this:
{
"providers": {
"provider-1": {
"apiKey": "${OC_LLM_PROVIDER_1_KEY}"
}
},
"telegram": {
"token": "${OC_TELEGRAM_BOT_TOKEN}"
},
"tts": {
"apiKey": "${OC_TTS_API_KEY}"
}
}
The config file is now safe to commit, back up, or even post publicly. It's just variable names.
The BWS access token — the one key that unlocks all the others — lives in macOS Keychain. Protected by the Secure Enclave. You need biometric authentication (Touch ID) or your system password to access it. Hardware-backed. Not a file on disk.
At gateway startup, the shell pulls the BWS token from Keychain, exports secrets as environment variables, and OpenClaw's config interpolation does the rest.
The whole migration took about 30 minutes. 21 secrets. 25+ config fields. Done.
The Security Model: Defense in Depth
Before, the security model was: one file, one layer, zero logging.
Now there are multiple layers an attacker has to breach:
Layer 1 — macOS Keychain. The BWS access token is stored here. Protected by the Secure Enclave, biometric-gated. You can't just read a file — you need the user's fingerprint or password.
Layer 2 — BWS Access Token. Even if you extract the token, it's revocable. One click in the Bitwarden dashboard and it's dead. The token also only works with network access to Bitwarden's servers.
Layer 3 — Bitwarden Vault. The secrets themselves are E2E encrypted in Bitwarden's zero-knowledge vault. Bitwarden can't read them. A network intercept can't read them.
Layer 4 — Individual Secrets. Each secret is a separate entity. You can revoke, rotate, or audit any single one without touching the others.
Every layer is logged. Every layer is independently revocable. Compare that to "one JSON file with everything."
"But If Someone Gets Your BWS Token..."
I can hear it already. "You've just moved the problem. Instead of protecting a config file, now you're protecting a BWS token. Same thing."
Not the same thing. Here's why:
The config file had zero protection. Any process that could read files could grab it. The BWS token sits in macOS Keychain — you need biometric auth or the system password to extract it. That's a hardware security boundary, not a filesystem permission.
The config file was irrevocable. If someone copied your keys, you'd have to rotate every single one across every provider. The BWS token? Revoke it instantly from the Bitwarden dashboard. One action. All access cut.
The config file left no trace. Someone reads a file, you'll never know. BWS has audit logs. Every secret access is tracked. You can see exactly what was accessed, when, and by what machine.
The config file worked offline. Copy it to a USB drive, walk away, use it anywhere. The BWS token requires network access to Bitwarden's servers. Air-gapped exfiltration doesn't work.
Is it perfect? No. Nothing is. But it's layers of defense versus a single point of failure. That's the whole point of defense in depth.
Rotation Is Now Trivial
This is the part that surprised me the most. Before, rotating a key meant:
- Generate new key at the provider
- Find the config file
- Find the right field (hope you remember which one)
- Update the value
- Restart the gateway
- Hope you didn't break the JSON formatting
Now it's:
- Generate new key at the provider
- Update the value in Bitwarden
- Restart the gateway
That's it. The config file doesn't change. The variable names don't change. You update one place and everything downstream picks it up.
The Meta Moment
The part I find genuinely interesting: OpenClaw migrated its own secrets. I told it what I wanted, it installed the BWS CLI, created the secrets, updated the config, and set up the Keychain integration.
An AI agent securing its own API keys. There's something poetic about that.
Takeaway
If you're running any kind of agent, bot, or service with API keys in plaintext config files — fix it. Today. It doesn't have to be Bitwarden. AWS Secrets Manager, HashiCorp Vault, 1Password's service accounts, even age-encrypted files. Anything is better than plaintext.
But if you want something open source, free-tier friendly, and done in 30 minutes: BWS + macOS Keychain is hard to beat.
21 secrets. 25+ config fields. 30 minutes. Zero excuses.