2026-02-24 16:26:59 -06:00
---
2026-02-25 20:29:39 -06:00
summary: "Secrets management: SecretRef contract, runtime snapshot behavior, and safe one-way scrubbing"
2026-02-24 16:26:59 -06:00
read_when:
2026-03-02 20:58:20 -06:00
- Configuring SecretRefs for provider credentials and `auth-profiles.json` refs
- Operating secrets reload, audit, configure, and apply safely in production
- Understanding startup fail-fast, inactive-surface filtering, and last-known-good behavior
2026-02-24 16:26:59 -06:00
title: "Secrets Management"
---
# Secrets management
2026-03-02 20:58:20 -06:00
OpenClaw supports additive SecretRefs so supported credentials do not need to be stored as plaintext in configuration.
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
Plaintext still works. SecretRefs are opt-in per credential.
2026-02-24 16:26:59 -06:00
## Goals and runtime model
Secrets are resolved into an in-memory runtime snapshot.
2026-02-25 17:58:10 -06:00
- Resolution is eager during activation, not lazy on request paths.
2026-03-02 20:58:20 -06:00
- Startup fails fast when an effectively active SecretRef cannot be resolved.
- Reload uses atomic swap: full success, or keep the last-known-good snapshot.
- Runtime requests read from the active in-memory snapshot only.
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
This keeps secret-provider outages off hot request paths.
## Active-surface filtering
SecretRefs are validated only on effectively active surfaces.
- Enabled surfaces: unresolved refs block startup/reload.
- Inactive surfaces: unresolved refs do not block startup/reload.
- Inactive refs emit non-fatal diagnostics with code `SECRETS_REF_IGNORED_INACTIVE_SURFACE` .
Examples of inactive surfaces:
- Disabled channel/account entries.
- Top-level channel credentials that no enabled account inherits.
- Disabled tool/feature surfaces.
- Web search provider-specific keys that are not selected by `tools.web.search.provider` .
In auto mode (provider unset), provider-specific keys are also active for provider auto-detection.
- `gateway.remote.token` / `gateway.remote.password` SecretRefs are active (when `gateway.remote.enabled` is not `false` ) if one of these is true:
- `gateway.mode=remote`
- `gateway.remote.url` is configured
- `gateway.tailscale.mode` is `serve` or `funnel`
In local mode without those remote surfaces:
- `gateway.remote.token` is active when token auth can win and no env/auth token is configured.
- `gateway.remote.password` is active only when password auth can win and no env/auth password is configured.
2026-03-05 12:53:56 -06:00
- `gateway.auth.token` SecretRef is inactive for startup auth resolution when `OPENCLAW_GATEWAY_TOKEN` (or `CLAWDBOT_GATEWAY_TOKEN` ) is set, because env token input wins for that runtime.
2026-03-02 20:58:20 -06:00
## Gateway auth surface diagnostics
2026-03-05 12:53:56 -06:00
When a SecretRef is configured on `gateway.auth.token` , `gateway.auth.password` ,
`gateway.remote.token` , or `gateway.remote.password` , gateway startup/reload logs the
surface state explicitly:
2026-03-02 20:58:20 -06:00
- `active` : the SecretRef is part of the effective auth surface and must resolve.
- `inactive` : the SecretRef is ignored for this runtime because another auth surface wins, or
because remote auth is disabled/not active.
These entries are logged with `SECRETS_GATEWAY_AUTH_SURFACE` and include the reason used by the
active-surface policy, so you can see why a credential was treated as active or inactive.
2026-02-24 16:26:59 -06:00
2026-02-24 22:26:33 -06:00
## Onboarding reference preflight
2026-03-02 20:58:20 -06:00
When onboarding runs in interactive mode and you choose SecretRef storage, OpenClaw runs preflight validation before saving:
2026-02-24 22:26:33 -06:00
- Env refs: validates env var name and confirms a non-empty value is visible during onboarding.
2026-03-02 20:58:20 -06:00
- Provider refs (`file` or `exec` ): validates provider selection, resolves `id` , and checks resolved value type.
2026-03-05 12:53:56 -06:00
- Quickstart reuse path: when `gateway.auth.token` is already a SecretRef, onboarding resolves it before probe/dashboard bootstrap (for `env` , `file` , and `exec` refs) using the same fail-fast gate.
2026-02-24 22:26:33 -06:00
2026-02-25 17:58:10 -06:00
If validation fails, onboarding shows the error and lets you retry.
2026-02-24 22:26:33 -06:00
2026-02-24 16:26:59 -06:00
## SecretRef contract
Use one object shape everywhere:
```json5
2026-02-25 17:58:10 -06:00
{ source: "env" | "file" | "exec", provider: "default", id: "..." }
2026-02-24 16:26:59 -06:00
```
### `source: "env"`
```json5
2026-02-25 17:58:10 -06:00
{ source: "env", provider: "default", id: "OPENAI_API_KEY" }
2026-02-24 16:26:59 -06:00
```
Validation:
2026-02-25 17:58:10 -06:00
- `provider` must match `^[a-z][a-z0-9_-]{0,63}$`
2026-02-24 16:26:59 -06:00
- `id` must match `^[A-Z][A-Z0-9_]{0,127}$`
### `source: "file"`
```json5
2026-02-25 17:58:10 -06:00
{ source: "file", provider: "filemain", id: "/providers/openai/apiKey" }
2026-02-24 16:26:59 -06:00
```
Validation:
2026-02-25 17:58:10 -06:00
- `provider` must match `^[a-z][a-z0-9_-]{0,63}$`
2026-02-24 16:26:59 -06:00
- `id` must be an absolute JSON pointer (`/...` )
2026-02-25 17:58:10 -06:00
- RFC6901 escaping in segments: `~` => `~0` , `/` => `~1`
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
### `source: "exec"`
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
```json5
{ source: "exec", provider: "vault", id: "providers/openai/apiKey" }
```
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
Validation:
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
- `provider` must match `^[a-z][a-z0-9_-]{0,63}$`
- `id` must match `^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$`
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
## Provider config
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
Define providers under `secrets.providers` :
2026-02-24 16:26:59 -06:00
```json5
{
secrets: {
2026-02-25 17:58:10 -06:00
providers: {
default: { source: "env" },
filemain: {
source: "file",
path: "~/.openclaw/secrets.json",
2026-02-25 23:17:31 -06:00
mode: "json", // or "singleValue"
2026-02-24 16:26:59 -06:00
},
2026-02-25 17:58:10 -06:00
vault: {
source: "exec",
command: "/usr/local/bin/openclaw-vault-resolver",
args: ["--profile", "prod"],
passEnv: ["PATH", "VAULT_ADDR"],
jsonOnly: true,
},
},
defaults: {
env: "default",
file: "filemain",
exec: "vault",
},
resolution: {
maxProviderConcurrency: 4,
maxRefsPerProvider: 512,
maxBatchBytes: 262144,
2026-02-24 16:26:59 -06:00
},
},
}
```
2026-02-25 17:58:10 -06:00
### Env provider
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
- Optional allowlist via `allowlist` .
- Missing/empty env values fail resolution.
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
### File provider
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
- Reads local file from `path` .
2026-02-25 23:17:31 -06:00
- `mode: "json"` expects JSON object payload and resolves `id` as pointer.
- `mode: "singleValue"` expects ref id `"value"` and returns file contents.
2026-02-25 17:58:10 -06:00
- Path must pass ownership/permission checks.
2026-03-02 20:58:20 -06:00
- Windows fail-closed note: if ACL verification is unavailable for a path, resolution fails. For trusted paths only, set `allowInsecurePath: true` on that provider to bypass path security checks.
2026-02-24 19:34:29 -06:00
2026-02-25 17:58:10 -06:00
### Exec provider
2026-02-24 19:34:29 -06:00
2026-02-25 17:58:10 -06:00
- Runs configured absolute binary path, no shell.
2026-02-25 23:25:23 -06:00
- By default, `command` must point to a regular file (not a symlink).
- Set `allowSymlinkCommand: true` to allow symlink command paths (for example Homebrew shims). OpenClaw validates the resolved target path.
2026-03-02 20:58:20 -06:00
- Pair `allowSymlinkCommand` with `trustedDirs` for package-manager paths (for example `["/opt/homebrew"]` ).
2026-02-25 17:58:10 -06:00
- Supports timeout, no-output timeout, output byte limits, env allowlist, and trusted dirs.
2026-03-02 20:58:20 -06:00
- Windows fail-closed note: if ACL verification is unavailable for the command path, resolution fails. For trusted paths only, set `allowInsecurePath: true` on that provider to bypass path security checks.
Request payload (stdin):
2026-02-25 17:58:10 -06:00
```json
{ "protocolVersion": 1, "provider": "vault", "ids": ["providers/openai/apiKey"] }
```
2026-03-02 20:58:20 -06:00
Response payload (stdout):
2026-02-25 17:58:10 -06:00
2026-03-06 19:35:26 -05:00
```jsonc
{ "protocolVersion": 1, "values": { "providers/openai/apiKey": "< openai-api-key > " } } // pragma: allowlist secret
2026-02-25 17:58:10 -06:00
```
Optional per-id errors:
```json
{
"protocolVersion": 1,
"values": {},
"errors": { "providers/openai/apiKey": { "message": "not found" } }
}
```
2026-02-24 16:26:59 -06:00
2026-02-25 23:39:33 -06:00
## Exec integration examples
2026-02-25 23:17:31 -06:00
2026-02-25 23:39:33 -06:00
### 1Password CLI
2026-02-25 23:25:23 -06:00
```json5
{
secrets: {
providers: {
onepassword_openai: {
source: "exec",
command: "/opt/homebrew/bin/op",
2026-02-25 23:39:33 -06:00
allowSymlinkCommand: true, // required for Homebrew symlinked binaries
2026-02-25 23:25:23 -06:00
trustedDirs: ["/opt/homebrew"],
args: ["read", "op://Personal/OpenClaw QA API Key/password"],
passEnv: ["HOME"],
jsonOnly: false,
},
},
},
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
models: [{ id: "gpt-5", name: "gpt-5" }],
apiKey: { source: "exec", provider: "onepassword_openai", id: "value" },
},
},
},
}
```
2026-02-25 23:17:31 -06:00
### HashiCorp Vault CLI
```json5
{
secrets: {
providers: {
vault_openai: {
source: "exec",
2026-02-25 23:39:33 -06:00
command: "/opt/homebrew/bin/vault",
allowSymlinkCommand: true, // required for Homebrew symlinked binaries
trustedDirs: ["/opt/homebrew"],
args: ["kv", "get", "-field=OPENAI_API_KEY", "secret/openclaw"],
2026-02-25 23:17:31 -06:00
passEnv: ["VAULT_ADDR", "VAULT_TOKEN"],
jsonOnly: false,
},
},
},
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
2026-02-25 23:25:23 -06:00
models: [{ id: "gpt-5", name: "gpt-5" }],
2026-02-25 23:17:31 -06:00
apiKey: { source: "exec", provider: "vault_openai", id: "value" },
},
},
},
}
```
### `sops`
```json5
{
secrets: {
providers: {
sops_openai: {
source: "exec",
2026-02-25 23:39:33 -06:00
command: "/opt/homebrew/bin/sops",
allowSymlinkCommand: true, // required for Homebrew symlinked binaries
trustedDirs: ["/opt/homebrew"],
args: ["-d", "--extract", '["providers"]["openai"]["apiKey"]', "/path/to/secrets.enc.json"],
2026-02-25 23:17:31 -06:00
passEnv: ["SOPS_AGE_KEY_FILE"],
jsonOnly: false,
},
},
},
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
2026-02-25 23:25:23 -06:00
models: [{ id: "gpt-5", name: "gpt-5" }],
2026-02-25 23:17:31 -06:00
apiKey: { source: "exec", provider: "sops_openai", id: "value" },
},
},
},
}
```
2026-03-02 20:58:20 -06:00
## Supported credential surface
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
Canonical supported and unsupported credentials are listed in:
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
- [SecretRef Credential Surface ](/reference/secretref-credential-surface )
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
Runtime-minted or rotating credentials and OAuth refresh material are intentionally excluded from read-only SecretRef resolution.
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
## Required behavior and precedence
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
- Field without a ref: unchanged.
- Field with a ref: required on active surfaces during activation.
- If both plaintext and ref are present, ref takes precedence on supported precedence paths.
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
Warning and audit signals:
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
- `SECRETS_REF_OVERRIDES_PLAINTEXT` (runtime warning)
- `REF_SHADOWED` (audit finding when `auth-profiles.json` credentials take precedence over `openclaw.json` refs)
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
Google Chat compatibility behavior:
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
- `serviceAccountRef` takes precedence over plaintext `serviceAccount` .
- Plaintext value is ignored when sibling ref is set.
2026-02-24 16:26:59 -06:00
## Activation triggers
2026-03-02 20:58:20 -06:00
Secret activation runs on:
2026-02-24 16:26:59 -06:00
2026-02-25 17:58:10 -06:00
- Startup (preflight plus final activation)
2026-02-24 16:26:59 -06:00
- Config reload hot-apply path
- Config reload restart-check path
- Manual reload via `secrets.reload`
Activation contract:
2026-02-25 17:58:10 -06:00
- Success swaps the snapshot atomically.
- Startup failure aborts gateway startup.
2026-03-02 20:58:20 -06:00
- Runtime reload failure keeps the last-known-good snapshot.
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
## Degraded and recovered signals
2026-02-24 16:26:59 -06:00
When reload-time activation fails after a healthy state, OpenClaw enters degraded secrets state.
One-shot system event and log codes:
- `SECRETS_RELOADER_DEGRADED`
- `SECRETS_RELOADER_RECOVERED`
Behavior:
- Degraded: runtime keeps last-known-good snapshot.
2026-03-02 20:58:20 -06:00
- Recovered: emitted once after the next successful activation.
2026-02-25 17:58:10 -06:00
- Repeated failures while already degraded log warnings but do not spam events.
2026-03-02 20:58:20 -06:00
- Startup fail-fast does not emit degraded events because runtime never became active.
## Command-path resolution
2026-03-05 23:07:13 -06:00
Command paths can opt into supported SecretRef resolution via gateway snapshot RPC.
There are two broad behaviors:
- Strict command paths (for example `openclaw memory` remote-memory paths and `openclaw qr --remote` ) read from the active snapshot and fail fast when a required SecretRef is unavailable.
- Read-only command paths (for example `openclaw status` , `openclaw status --all` , `openclaw channels status` , `openclaw channels resolve` , and read-only doctor/config repair flows) also prefer the active snapshot, but degrade instead of aborting when a targeted SecretRef is unavailable in that command path.
Read-only behavior:
- When the gateway is running, these commands read from the active snapshot first.
- If gateway resolution is incomplete or the gateway is unavailable, they attempt targeted local fallback for the specific command surface.
- If a targeted SecretRef is still unavailable, the command continues with degraded read-only output and explicit diagnostics such as “configured but unavailable in this command path”.
- This degraded behavior is command-local only. It does not weaken runtime startup, reload, or send/auth paths.
Other notes:
2026-03-02 20:58:20 -06:00
- Snapshot refresh after backend secret rotation is handled by `openclaw secrets reload` .
- Gateway RPC method used by these command paths: `secrets.resolve` .
2026-02-24 16:26:59 -06:00
2026-02-25 20:29:39 -06:00
## Audit and configure workflow
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
Default operator flow:
2026-02-24 16:26:59 -06:00
```bash
2026-02-25 20:29:39 -06:00
openclaw secrets audit --check
openclaw secrets configure
openclaw secrets audit --check
2026-02-24 16:26:59 -06:00
```
2026-02-25 20:29:39 -06:00
### `secrets audit`
2026-02-24 16:26:59 -06:00
2026-02-25 20:29:39 -06:00
Findings include:
2026-02-24 16:26:59 -06:00
2026-02-25 20:29:39 -06:00
- plaintext values at rest (`openclaw.json` , `auth-profiles.json` , `.env` )
- unresolved refs
2026-03-02 20:58:20 -06:00
- precedence shadowing (`auth-profiles.json` taking priority over `openclaw.json` refs)
- legacy residues (`auth.json` , OAuth reminders)
2026-02-24 16:26:59 -06:00
2026-02-25 20:29:39 -06:00
### `secrets configure`
2026-02-24 16:26:59 -06:00
2026-02-25 20:29:39 -06:00
Interactive helper that:
2026-02-24 16:26:59 -06:00
2026-02-25 23:17:31 -06:00
- configures `secrets.providers` first (`env` /`file` /`exec` , add/edit/remove)
2026-03-02 20:58:20 -06:00
- lets you select supported secret-bearing fields in `openclaw.json` plus `auth-profiles.json` for one agent scope
- can create a new `auth-profiles.json` mapping directly in the target picker
2026-02-25 20:29:39 -06:00
- captures SecretRef details (`source` , `provider` , `id` )
- runs preflight resolution
- can apply immediately
2026-02-25 17:58:10 -06:00
2026-02-25 23:17:31 -06:00
Helpful modes:
- `openclaw secrets configure --providers-only`
- `openclaw secrets configure --skip-provider-setup`
2026-03-02 20:58:20 -06:00
- `openclaw secrets configure --agent <id>`
2026-02-25 23:17:31 -06:00
2026-03-02 20:58:20 -06:00
`configure` apply defaults:
2026-02-25 17:58:10 -06:00
2026-03-02 20:58:20 -06:00
- scrub matching static credentials from `auth-profiles.json` for targeted providers
2026-02-25 20:29:39 -06:00
- scrub legacy static `api_key` entries from `auth.json`
- scrub matching known secret lines from `<config-dir>/.env`
### `secrets apply`
Apply a saved plan:
```bash
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
```
2026-02-24 19:34:29 -06:00
2026-02-26 14:32:04 +01:00
For strict target/path contract details and exact rejection rules, see:
- [Secrets Apply Plan Contract ](/gateway/secrets-plan-contract )
2026-02-25 20:29:39 -06:00
## One-way safety policy
2026-02-24 19:34:29 -06:00
2026-03-02 20:58:20 -06:00
OpenClaw intentionally does not write rollback backups containing historical plaintext secret values.
2026-02-24 16:26:59 -06:00
2026-02-25 20:29:39 -06:00
Safety model:
2026-02-24 16:26:59 -06:00
2026-02-25 20:29:39 -06:00
- preflight must succeed before write mode
- runtime activation is validated before commit
2026-03-02 20:58:20 -06:00
- apply updates files using atomic file replacement and best-effort restore on failure
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
## Legacy auth compatibility notes
2026-02-24 16:26:59 -06:00
2026-03-02 20:58:20 -06:00
For static credentials, runtime no longer depends on plaintext legacy auth storage.
2026-02-24 16:26:59 -06:00
- Runtime credential source is the resolved in-memory snapshot.
2026-03-02 20:58:20 -06:00
- Legacy static `api_key` entries are scrubbed when discovered.
- OAuth-related compatibility behavior remains separate.
## Web UI note
Some SecretInput unions are easier to configure in raw editor mode than in form mode.
2026-02-24 16:26:59 -06:00
## Related docs
- CLI commands: [secrets ](/cli/secrets )
2026-02-26 14:32:04 +01:00
- Plan contract details: [Secrets Apply Plan Contract ](/gateway/secrets-plan-contract )
2026-03-02 20:58:20 -06:00
- Credential surface: [SecretRef Credential Surface ](/reference/secretref-credential-surface )
2026-02-24 16:26:59 -06:00
- Auth setup: [Authentication ](/gateway/authentication )
- Security posture: [Security ](/gateway/security )
- Environment precedence: [Environment Variables ](/help/environment )