openclaw-secret-providers
v1.0.2
Published
Exec secret provider scripts for OpenClaw — Azure Key Vault, macOS Keychain, 1Password
Maintainers
Readme
openclaw-secret-providers
Exec secret provider scripts for OpenClaw — integrates external secret stores via OpenClaw's built-in exec provider protocol.
Providers
| Provider | Script | Deps |
|---|---|---|
| Azure Key Vault | akv/akv-secret-fetch.cjs | @azure/identity, @azure/keyvault-secrets |
| macOS Keychain | keychain/keychain-secret-fetch.cjs | none (system /usr/bin/security) |
| 1Password | 1password/op-secret-fetch.cjs | none (op CLI) |
Install
npm install -g openclaw-secret-providersHow it works
OpenClaw resolves secrets at gateway startup and on each agent turn that needs credentials. When a SecretRef uses source: "exec", OpenClaw:
- Spawns the configured script as a subprocess (no shell, direct
execFile) - Sends a JSON request on stdin:
{"protocolVersion":1,"provider":"<name>","ids":["secret-id",...]} - Expects a JSON response on stdout:
{"protocolVersion":1,"values":{"secret-id":"value"}}
Scripts are batched — all secrets for a provider are fetched in one subprocess call.
Quick start
# See available providers
openclaw-secrets list
# Get the absolute path for your openclaw.json
openclaw-secrets path akv
openclaw-secrets path keychain
openclaw-secrets path 1password
# Print a ready-to-paste config snippet
openclaw-secrets config akvAzure Key Vault
Prerequisites
brew install azure-cli
az loginCreate vault and service principal
az group create --name openclaw-rg --location eastus
az keyvault create --name my-secrets --resource-group openclaw-rg --location eastus
az ad sp create-for-rbac --name openclaw-akv-sp --skip-assignment
# note: appId, password, tenant from output
az keyvault set-policy --name my-secrets --spn <appId> --secret-permissions get listStore secrets
az keyvault secret set --vault-name my-secrets --name openai-api-key --value "$OPENAI_API_KEY"
az keyvault secret set --vault-name my-secrets --name discord-token --value "$DISCORD_TOKEN"
# AKV naming: kebab-case version of the secret nameTest
node $(openclaw-secrets path akv) openai-api-keyopenclaw.json config
{
"secrets": {
"providers": {
"akv": {
"source": "exec",
"command": "<output of: openclaw-secrets path akv>",
"timeoutMs": 10000,
"allowSymlinkCommand": true,
"env": {
"AKV_VAULT_URL": "https://my-secrets.vault.azure.net",
"AKV_TENANT_ID": "<tenant-id>",
"AKV_CLIENT_ID": "<app-id>",
"AKV_CLIENT_SECRET": "<password>"
}
}
},
"defaults": { "exec": "akv" }
}
}Reference a secret:
{ "source": "exec", "provider": "akv", "id": "openai-api-key" }Auth options
| Env vars set | Credential used |
|---|---|
| AKV_CLIENT_SECRET + AKV_TENANT_ID + AKV_CLIENT_ID | Service Principal (client secret) |
| AKV_CLIENT_CERT_PATH + AKV_TENANT_ID + AKV_CLIENT_ID | Service Principal (certificate) |
| none | DefaultAzureCredential (managed identity, az CLI, env) |
Cache
Default TTL: 1 hour. Override with AKV_CACHE_TTL_MS env var (set to 0 to disable).
macOS Keychain
No external deps. Uses the system /usr/bin/security binary.
Store secrets
# Add
security add-generic-password -a openclaw -s openai-api-key -w "sk-..."
# Update
security add-generic-password -U -a openclaw -s openai-api-key -w "new-value"
# Verify
node $(openclaw-secrets path keychain) openai-api-keyopenclaw.json config
{
"secrets": {
"providers": {
"keychain": {
"source": "exec",
"command": "<output of: openclaw-secrets path keychain>",
"timeoutMs": 5000,
"allowSymlinkCommand": true
}
}
}
}Reference a secret:
{ "source": "exec", "provider": "keychain", "id": "openai-api-key" }Env vars
| Var | Default | Description |
|---|---|---|
| KEYCHAIN_ACCOUNT | openclaw | Keychain account name (-a arg) |
| KEYCHAIN_PATH | system default | Path to a specific .keychain-db file |
1Password
Requires the 1Password CLI (op) installed and authenticated.
Auth
# Interactive (dev)
op signin
# Non-interactive (server/daemon)
export OP_SERVICE_ACCOUNT_TOKEN="ops_..."Secret IDs
Secret IDs must be op:// references:
op://vault-name/item-name/field-nameExamples:
op://Personal/OpenAI/passwordop://openclaw/discord-bot-token/credential
Store secrets
op item create --category=Password --title="OpenAI" --vault=Personal password="sk-..."
# Verify
node $(openclaw-secrets path 1password) "op://Personal/OpenAI/password"openclaw.json config
{
"secrets": {
"providers": {
"1password": {
"source": "exec",
"command": "<output of: openclaw-secrets path 1password>",
"timeoutMs": 10000,
"allowSymlinkCommand": true,
"passEnv": ["OP_SERVICE_ACCOUNT_TOKEN"]
}
}
}
}Reference a secret:
{ "source": "exec", "provider": "1password", "id": "op://Personal/OpenAI/password" }Env vars
| Var | Default | Description |
|---|---|---|
| OP_SERVICE_ACCOUNT_TOKEN | — | Service account token for non-interactive auth |
| OP_BIN | op | Path to op binary |
| OP_CACHE_TTL_MS | 300000 (5m) | Cache TTL in ms; set to 0 to disable |
Security notes
- Scripts run as a subprocess with no shell. No injection risk from secret IDs.
- OC enforces: script must be owned by current user, not world-writable, not a symlink (set
allowSymlinkCommand: truewhen Node is installed via Homebrew). - AKV bootstrap credentials (
AKV_*) are the only secrets that cannot be in AKV itself — store them inopenclaw.jsonenvblock or (better) in macOS Keychain via a separatekeychainprovider. - 1Password service account tokens follow the same bootstrap pattern — set via
passEnvso the gateway forwards them to the subprocess.
Contributing
PRs welcome. Use the same stdin/stdout protocol:
// stdin
{"protocolVersion":1,"provider":"<name>","ids":["secret-id"]}
// stdout
{"protocolVersion":1,"values":{"secret-id":"the-value"}}Exit code must be 0 on success, non-zero on failure. Write errors to stderr only.
License
MIT
