Skip to content

OpenClaw Secrets Management with 1Password

This guide walks through setting up 1Password as a centralized secret provider for OpenClaw. By the end, every API key, token, and credential in your OpenClaw config will resolve at runtime through 1Password — nothing sensitive stored in plaintext.

  • OpenClaw installed and running
  • 1Password account (personal or business)
  • 1Password CLI (op) installed
  • Basic familiarity with OpenClaw’s openclaw.json config

Part 1: Install and Configure 1Password CLI

Section titled “Part 1: Install and Configure 1Password CLI”
Terminal window
# Ubuntu/Debian
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/$(dpkg --print-architecture) stable main" | \
sudo tee /etc/apt/sources.list.d/1password.list
sudo apt update && sudo apt install 1password-cli
# Verify
op --version

For non-interactive (agent) access, you need a service account — not a personal sign-in.

  1. Go to your 1Password admin console
  2. Navigate to DeveloperService Accounts
  3. Create a new service account with access to your vault
  4. Copy the service account token

Add the service account token to your OpenClaw environment:

Terminal window
# In ~/.openclaw/.env
OP_SERVICE_ACCOUNT_TOKEN=ops_your_service_account_token_here

This is the one secret that bootstraps access to everything else. Protect this file with appropriate permissions:

Terminal window
chmod 600 ~/.openclaw/.env
Terminal window
# Test that op can authenticate
export OP_SERVICE_ACCOUNT_TOKEN="ops_your_token"
op vault list
op item list --vault "Your-Vault-Name"

Part 2: Create a Vault and Add Credentials

Section titled “Part 2: Create a Vault and Add Credentials”

Keep OpenClaw credentials separate from personal items:

Terminal window
# Via 1Password web UI or CLI
op vault create "OpenClaw-API-Keys"

For each API key your OpenClaw instance uses:

Terminal window
# Create items with the password field
op item create \
--category=password \
--title="ANTHROPIC_API_KEY" \
--vault="OpenClaw-API-Keys" \
password="sk-ant-your-key-here"
op item create \
--category=password \
--title="OPENROUTER_API_KEY" \
--vault="OpenClaw-API-Keys" \
password="sk-or-v1-your-key-here"
op item create \
--category=password \
--title="DISCORD_TOKEN" \
--vault="OpenClaw-API-Keys" \
password="your-discord-bot-token"
Terminal window
# List all items (titles only, never echo values)
op item list --vault OpenClaw-API-Keys --format json | jq '.[].title'
# Verify a specific item resolves (check existence, not value)
op read "op://OpenClaw-API-Keys/ANTHROPIC_API_KEY/password" > /dev/null && echo "OK" || echo "FAILED"

Important: Never echo secret values to terminal, logs, or chat. Always verify with existence checks.

Part 3: Configure OpenClaw Secret Providers

Section titled “Part 3: Configure OpenClaw Secret Providers”

In openclaw.json, add a secrets.providers entry for each credential:

{
"secrets": {
"providers": {
"onepassword_anthropic": {
"source": "exec",
"command": "/usr/bin/op",
"args": ["read", "op://OpenClaw-API-Keys/ANTHROPIC_API_KEY/password"],
"jsonOnly": false,
"passEnv": ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],
"allowInsecurePath": true
},
"onepassword_openrouter": {
"source": "exec",
"command": "/usr/bin/op",
"args": ["read", "op://OpenClaw-API-Keys/OPENROUTER_API_KEY/password"],
"jsonOnly": false,
"passEnv": ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],
"allowInsecurePath": true
}
}
}
}
Field Purpose Notes
source Always "exec" for CLI-based providers
command Full path to op binary Use which op to find it
args Arguments for op read Format: op://Vault/Item/Field
jsonOnly Set false for raw text output op read returns raw text, not JSON
passEnv Environment variables to forward Must include OP_SERVICE_ACCOUNT_TOKEN
allowInsecurePath Allow non-user-owned binaries Required because op is owned by root

Most 1Password items use password as the field name, but watch for variations:

  • password — Standard for most items
  • credential — Some items created through web UI
  • token — Custom field name
  • notesPlain — Notes field

Check your item’s field structure:

Terminal window
op item get "ITEM_NAME" --vault "OpenClaw-API-Keys" --format json | \
jq '[.fields[] | select(.value != null) | .label]'

Part 4: Replace Plaintext Values with SecretRefs

Section titled “Part 4: Replace Plaintext Values with SecretRefs”

Replace plaintext apiKey values with SecretRef objects:

Before:

{
"tools": {
"web": {
"search": {
"apiKey": "BSA-actual-key-value"
}
}
}
}

After:

{
"tools": {
"web": {
"search": {
"apiKey": {
"source": "exec",
"provider": "onepassword_brave",
"id": "value"
}
}
}
}
}

Same pattern for skill configs:

{
"skills": {
"entries": {
"todoist": {
"apiKey": {
"source": "exec",
"provider": "onepassword_todoist",
"id": "value"
}
}
}
}
}

For model provider credentials, use auth-profiles instead of direct apiKey values.

Create auth-profiles.json in each agent’s directory (~/.openclaw/agents/<name>/agent/auth-profiles.json):

{
"version": 1,
"profiles": {
"openrouter:default": {
"type": "api_key",
"provider": "openrouter",
"keyRef": {
"source": "exec",
"provider": "onepassword_openrouter",
"id": "value"
}
},
"anthropic:default": {
"type": "token",
"provider": "anthropic",
"tokenRef": {
"source": "exec",
"provider": "onepassword_anthropic",
"id": "value"
}
},
"cloudflare-ai-gateway:default": {
"type": "api_key",
"provider": "cloudflare-ai-gateway",
"keyRef": {
"source": "exec",
"provider": "onepassword_cloudflare",
"id": "value"
}
}
}
}

Then set the apiKey in the agent’s models.json to "secretref-managed":

{
"providers": {
"openrouter": {
"apiKey": "secretref-managed"
},
"anthropic": {
"apiKey": "secretref-managed"
}
}
}

If a provider doesn’t require authentication (local Ollama, local Whisper), remove the apiKey field entirely rather than setting a placeholder:

{
"providers": {
"ollama": {
"baseUrl": "http://localhost:11434",
"api": "ollama"
}
}
}

Always back up before restarting:

Terminal window
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.backup.$(date +%Y%m%d-%H%M%S)
Terminal window
openclaw secrets audit --check

Target output:

Secrets audit: clean. plaintext=0, unresolved=0, shadowed=0, legacy=0.

If you see REF_UNRESOLVED, the SecretRef can’t resolve. Common causes:

Error Cause Fix
“returned invalid JSON” Wrong id value (e.g., "google/apiKey" instead of "value") Use "id": "value" for op read providers
“must be owned by current user” Missing allowInsecurePath Add "allowInsecurePath": true
“provider not found” Typo in provider name Check secrets.providers key matches
“command failed” OP_SERVICE_ACCOUNT_TOKEN not available Add to passEnv array
Terminal window
openclaw gateway restart

If things break, restore:

Terminal window
cp ~/.openclaw/openclaw.json.backup.TIMESTAMP ~/.openclaw/openclaw.json
openclaw gateway restart
  1. Start with low-risk credentials — Skills and non-critical integrations first
  2. Test one at a time — Validate each SecretRef resolves before moving to the next
  3. Save critical-path credentials for last — Discord token, primary model provider keys
  4. Back up before every restart — Revert should always be one command away
  • One vault, one purpose — Keep OpenClaw credentials in a dedicated vault
  • Service account, not personal — Don’t rely on interactive sign-in for agent access
  • Never echo values — Verify credentials exist with > /dev/null && echo OK, never print them
  • Audit regularly — Run openclaw secrets audit --check after any config change
  • Restrict .env permissionschmod 600 on the file containing OP_SERVICE_ACCOUNT_TOKEN
  • Boot order matters — The OP_SERVICE_ACCOUNT_TOKEN must be available in the environment when OpenClaw starts. If using systemd, ensure the .env file is loaded via EnvironmentFile=
  • 1Password CLI caches — The CLI caches auth sessions. If you rotate the service account token, restart the gateway to pick up the new one
  • Field name consistency — Standardize on password as the field name when creating vault items. It makes op read paths predictable

When you add a new integration:

  1. Create the 1Password vault item
  2. Add a secrets.providers entry in openclaw.json
  3. Reference it with a SecretRef in the appropriate config location
  4. Run openclaw secrets audit --check
  5. Restart and verify
{
"source": "exec",
"provider": "<provider_name>",
"id": "value"
}
"onepassword_<name>": {
"source": "exec",
"command": "/usr/bin/op",
"args": ["read", "op://OpenClaw-API-Keys/<ITEM_TITLE>/password"],
"jsonOnly": false,
"passEnv": ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],
"allowInsecurePath": true
}
Terminal window
# Audit current state
openclaw secrets audit --check
# List vault items
op item list --vault OpenClaw-API-Keys --format json | jq '.[].title'
# Verify a specific item (never echo the value)
op read "op://OpenClaw-API-Keys/ITEM/password" > /dev/null && echo "resolves: yes"
# Backup config
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.backup.$(date +%Y%m%d-%H%M%S)

Written based on a real migration from 127 plaintext credentials to zero on an OpenClaw homelab deployment. March 2026.