openclaw-plugin-onepassword
v0.1.1
Published
Native OpenClaw plugin that resolves 1Password secrets in-process (no exec sandbox), plus agent tools for vault/item CRUD.
Maintainers
Readme
openclaw-plugin-onepassword
A native OpenClaw plugin that resolves 1Password secrets in-process inside the Gateway — no op CLI, no child process, no exec sandbox — and writes them into OpenClaw's shared secret store. It also exposes optional 1Password vault/item agent tools.
Why this exists. OpenClaw v2026.8.1 introduced a security sandbox for
execsecret providers that blocks filesystem writes and network access during provider execution. That breaks the common pattern of usingop readas an exec provider (you'll see errors likesh: cannot open /tmp/op_out.txtand blocked network calls). This plugin runs inside the Gateway process, where the sandbox does not apply, and uses the official@1password/sdkover HTTPS.
Contents
- How it works
- Requirements
- Install
- Quick start (store sync — recommended)
- Configuration reference
- Agent tools (optional)
- Gateway methods
- Refreshing secrets at runtime
- Exec resolver mode (advanced)
- Security notes
- Troubleshooting
- Development
- Publishing
- License
How it works
This plugin can feed secrets to OpenClaw three ways. The in-process modes (store sync and file sync) run inside the Gateway and reliably bypass the exec sandbox; pick based on what the consuming channel/provider accepts.
| Mode | Runs where | Bypasses exec sandbox? | SecretRef you write | Vault/item paths live in |
| ---------------------------- | ---------------------- | -------------------------------- | -------------------------------------- | ------------------------------ |
| Store sync (default) | In-process (Gateway) | ✅ Yes | source: "store" | plugin config.secrets map |
| File sync | In-process (Gateway) | ✅ Yes | source: "file" | plugin config.syncToFile map |
| Exec resolver (advanced) | Sandboxed child node | ⚠️ Only with egress allowlisting | source: "exec" + pluginIntegration | the SecretRef.id (op://…) |
Which in-process mode? Prefer store sync (
source: "store"). But some bundled channel plugins — notably Slack — rejectsource: "store"at config-validation time and accept onlyenv,file, orexec. For those, use file sync (source: "file"). The two are independent and can run together (e.g. store sync for OpenAI, file sync for Slack).
Store sync, in one picture:
Gateway start / onepassword.sync
│
▼
read OP_SERVICE_ACCOUNT_TOKEN (env)
│
▼
@1password/sdk ──HTTPS──▶ 1Password API
│
▼
secrets.store.set { name, value } (in-process Gateway RPC)
│
▼
OpenClaw shared store ◀── resolved by SecretRefs with source:"store"The plugin never writes secrets to openclaw.json, environment variables, or disk of its own. Values live only in OpenClaw's store (SQLite, 0600/0700 permissions, team scope).
Requirements
- OpenClaw
>= 2026.8.0(Gateway runs on Node>= 22.22.3). - A 1Password service account token — see 1Password service accounts. The service account must have access to the vaults/items you reference.
Install
From npm (recommended):
openclaw plugins install openclaw-plugin-onepasswordOr from a local checkout:
openclaw plugins install ./openclaw-plugin-onepasswordThen provide the service account token to the Gateway process via the environment variable (default OP_SERVICE_ACCOUNT_TOKEN). Keep it out of openclaw.json, docker-compose files, and shell history — use your process manager's secret mechanism (systemd LoadCredential, Docker/Kubernetes secrets, etc.).
export OP_SERVICE_ACCOUNT_TOKEN="ops_..."Quick start (store sync — recommended)
- Enable the plugin and map store keys to 1Password references in
openclaw.json:
{
"plugins": {
"entries": {
"onepassword": {
"enabled": true,
"config": {
"serviceAccountTokenEnvVar": "OP_SERVICE_ACCOUNT_TOKEN",
"secrets": {
"SLACK_BOT_TOKEN": "op://MyVault/SlackBot/bot_token",
"SLACK_APP_TOKEN": "op://MyVault/SlackBot/app_token",
"OPENAI_API_KEY": "op://MyVault/OpenAI/credential"
}
}
}
}
}
}Store keys must match ^[A-Z][A-Z0-9_]{0,127}$ (OpenClaw store-id grammar). Values are standard 1Password secret references: op://Vault/Item[/Section]/Field.
- Reference those store keys anywhere OpenClaw accepts a
SecretRef, usingsource: "store":
{
"channels": {
"slack": {
"accounts": {
"myworkspace": {
"botToken": { "source": "store", "id": "SLACK_BOT_TOKEN" },
"appToken": { "source": "store", "id": "SLACK_APP_TOKEN" }
}
}
}
},
"agents": {
"defaults": {
"model": {
"providers": {
"openai": {
"apiKey": { "source": "store", "id": "OPENAI_API_KEY" }
}
}
}
}
}
}- Start the Gateway. On startup the plugin fetches each reference from 1Password and writes it into the store; the referencing channels/providers then initialize with resolved credentials. Because store values persist, subsequent restarts are covered even before the first sync completes.
See examples/ for complete config files.
File sync mode (for channels that reject source: "store")
Some bundled channel plugins — Slack in particular — reject source: "store" SecretRefs at config-validation time and accept only env, file, or exec. Because this plugin runs in-process, it can resolve op:// references and write them to a JSON file that OpenClaw's built-in source: "file" provider reads — which those channels accept.
- Configure
syncToFilein the plugin config (independent ofsecrets; both can be active):
{
"plugins": {
"entries": {
"onepassword": {
"enabled": true,
"config": {
"serviceAccountTokenEnvVar": "OP_SERVICE_ACCOUNT_TOKEN",
"syncToFile": {
"path": "/home/node/.openclaw/op-secrets.json",
"mode": "json",
"secrets": {
"SLACK_BOT_TOKEN_A": "op://MyVault/SlackBot/bot_token",
"SLACK_APP_TOKEN_A": "op://MyVault/SlackBot/app_token"
}
}
}
}
}
}
}- Declare a
fileprovider and reference the keys withsource: "file"(theidis a JSON pointer,/KEY):
{
"secrets": {
"providers": {
"op-file": {
"source": "file",
"path": "/home/node/.openclaw/op-secrets.json",
"mode": "json"
}
}
},
"channels": {
"slack": {
"accounts": {
"myworkspace": {
"botToken": { "source": "file", "provider": "op-file", "id": "/SLACK_BOT_TOKEN_A" },
"appToken": { "source": "file", "provider": "op-file", "id": "/SLACK_APP_TOKEN_A" }
}
}
}
}
}The file is written atomically (temp file + rename) with 0600 permissions. Keys that fail to resolve keep their last-known-good value from the existing file, so a transient 1Password failure doesn't break a running channel. See examples/openclaw.file-mode.json.
⚠️ The sync file contains plaintext secret values. It lives in the OpenClaw data volume (same trust boundary as the SQLite store). Keep its path inside that volume and outside any path an agent can read — otherwise an agent with file tools could exfiltrate it. Add the file (or its directory) to your agent file-access denylist (e.g.
tools.exec.denyPathsor the equivalent for your sandbox).
Configuration reference
All keys live under plugins.entries.onepassword.config.
| Key | Type | Default | Description |
| --------------------------- | ------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| serviceAccountTokenEnvVar | string | "OP_SERVICE_ACCOUNT_TOKEN" | Name of the environment variable holding the service account token. |
| secrets | object | {} | Map of store key → op://… reference (store sync). Keys must match ^[A-Z][A-Z0-9_]{0,127}$. |
| syncToFile | object | – | File sync block: { path, mode: "json", secrets }. See File sync mode. |
| syncOnStartup | boolean | true | Fetch configured secrets from 1Password and sync them (store + file) at Gateway startup. |
| failFastOnStartup | boolean | false | If true, a startup sync failure (resolve, store write, or file write) throws and prevents startup. If false, log and fall back to last-known-good values. |
| integrationName | string | "openclaw-plugin-onepassword" | Integration name reported to 1Password audit logs. |
| requestTimeoutMs | number | 15000 | Per-operation timeout for 1Password SDK calls. |
| tools.enabled | boolean | false | Register the read-only 1Password agent tools. |
| tools.allowWrite | boolean | false | Also register create/update/delete tools. Requires tools.enabled. |
The plugin hardcodes no vault names, item paths, or field names. The only 1Password-specific configuration is the env var name and the secrets / syncToFile.secrets maps you provide.
Agent tools (optional)
Set tools.enabled: true to expose in-process tools to the agent. Read tools redact concealed field values by default.
| Tool | Requires allowWrite | Description |
| ----------------------- | --------------------- | -------------------------------------------------------------------------- |
| 1password_list_vaults | | List vaults accessible to the service account. |
| 1password_list_items | | List item overviews in a vault. |
| 1password_get_item | | Get a full item (concealed fields redacted unless includeSecrets: true). |
| 1password_read_field | | Resolve a single op://… reference to its value. |
| 1password_create_item | ✅ | Create a new item. |
| 1password_update_item | ✅ | Update an existing item. |
| 1password_delete_item | ✅ | Delete an item. |
{
"plugins": {
"entries": {
"onepassword": {
"enabled": true,
"config": { "tools": { "enabled": true, "allowWrite": false } }
}
}
}
}Gateway methods
Both require the operator.admin scope.
onepassword.sync— re-fetch every configured secret from 1Password and write it to the store and/or the sync file. Returns{ written, fileWritten, total, resolveErrors, storeErrors, fileErrors }.onepassword.status— non-secret health/config summary:{ version, serviceAccountTokenEnvVar, tokenPresent, syncOnStartup, managedStoreKeys, syncToFileEnabled, managedFileKeys, filePath, toolsEnabled, toolsWriteEnabled }.
Refreshing secrets at runtime
- Rotate a value in 1Password, then call
onepassword.sync(or restart the Gateway). The plugin re-fetches and writes fresh values;secrets.store.settriggers a live runtime refresh so dependent channels/providers pick up the new value without a full restart. openclaw secrets reloadre-reads the store; runonepassword.syncfirst if you need the store repopulated from 1Password.
Prefer native
openclaw secrets reloadto re-fetch directly from 1Password? Use the exec resolver mode, which OpenClaw re-invokes on reload — at the cost of requiring egress allowlisting.
Exec resolver mode (advanced)
The plugin also declares a secretProviderIntegrations entry so it can act as a plugin-managed exec secret provider using standard op:// ids:
{
"secrets": {
"providers": {
"op": {
"source": "exec",
"pluginIntegration": { "pluginId": "onepassword", "integrationId": "op" }
}
}
},
"channels": {
"slack": {
"accounts": {
"myworkspace": {
"botToken": {
"source": "exec",
"provider": "op",
"id": "op://MyVault/SlackBot/bot_token"
}
}
}
}
}
}Caveat: this runs OpenClaw's resolver as a sandboxed child node process (command: "${node}"). Under the v2026.8.1 sandbox its network is blocked, so it can only reach the 1Password API if you allowlist egress for secret resolution:
{
"secrets": {
"egressProxy": {
"enabled": true,
"allowedHosts": ["my.1password.com", "my.1password.eu", "my.1password.ca"]
}
}
}Use the host that matches your 1Password account region. If your environment cannot allow this egress, use the store sync mode instead. The exec resolver reads the token from OP_SERVICE_ACCOUNT_TOKEN (override the env var name with OP_RESOLVER_TOKEN_ENV_VAR).
Security notes
- The token is the crown jewel. Anyone with the service account token has the service account's access. Scope the service account to the minimum vaults required, and inject the token via your platform's secret mechanism — never commit it.
- No plaintext secrets in config. The plugin only reads an env var name and
op://references; resolved values live only in OpenClaw's store (store sync) or the sync file (file sync). - The file sync output is plaintext. If you use
syncToFile, keep its path inside the OpenClaw data volume and out of any agent-readable path; add it to your agent file-access denylist. See File sync mode. - Plugin code runs in your Gateway process with full Gateway privileges (this is true of every OpenClaw plugin). Review the source before installing.
- Write tools are opt-in (
tools.allowWrite) and concealed fields are redacted by read tools unlessincludeSecrets: true. - Report vulnerabilities per SECURITY.md.
Troubleshooting
| Symptom | Likely cause / fix |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ... token not found at startup | The env var named by serviceAccountTokenEnvVar is unset on the Gateway process. |
| SECRETS_PROVIDER_DEGRADED for a store ref | The store key hasn't been populated yet — run onepassword.sync, or check the startup logs for resolve errors. |
| source: must be equal to constant (allowed: "env"/"file"/"exec") on a channel | That channel (e.g. Slack) rejects source: "store". Use File sync mode with source: "file". |
| source: "file" ref resolves empty | The sync file wasn't written yet, or the id JSON pointer is wrong (must be /KEY). Check onepassword.status → filePath and startup logs. |
| Resolve error NOT_FOUND | The op:// reference is wrong or the service account can't access that vault/item/field. |
| Exec resolver returns nothing | Sandbox is blocking network — add your 1Password host to secrets.egressProxy.allowedHosts, or switch to store sync. |
Development
npm install --ignore-scripts # openclaw's preinstall gate is skipped here
npm run build # tsc -> dist/
npm run typecheck
npm run lint
npm test
--ignore-scriptsis used because theopenclawdev dependency runs a Node-version preinstall check; the plugin itself only needs its type definitions to build and test.
Releasing
Releases are automated on push to main. The Release workflow publishes to
npm (with provenance)
and creates a GitHub release only when the version in package.json is not yet
on npm — ordinary commits are a no-op.
To cut a release:
npm run release:prepare -- <x.y.z> # bumps the 3 version files + rolls CHANGELOG
# edit CHANGELOG.md for the new section, then:
npm run verify
git commit -am "chore(release): v<x.y.z>" && git push origin mainThe workflow publishes the package and tags v<x.y.z>. It requires an NPM_TOKEN
repository secret (an npm automation token). See CONTRIBUTING.md → Releasing
and AGENTS.md for details and the semver policy.
