The OpenClaw VM uses a 1Password service account to access credentials without interactive sign-in. This guide walks through creating the account, vault, and token.
- A 1Password account (Business or Teams plan -- service accounts are not available on individual plans)
- Admin access to create vaults and service accounts
Create a dedicated vault for OpenClaw credentials. Keeping them separate from personal vaults limits the blast radius if a token is compromised.
- Open 1Password in the browser or desktop app.
- Go to Vaults and click New Vault.
- Name it something like
OpenClaworOpenClaw-<environment>. - Add any secrets your OpenClaw instance needs (API keys, database credentials, etc.).
Service accounts provide non-interactive access to 1Password vaults via the CLI.
- Go to Developer > Infrastructure Secrets > Service Accounts in your 1Password admin console.
- Click Create Service Account.
- Name it (e.g.,
openclaw-vm). - Under Vault Access, grant access to only the vault you created in Step 1. Do not grant access to other vaults.
- Set permissions to Read Items (write access is not needed for retrieving secrets).
- Click Save and copy the generated token. This is your
OP_SERVICE_ACCOUNT_TOKEN.
The token starts with ops_ and is a long base64-encoded string. You will not be able to see it again after closing the dialog.
During the wizard's Credentials step (Step 6), you will be prompted:
? Set up 1Password now? [Y/n]
? OP_SERVICE_ACCOUNT_TOKEN: ****
The CLI validates the token by running op whoami inside the VM. On success, it:
- Stores the token at
~/.openclaw/credentials/op-tokeninside the VM withchmod 600. - Appends
export OP_SERVICE_ACCOUNT_TOKEN="<token>"to~/.bashrcinside the VM.
If you skip this step during the wizard, you can configure it manually later:
limactl shell <vmName>
mkdir -p ~/.openclaw/credentials
echo 'ops_your_token_here' > ~/.openclaw/credentials/op-token
chmod 600 ~/.openclaw/credentials/op-token
echo 'export OP_SERVICE_ACCOUNT_TOKEN="ops_your_token_here"' >> ~/.bashrc
source ~/.bashrcThe recommended way to inject secrets into processes is op run, which sets environment variables from 1Password references:
# .env file with op:// references
DATABASE_URL=op://OpenClaw/database/url
API_KEY=op://OpenClaw/api-service/credential
# Run a command with secrets injected
op run --env-file .env -- node server.jsThe format is op://<vault>/<item>/<field>.
# Read a specific field
op read "op://OpenClaw/database/url"
# Read as JSON
op item get "database" --vault "OpenClaw" --format jsonThe op:// URI scheme follows this pattern:
op://<vault-name>/<item-name>/<field-name>
op://<vault-name>/<item-name>/<section-name>/<field-name>
Field names are case-insensitive. If a field name has spaces, use the field's label as shown in 1Password.
- The token file at
~/.openclaw/credentials/op-tokenis set tochmod 600(owner read/write only). - The
.gitignorein the generated project excludes*.tokenand.envfiles. - Never commit the
OP_SERVICE_ACCOUNT_TOKENto version control.
- Grant the service account access to only the vault it needs.
- Use read-only permissions unless write access is specifically required.
- Create separate service accounts for separate environments (dev, staging, production).
- Rotate the token periodically via the 1Password admin console.
- After rotating, update the token inside the VM:
limactl shell <vmName>
echo 'new_token' > ~/.openclaw/credentials/op-token
# Update .bashrc or re-run the credentials stepThe token only lives inside the VM. The host macOS machine does not have the token in its environment or filesystem (unless you put it there). The project directory mount at /mnt/project is read-only from the guest, so the VM cannot write the token to your host filesystem.
Config files support op:// and env:// URI references so you never need
plaintext secrets in your config. This means configs can be committed to git.
env://VAR_NAME resolves from the host's environment at config-load time.
Bun auto-loads .env files, so you can put the value there:
# .env (gitignored)
OP_SERVICE_ACCOUNT_TOKEN=ops_your_token_here{
"capabilities": {
"one-password": {
"serviceAccountToken": "env://OP_SERVICE_ACCOUNT_TOKEN"
}
}
}op://vault/item/field references are resolved inside the VM via op read
after the 1Password service account is set up. The resolved values are held
in memory only -- never written to disk.
{
"capabilities": {
"one-password": {
"serviceAccountToken": "env://OP_SERVICE_ACCOUNT_TOKEN"
}
},
"provider": {
"type": "anthropic",
"apiKey": "op://Infrastructure/Anthropic/api-key"
},
"telegram": {
"botToken": "op://Infrastructure/Telegram/bot-token"
}
}Config files with op:// references require capabilities["one-password"] to be
configured (validation will reject configs with op:// refs but no 1Password
service account).
When 1Password is configured, the bootstrap flow uses --secret-input-mode ref
during openclaw onboard. This tells openclaw to store SecretRefs (pointers to
secrets) instead of the plaintext values. After onboard, an exec secret
provider is registered so openclaw resolves secrets via op read at runtime.
The result: no plaintext secrets anywhere on disk -- not in the clawctl config, not in openclaw's config, and not in any environment file.
When 1Password is configured, a custom skill is installed at
data/workspace/skills/secret-management/SKILL.md. This teaches the
agent how to use the pre-configured op CLI for reading secrets, injecting
them into processes, and discovering vaults. It supersedes the built-in
1password skill (which requires interactive tmux-based auth).
OpenClaw's exec tool doesn't source ~/.profile, so OP_SERVICE_ACCOUNT_TOKEN
is not available in the exec environment. To work around this, bootstrap installs
a wrapper script at ~/.local/bin/op that reads the token from
~/.openclaw/secrets/op-token and execs the real binary (moved to
~/.local/bin/.op-real). The agent uses op normally — no tmux or manual
environment setup needed.
The op CLI is gated behind an exec-approval rule (~/.openclaw/exec-approvals.json).
The default policy is ask: on-miss — the user must approve the first op
invocation, then subsequent calls are allowed automatically. This ensures
credential access is an explicit opt-in at runtime, not just at setup time.
See config.op.json for a complete
example with zero plaintext secrets.