* Secrets: add inline allowlist review set * Secrets: narrow detect-secrets file exclusions * Secrets: exclude Docker fingerprint false positive * Secrets: allowlist test and docs false positives * Secrets: refresh baseline after allowlist updates * Secrets: fix gateway chat fixture pragma * Secrets: format pre-commit config * Android: keep talk mode fixture JSON valid * Feishu: rely on client timeout injection * Secrets: allowlist provider auth test fixtures * Secrets: allowlist onboard search fixtures * Secrets: allowlist onboard mode fixture * Secrets: allowlist gateway auth mode fixture * Secrets: allowlist APNS wake test key * Secrets: allowlist gateway reload fixtures * Secrets: allowlist moonshot video fixture * Secrets: allowlist auto audio fixture * Secrets: allowlist tiny audio fixture * Secrets: allowlist embeddings fixtures * Secrets: allowlist resolve fixtures * Secrets: allowlist target registry pattern fixtures * Secrets: allowlist gateway chat env fixture * Secrets: refresh baseline after fixture allowlists * Secrets: reapply gateway chat env allowlist * Secrets: reapply gateway chat env allowlist * Secrets: stabilize gateway chat env allowlist * Secrets: allowlist runtime snapshot save fixture * Secrets: allowlist oauth profile fixtures * Secrets: allowlist compaction identifier fixture * Secrets: allowlist model auth fixture * Secrets: allowlist model status fixtures * Secrets: allowlist custom onboarding fixture * Secrets: allowlist mattermost token summary fixtures * Secrets: allowlist gateway auth suite fixtures * Secrets: allowlist channel summary fixture * Secrets: allowlist provider usage auth fixtures * Secrets: allowlist media proxy fixture * Secrets: allowlist secrets audit fixtures * Secrets: refresh baseline after final fixture allowlists * Feishu: prefer explicit client timeout * Feishu: test direct timeout precedence
14 KiB
summary, read_when, title
| summary | read_when | title | |||
|---|---|---|---|---|---|
| Secrets management: SecretRef contract, runtime snapshot behavior, and safe one-way scrubbing |
|
Secrets Management |
Secrets management
OpenClaw supports additive SecretRefs so supported credentials do not need to be stored as plaintext in configuration.
Plaintext still works. SecretRefs are opt-in per credential.
Goals and runtime model
Secrets are resolved into an in-memory runtime snapshot.
- Resolution is eager during activation, not lazy on request paths.
- 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.
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.passwordSecretRefs are active (whengateway.remote.enabledis notfalse) if one of these is true:gateway.mode=remotegateway.remote.urlis configuredgateway.tailscale.modeisserveorfunnelIn local mode without those remote surfaces:gateway.remote.tokenis active when token auth can win and no env/auth token is configured.gateway.remote.passwordis active only when password auth can win and no env/auth password is configured.
gateway.auth.tokenSecretRef is inactive for startup auth resolution whenOPENCLAW_GATEWAY_TOKEN(orCLAWDBOT_GATEWAY_TOKEN) is set, because env token input wins for that runtime.
Gateway auth surface diagnostics
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:
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.
Onboarding reference preflight
When onboarding runs in interactive mode and you choose SecretRef storage, OpenClaw runs preflight validation before saving:
- Env refs: validates env var name and confirms a non-empty value is visible during onboarding.
- Provider refs (
fileorexec): validates provider selection, resolvesid, and checks resolved value type. - Quickstart reuse path: when
gateway.auth.tokenis already a SecretRef, onboarding resolves it before probe/dashboard bootstrap (forenv,file, andexecrefs) using the same fail-fast gate.
If validation fails, onboarding shows the error and lets you retry.
SecretRef contract
Use one object shape everywhere:
{ source: "env" | "file" | "exec", provider: "default", id: "..." }
source: "env"
{ source: "env", provider: "default", id: "OPENAI_API_KEY" }
Validation:
providermust match^[a-z][a-z0-9_-]{0,63}$idmust match^[A-Z][A-Z0-9_]{0,127}$
source: "file"
{ source: "file", provider: "filemain", id: "/providers/openai/apiKey" }
Validation:
providermust match^[a-z][a-z0-9_-]{0,63}$idmust be an absolute JSON pointer (/...)- RFC6901 escaping in segments:
~=>~0,/=>~1
source: "exec"
{ source: "exec", provider: "vault", id: "providers/openai/apiKey" }
Validation:
providermust match^[a-z][a-z0-9_-]{0,63}$idmust match^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$
Provider config
Define providers under secrets.providers:
{
secrets: {
providers: {
default: { source: "env" },
filemain: {
source: "file",
path: "~/.openclaw/secrets.json",
mode: "json", // or "singleValue"
},
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,
},
},
}
Env provider
- Optional allowlist via
allowlist. - Missing/empty env values fail resolution.
File provider
- Reads local file from
path. mode: "json"expects JSON object payload and resolvesidas pointer.mode: "singleValue"expects ref id"value"and returns file contents.- Path must pass ownership/permission checks.
- Windows fail-closed note: if ACL verification is unavailable for a path, resolution fails. For trusted paths only, set
allowInsecurePath: trueon that provider to bypass path security checks.
Exec provider
- Runs configured absolute binary path, no shell.
- By default,
commandmust point to a regular file (not a symlink). - Set
allowSymlinkCommand: trueto allow symlink command paths (for example Homebrew shims). OpenClaw validates the resolved target path. - Pair
allowSymlinkCommandwithtrustedDirsfor package-manager paths (for example["/opt/homebrew"]). - Supports timeout, no-output timeout, output byte limits, env allowlist, and trusted dirs.
- Windows fail-closed note: if ACL verification is unavailable for the command path, resolution fails. For trusted paths only, set
allowInsecurePath: trueon that provider to bypass path security checks.
Request payload (stdin):
{ "protocolVersion": 1, "provider": "vault", "ids": ["providers/openai/apiKey"] }
Response payload (stdout):
{ "protocolVersion": 1, "values": { "providers/openai/apiKey": "<openai-api-key>" } } // pragma: allowlist secret
Optional per-id errors:
{
"protocolVersion": 1,
"values": {},
"errors": { "providers/openai/apiKey": { "message": "not found" } }
}
Exec integration examples
1Password CLI
{
secrets: {
providers: {
onepassword_openai: {
source: "exec",
command: "/opt/homebrew/bin/op",
allowSymlinkCommand: true, // required for Homebrew symlinked binaries
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" },
},
},
},
}
HashiCorp Vault CLI
{
secrets: {
providers: {
vault_openai: {
source: "exec",
command: "/opt/homebrew/bin/vault",
allowSymlinkCommand: true, // required for Homebrew symlinked binaries
trustedDirs: ["/opt/homebrew"],
args: ["kv", "get", "-field=OPENAI_API_KEY", "secret/openclaw"],
passEnv: ["VAULT_ADDR", "VAULT_TOKEN"],
jsonOnly: false,
},
},
},
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
models: [{ id: "gpt-5", name: "gpt-5" }],
apiKey: { source: "exec", provider: "vault_openai", id: "value" },
},
},
},
}
sops
{
secrets: {
providers: {
sops_openai: {
source: "exec",
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"],
passEnv: ["SOPS_AGE_KEY_FILE"],
jsonOnly: false,
},
},
},
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
models: [{ id: "gpt-5", name: "gpt-5" }],
apiKey: { source: "exec", provider: "sops_openai", id: "value" },
},
},
},
}
Supported credential surface
Canonical supported and unsupported credentials are listed in:
Runtime-minted or rotating credentials and OAuth refresh material are intentionally excluded from read-only SecretRef resolution.
Required behavior and precedence
- 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.
Warning and audit signals:
SECRETS_REF_OVERRIDES_PLAINTEXT(runtime warning)REF_SHADOWED(audit finding whenauth-profiles.jsoncredentials take precedence overopenclaw.jsonrefs)
Google Chat compatibility behavior:
serviceAccountReftakes precedence over plaintextserviceAccount.- Plaintext value is ignored when sibling ref is set.
Activation triggers
Secret activation runs on:
- Startup (preflight plus final activation)
- Config reload hot-apply path
- Config reload restart-check path
- Manual reload via
secrets.reload
Activation contract:
- Success swaps the snapshot atomically.
- Startup failure aborts gateway startup.
- Runtime reload failure keeps the last-known-good snapshot.
Degraded and recovered signals
When reload-time activation fails after a healthy state, OpenClaw enters degraded secrets state.
One-shot system event and log codes:
SECRETS_RELOADER_DEGRADEDSECRETS_RELOADER_RECOVERED
Behavior:
- Degraded: runtime keeps last-known-good snapshot.
- Recovered: emitted once after the next successful activation.
- Repeated failures while already degraded log warnings but do not spam events.
- Startup fail-fast does not emit degraded events because runtime never became active.
Command-path resolution
Command paths can opt into supported SecretRef resolution via gateway snapshot RPC.
There are two broad behaviors:
- Strict command paths (for example
openclaw memoryremote-memory paths andopenclaw 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:
- Snapshot refresh after backend secret rotation is handled by
openclaw secrets reload. - Gateway RPC method used by these command paths:
secrets.resolve.
Audit and configure workflow
Default operator flow:
openclaw secrets audit --check
openclaw secrets configure
openclaw secrets audit --check
secrets audit
Findings include:
- plaintext values at rest (
openclaw.json,auth-profiles.json,.env) - unresolved refs
- precedence shadowing (
auth-profiles.jsontaking priority overopenclaw.jsonrefs) - legacy residues (
auth.json, OAuth reminders)
secrets configure
Interactive helper that:
- configures
secrets.providersfirst (env/file/exec, add/edit/remove) - lets you select supported secret-bearing fields in
openclaw.jsonplusauth-profiles.jsonfor one agent scope - can create a new
auth-profiles.jsonmapping directly in the target picker - captures SecretRef details (
source,provider,id) - runs preflight resolution
- can apply immediately
Helpful modes:
openclaw secrets configure --providers-onlyopenclaw secrets configure --skip-provider-setupopenclaw secrets configure --agent <id>
configure apply defaults:
- scrub matching static credentials from
auth-profiles.jsonfor targeted providers - scrub legacy static
api_keyentries fromauth.json - scrub matching known secret lines from
<config-dir>/.env
secrets apply
Apply a saved plan:
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
For strict target/path contract details and exact rejection rules, see:
One-way safety policy
OpenClaw intentionally does not write rollback backups containing historical plaintext secret values.
Safety model:
- preflight must succeed before write mode
- runtime activation is validated before commit
- apply updates files using atomic file replacement and best-effort restore on failure
Legacy auth compatibility notes
For static credentials, runtime no longer depends on plaintext legacy auth storage.
- Runtime credential source is the resolved in-memory snapshot.
- Legacy static
api_keyentries 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.
Related docs
- CLI commands: secrets
- Plan contract details: Secrets Apply Plan Contract
- Credential surface: SecretRef Credential Surface
- Auth setup: Authentication
- Security posture: Security
- Environment precedence: Environment Variables