> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wednesdayai.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Secrets management

> Keep credentials out of plaintext config with SecretRefs — env, file, and exec providers, plus the audit and apply workflow.

# Secrets management

WednesdayAI supports **SecretRefs** so supported credentials do not have to live as plaintext in your config. SecretRefs are opt-in, additive, and per-credential — plaintext still works everywhere it did before.

A SecretRef points the gateway at a credential held in an environment variable, a local file, or an external resolver command (1Password, Vault, sops, and so on). The credential is resolved into an in-memory snapshot at activation time, never on the hot request path.

## Runtime model

* Resolution is **eager** during activation, not lazy on each request.
* Startup **fails fast** if an effectively active SecretRef cannot be resolved.
* Reload uses an **atomic swap**: full success, or the last-known-good snapshot is kept.
* Requests read only from the active in-memory snapshot.

This keeps secret-provider outages off the request path. If a reload fails after a healthy start, the gateway enters a degraded state (event `SECRETS_RELOADER_DEGRADED`) and keeps serving from the last good snapshot until a later reload recovers (`SECRETS_RELOADER_RECOVERED`).

## Active-surface filtering

SecretRefs are only validated on surfaces that are actually in use. A ref on a disabled channel, an unselected web-search provider, or an inactive auth path does **not** block startup — it emits a non-fatal diagnostic (`SECRETS_REF_IGNORED_INACTIVE_SURFACE`) instead.

Gateway auth surfaces (`gateway.auth.password`, `gateway.remote.token`, `gateway.remote.password`) log their state explicitly as `active` or `inactive` under `SECRETS_GATEWAY_AUTH_SURFACE`, with the reason, so you can see why a credential was required or skipped.

## SecretRef contract

Every SecretRef uses the same object shape:

```json5 theme={"dark"}
{ source: "env" | "file" | "exec", provider: "default", id: "..." }
```

<Tabs>
  <Tab title="env">
    ```json5 theme={"dark"}
    { source: "env", provider: "default", id: "OPENAI_API_KEY" }
    ```

    * `provider` matches `^[a-z][a-z0-9_-]{0,63}$`
    * `id` matches `^[A-Z][A-Z0-9_]{0,127}$`
    * Missing or empty env values fail resolution.
  </Tab>

  <Tab title="file">
    ```json5 theme={"dark"}
    { source: "file", provider: "filemain", id: "/providers/openai/apiKey" }
    ```

    * `id` is an absolute JSON pointer (`/...`). RFC6901 escaping applies (`~` becomes `~0`, `/` becomes `~1`).
    * The file path must pass ownership and permission checks.
  </Tab>

  <Tab title="exec">
    ```json5 theme={"dark"}
    { source: "exec", provider: "vault", id: "providers/openai/apiKey" }
    ```

    * `id` matches `^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$`
    * Runs a configured absolute binary path (no shell).
  </Tab>
</Tabs>

## Defining providers

Providers live under `secrets.providers`, with defaults selecting one provider per source:

```json5 theme={"dark"}
{
  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" },
  },
}
```

The **file** provider reads `path`: `mode: "json"` resolves `id` as a JSON pointer; `mode: "singleValue"` returns the whole file for `id: "value"`. The **exec** provider runs an absolute binary path; by default the command must be a regular file (not a symlink). For package-manager shims (such as Homebrew), set `allowSymlinkCommand: true` and pair it with `trustedDirs`.

<Warning>
  On Windows, secret resolution **fails closed** when ACL verification is unavailable for a file or command path. For trusted paths only, set `allowInsecurePath: true` on that provider to bypass path security checks.
</Warning>

## Exec integration examples

<AccordionGroup>
  <Accordion title="1Password CLI">
    ```json5 theme={"dark"}
    {
      secrets: {
        providers: {
          onepassword_openai: {
            source: "exec",
            command: "/opt/homebrew/bin/op",
            allowSymlinkCommand: true,
            trustedDirs: ["/opt/homebrew"],
            args: ["read", "op://Personal/OpenClaw 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" },
          },
        },
      },
    }
    ```
  </Accordion>

  <Accordion title="HashiCorp Vault CLI">
    ```json5 theme={"dark"}
    {
      secrets: {
        providers: {
          vault_openai: {
            source: "exec",
            command: "/opt/homebrew/bin/vault",
            allowSymlinkCommand: true,
            trustedDirs: ["/opt/homebrew"],
            args: ["kv", "get", "-field=OPENAI_API_KEY", "secret/openclaw"],
            passEnv: ["VAULT_ADDR", "VAULT_TOKEN"],
            jsonOnly: false,
          },
        },
      },
    }
    ```
  </Accordion>

  <Accordion title="sops">
    ```json5 theme={"dark"}
    {
      secrets: {
        providers: {
          sops_openai: {
            source: "exec",
            command: "/opt/homebrew/bin/sops",
            allowSymlinkCommand: true,
            trustedDirs: ["/opt/homebrew"],
            args: ["-d", "--extract", "[\"providers\"][\"openai\"][\"apiKey\"]", "/path/to/secrets.enc.json"],
            passEnv: ["SOPS_AGE_KEY_FILE"],
            jsonOnly: false,
          },
        },
      },
    }
    ```
  </Accordion>
</AccordionGroup>

## Precedence

* A field without a ref is left unchanged.
* A field with a ref is required on active surfaces during activation.
* If both plaintext and a ref are present, the **ref wins** on supported precedence paths and the gateway logs `SECRETS_REF_OVERRIDES_PLAINTEXT`.
* When `auth-profiles.json` credentials shadow an `openclaw.json` ref, the audit reports `REF_SHADOWED`.

<Note>
  Runtime-minted or rotating credentials and OAuth refresh material are intentionally **excluded** from read-only SecretRef resolution. Use the auth profile flows for those.
</Note>

## Audit and configure workflow

The standard operator flow:

```bash theme={"dark"}
openclaw secrets audit --check
openclaw secrets configure
openclaw secrets audit --check
```

* **`secrets audit`** reports plaintext values at rest (in `openclaw.json`, `auth-profiles.json`, `.env`), unresolved refs, precedence shadowing, and legacy residues.
* **`secrets configure`** is an interactive helper: set up `secrets.providers`, pick the secret-bearing fields to convert, capture the SecretRef details, run preflight resolution, and optionally apply. Useful modes: `--providers-only`, `--skip-provider-setup`, `--agent <id>`.

Applying a saved plan:

```bash theme={"dark"}
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json
openclaw secrets apply --from /tmp/openclaw-secrets-plan.json --dry-run
```

After rotating a backend secret, refresh the gateway snapshot:

```bash theme={"dark"}
openclaw secrets reload
```

## One-way safety policy

WednesdayAI deliberately does **not** write rollback backups containing historical plaintext secrets. Preflight must succeed before write mode, runtime activation is validated before commit, and applies use atomic file replacement with best-effort restore on failure. When you convert a field to a SecretRef, `configure --apply` scrubs the matching plaintext from `auth-profiles.json`, legacy `auth.json`, and `<config-dir>/.env`.

***

**Related:** [Authentication](/admin/gateway/authentication) · [Gateway configuration](/admin/gateway/configuration) · [Security hardening](/admin/security)
