OpenClaw plugin for 1Password secrets. Uses the official JavaScript SDK with service accounts for fully headless operation — no desktop app, no biometrics, no popups.
-
Resolves
op://references inopenclaw.jsonat startup — Replace plaintext API keys withop://Agent Secrets/Item/fieldreferences. The plugin resolves them to real values in memory when OpenClaw boots. The plaintext key never touches disk. -
Gives agents tools to read and write secrets on demand — Agents call
op_read_secretto pull a key from 1Password at runtime,op_list_itemsto discover what's available, orop_write_secretto store new credentials. -
CLI for diagnostics —
openclaw op-secrets testverifies 1Password connectivity.openclaw op-secrets readshows a redacted preview of any secret.
-
auth-profiles.jsoncannot useop://refs. This file is loaded by a separate OpenClaw subsystem that the plugin can't hook into. Auth provider tokens (Anthropic, OpenAI OAuth) must remain as real values in that file. The resolver service only handlesopenclaw.json(the main config passed to plugins viactx.config). -
op_write_secretrequireswrite_itemspermission on the service account. The default setup usesread_itemsonly. See Write Support for setup. -
Secrets are resolved in memory only. If OpenClaw reloads config from disk mid-session,
op://refs need to be resolved again. The service runs at startup, so this is fine for normal operation.
- 1Password Teams or Business plan (required for service accounts)
- 1Password account with service account support (Teams, Business, or Enterprise)
- Node.js 18+
- OpenClaw 2026.1+
openclaw plugins install @openclaw/1passwordCreate a custom vault and service account (service accounts can't access built-in vaults):
# Create a vault for agent secrets
op vault create "Agent Secrets"
# Add your API keys
op item create --category "API Credential" --title "OpenAI API" \
--vault "Agent Secrets" 'api key=YOUR_KEY_HERE'
# Create a read-only service account
op service-account create "OpenClaw Agent" \
--expires-in 0 \
--vault "Agent Secrets:read_items"mkdir -p ~/.openclaw/secrets
# Save the ops_... token from step 2 into this file:
echo "ops_..." > ~/.openclaw/secrets/op-sa-token
chmod 600 ~/.openclaw/secrets/op-sa-tokenAdd to ~/.openclaw/openclaw.json:
{
"plugins": {
"entries": {
"op-secrets": {
"enabled": true,
"config": {
"defaultVault": "Agent Secrets"
}
}
}
}
}Note: The config key must be op-secrets (matching the plugin manifest id), not 1password.
openclaw op-secrets testThe plugin resolves op://vault/item/field strings in openclaw.json at startup. The real key never appears on disk.
Example: a custom config field
{
"someService": {
"apiKey": "op://Agent Secrets/Some Service/api key"
}
}Important: OpenAI API key for memory search
Do NOT put an apiKey in memorySearch.remote. OpenClaw's resolveOpenAiEmbeddingClient() checks remote.apiKey first — if any value exists (even an op:// reference), it uses it directly and never falls through to the env var. Instead, leave "remote": {} empty. The plugin resolves the OpenAI key from 1Password and sets process.env.OPENAI_API_KEY at startup; memory search picks it up from the env var.
Note: op:// resolution only works for openclaw.json. Auth profile tokens in auth-profiles.json cannot use op:// refs — see Limitations.
Reads a secret value from 1Password. Agents call this tool when they need an API key or credential.
| Parameter | Required | Default | Description |
|---|---|---|---|
item |
Yes | — | Item title (e.g. "OpenAI API") |
vault |
No | Config default | Vault name |
field |
No | "api key" |
Field name |
Lists all items in a vault so agents can discover available secrets.
| Parameter | Required | Default | Description |
|---|---|---|---|
vault |
No | Config default | Vault name |
Creates or updates a secret in 1Password. Requires write_items permission on the service account.
| Parameter | Required | Default | Description |
|---|---|---|---|
item |
Yes | — | Item title (e.g. "New API Key") |
value |
Yes | — | The secret value to store |
vault |
No | Config default | Vault name |
field |
No | "api key" |
Field name |
All tools are registered as optional and must be allowlisted in agent config to use.
The plugin registers a startup service that walks openclaw.json and resolves any string matching op://vault/item/field to its real value via the 1Password SDK. Resolution happens in memory — the file on disk keeps the op:// reference.
The resolveSecretRefs utility is also exported for programmatic use:
import { resolveSecretRefs } from "@openclaw/1password";
const resolved = await resolveSecretRefs({
apiKey: "op://Agent Secrets/OpenAI API/api key",
other: "not a secret, passed through",
});
// resolved.apiKey === "sk-proj-..."
// resolved.other === "not a secret, passed through"# Verify 1Password connectivity
openclaw op-secrets test
# Read a secret (shows redacted preview, safe for terminals)
openclaw op-secrets read "OpenAI API"
openclaw op-secrets read "Anthropic Auth Token" --vault "Agent Secrets"
openclaw op-secrets read "DB Password" --field "password"
# Dry-run: show which op:// refs would be resolved in a JSON file
openclaw op-secrets resolve ~/.openclaw/openclaw.jsonTo enable write operations (op_write_secret), the service account needs write_items permission:
op service-account create "OpenClaw Agent" \
--expires-in 0 \
--vault "Agent Secrets:read_items,write_items"Then update the token at ~/.openclaw/secrets/op-sa-token.
Plugin config in openclaw.json under plugins.entries.op-secrets.config:
| Key | Type | Default | Description |
|---|---|---|---|
defaultVault |
string | "Agent Secrets" |
Default vault for tool calls |
tokenPath |
string | ~/.openclaw/secrets/op-sa-token |
Path to service account token file |
The token path can also be set via the OP_SA_TOKEN_PATH environment variable.
- On plugin load, the resolver service walks
openclaw.jsonand resolvesop://references in memory - On first secret request, the plugin reads the service account token from disk
- It creates a 1Password SDK client (cached for the session lifetime)
- Secrets are resolved via
client.secrets.resolve("op://Vault/Item/field") - The SDK talks directly to 1Password's servers over HTTPS
- Secret values exist in memory only — never written to disk or logs
The service account token does not expire (unless you set --expires-in). It survives reboots. No desktop app needed.
- Service account tokens grant access only to specific vaults you configure
- The token file is
chmod 600— only the owning user can read it - Secret values are never logged, cached, or written to disk by the plugin
- The SKILL.md file instructs agents to never echo or log returned secrets
- If a secret is exposed, rotate it in 1Password — the plugin always reads the current value
op://references in config files are safe to commit to version control
"Token file not found" — Make sure ~/.openclaw/secrets/op-sa-token exists and contains your ops_... token.
"Vault not found" — Service accounts can only access custom vaults, not built-in ones (Shared, Employee, Private). Create a custom vault and grant access.
"403 Forbidden" when creating service account — Enable "Developer" / "Secrets Automation" in your 1Password admin console under Settings.
"Granting a service account access to the Team/Shared vault is not supported" — Use a custom vault name, not the built-in "Shared" vault.
Write operations fail — The service account needs write_items permission. See Write Support.
This section covers how to use 1Password secrets in new OpenClaw plugins, MCP servers, standalone scripts, or any Node.js project that needs secrets from the same vault.
Shell out to the op CLI with the service account token. This is what lesa-bridge uses.
import { execSync } from "node:child_process";
import { readFileSync } from "node:fs";
const saToken = readFileSync(
`${process.env.HOME}/.openclaw/secrets/op-sa-token`,
"utf-8"
).trim();
const apiKey = execSync(
`op read "op://Agent Secrets/OpenAI API/api key"`,
{
env: { ...process.env, OP_SERVICE_ACCOUNT_TOKEN: saToken },
encoding: "utf-8",
timeout: 10000,
}
).trim();Pros: No native dependencies, works in any language, one-liner per secret.
Cons: Requires op CLI installed (brew install 1password-cli), subprocess overhead.
import { createClient } from "@1password/sdk";
import { readFileSync } from "node:fs";
const token = readFileSync(
`${process.env.HOME}/.openclaw/secrets/op-sa-token`,
"utf-8"
).trim();
const client = await createClient({
auth: token,
integrationName: "MyProject",
integrationVersion: "0.1.0",
});
// Resolve a secret
const apiKey = await client.secrets.resolve(
"op://Agent Secrets/OpenAI API/api key"
);
// List vaults
const vaults = await client.vaults.list();
// List items in a vault
const items = await client.items.list(vaultId);Pros: No subprocess, faster, full API (list/create/update items).
Cons: Native dependency (@1password/sdk), larger install footprint.
If your code runs as an OpenClaw agent or plugin, call the existing op_read_secret tool:
op_read_secret({ item: "OpenAI API", vault: "Agent Secrets", field: "api key" })
No code needed — the tool is already registered by this plugin.
Resolve a key at startup, cache it:
let cachedKey: string | null = null;
function getApiKey(): string {
if (cachedKey) return cachedKey;
cachedKey = execSync(`op read "op://Agent Secrets/MyService/api key"`, {
env: { ...process.env, OP_SERVICE_ACCOUNT_TOKEN: saToken },
encoding: "utf-8",
timeout: 10000,
}).trim();
return cachedKey;
}Set as environment variable (for libraries that read process.env):
process.env.OPENAI_API_KEY = await client.secrets.resolve(
"op://Agent Secrets/OpenAI API/api key"
);
// Now any library that checks process.env.OPENAI_API_KEY will find itStore a new secret (requires write_items permission):
await client.items.create({
vaultId: "vault-uuid",
title: "New API Key",
category: ItemCategory.ApiCredential,
fields: [
{
id: "api_key",
title: "api key",
value: "sk-proj-...",
fieldType: ItemFieldType.Concealed,
},
],
});- Use your 1Password account with service account support (Teams, Business, or Enterprise).
- Never hardcode secrets. Use
op://references in config, resolve at runtime. - Never log secrets. Use the
redact()helper for debug output. - Cache the client. Creating a 1Password SDK client is expensive (~200ms). Create once, reuse.
- Cache resolved values. Secrets don't change mid-session. Resolve once at startup.
- Service account token location: Always
~/.openclaw/secrets/op-sa-token. Don't invent new paths. - Vault name:
Agent Secretsis the shared vault. Add items there unless you need isolation. - Item naming: Use descriptive titles like
"OpenAI API","GitHub Token". Only alphanumeric,_,.,-characters. - SA permissions are immutable. If you need
write_itemsand the current SA only hasread_items, you must create a new service account.
# Via op CLI
OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) \
op item create --category "API Credential" --title "My New Service" \
--vault "Agent Secrets" 'api key=YOUR_KEY_HERE'
# Verify
OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) \
op read "op://Agent Secrets/My New Service/api key"| Project | How it uses 1Password |
|---|---|
openclaw-1password (this) |
JS SDK, startup resolver, agent tools |
lesa-bridge |
op CLI, resolves OpenAI key at MCP server startup |
openclaw-context-embeddings |
Reads process.env.OPENAI_API_KEY (set by this plugin at boot) |
openclaw-tavily |
JS SDK, resolves Tavily API key at startup, sets TAVILY_API_KEY env var |
MIT