I Let OpenClaw Migrate Its Own Secrets to Bitwarden
My OpenClaw config file had 25+ API keys and tokens sitting in plaintext JSON, which should make you uncomfortable. LLM provider keys, Telegram bot tokens, TTS keys, search APIs — all of it in one file, with no encryption, no access control, and no audit trail.
Anyone who stole that file would get everything, and there'd be no trace left behind.
I finally fixed it, and the ironic part is that 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..."
}
}That's 21 secrets sitting in a JSON file on disk.
Every security person reading this just winced. I know.
The threat model is simple. If someone gets access to your machine — malware, a compromised backup, a stolen laptop, a rogue app with file access — they have every key. No authentication is required, no logs are generated, and there's 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. It uses a zero-knowledge architecture with end-to-end encryption, so 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 so every secret starts with OC_ — OC_LLM_PROVIDER_1_KEY, OC_TELEGRAM_BOT_TOKEN, OC_TTS_API_KEY. It keeps everything clean and easy to grep.
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}"
}
}Since it now holds nothing but variable names, the config file is safe to commit, back up, or even post publicly.
The BWS access token — the one key that unlocks all the others — lives in macOS Keychain, protected by the Secure Enclave. Accessing it requires biometric authentication (Touch ID) or your system password. It's hardware-backed rather than a plain file sitting 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 — 21 secrets across 25+ config fields — took about 30 minutes.
The Security Model: Defense in Depth
Before, the security model came down to one file, one layer, and 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 and 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 kills it. 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, so neither Bitwarden nor a network intercept can 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 and independently revocable, which is a far cry from one JSON file holding 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."
It isn't, and 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, where extracting it requires biometric auth or the system password — a hardware security boundary rather than 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 can be revoked instantly from the Bitwarden dashboard. One action cuts all access.
The config file left no trace. If someone reads a file, you'll never know. BWS keeps audit logs that track every secret access, so you can see exactly what was accessed, when, and by which machine.
The config file worked offline. Copy it to a USB drive, walk away, and use it anywhere. The BWS token requires network access to Bitwarden's servers, so air-gapped exfiltration doesn't work.
None of this is perfect, but it trades a single point of failure for several layers an attacker has to get through.
Rotation Is Now Trivial
This 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
The config file and the variable names stay the same. You update one place and everything downstream picks it up.
The Meta Moment
OpenClaw migrated its own secrets, which I find genuinely interesting. I told it what I wanted, and 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.
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 plus macOS Keychain is hard to beat. Twenty-one secrets and 25+ config fields, migrated in half an hour — there's really no excuse left.