# SecretRef contract and provider config

> SecretRef sources, id grammar, validation rules, and provider configuration

- 网址：https://funcoding.ai/agents/openclaw/gateway/secrets/secretref-contract/
- 来源：OpenClaw 官方文档原文（英文），MIT 许可，同步于 2026-10-11
- 官方原文：https://docs.openclaw.ai/zh-CN/gateway/secrets/secretref-contract

---
This page is the SecretRef reference: the accepted sources and their id grammars, the validation rules, and the provider configuration blocks that back them.

## SecretRef contract

One object shape everywhere:

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

`env` and `store` refs have an implicit provider at their source's effective default alias: `secrets.defaults.env` or `secrets.defaults.store`, falling back to `default` when unset. A matching same-source `secrets.providers` entry takes precedence; otherwise, the ref uses the built-in reader without a provider entry.

Structured refs always include `provider`. Run `openclaw doctor --fix` for older
objects containing only `source` and `id`. Doctor adds the source's configured
default provider to registered config credentials. Stored auth-profile refs keep
their historical `default` provider, independently of config defaults. Doctor
backs up config and auth databases before rewriting them; updates run the same
repair. The Plugin SDK's input coercion remains compatible, and SDK auth writes
persist canonical refs.

SecretRefs contain exactly those three fields. If an older providerless ref in
a registered config credential or auth profile includes other fields, Doctor
reports their removal and preserves the original config or auth row in its
backup or source archive before writing the canonical ref. Opaque values outside
registered credential paths stay unchanged. New SDK auth writes reject extended
inputs before changing stored or published state.
Keep that metadata separately and explicitly call `coerceSecretRef` to choose
the canonical reference before saving.

Other aliases and all `file`/`exec` refs require a registered `secrets.providers` entry with the same `source`. Changing a source's default does not rewrite explicit refs: a ref that still names `default` after an override must match a registered same-source provider, or resolution fails.

**env**

```json5
{ source: "env", provider: "default", id: "OPENAI_API_KEY" }
```

Shorthand strings are also accepted on SecretInput fields:

```json5
"${OPENAI_API_KEY}"
"$OPENAI_API_KEY"
```

Validation:

- `provider` must match `^[a-z][a-z0-9_-]{0,63}$`
- `id` must match `^[A-Z][A-Z0-9_]{0,127}$`

**file**

```json5
{ source: "file", provider: "filemain", id: "/providers/openai/apiKey" }
```

Validation:

- `provider` must match `^[a-z][a-z0-9_-]{0,63}$`
- `id` must be an absolute JSON pointer (`/...`), or the literal `value` for `singleValue` providers
- RFC 6901 escaping in segments: `~` becomes `~0`, `/` becomes `~1`

**exec**

```json5
{ source: "exec", provider: "vault", id: "providers/openai/apiKey#value" }
```

Validation:

- `provider` must match `^[a-z][a-z0-9_-]{0,63}$`
- `id` must match `^[A-Za-z0-9][A-Za-z0-9._:/#-]{0,255}$` (supports selectors such as `secret#json_key`)
- `id` must not contain `.` or `..` as slash-delimited path segments (for example `a/../b` is rejected)

**store**

```json5
{ source: "store", provider: "default", id: "OPENAI_API_KEY" }
```

Validation:

- `provider` must match `^[a-z][a-z0-9_-]{0,63}$`
- `id` uses the environment-name grammar `^[A-Z][A-Z0-9_]{0,127}$`
- Only the Gateway-wide team scope is resolved

## Provider config

Define providers under `secrets.providers`:

```json5
{
  secrets: {
    providers: {
      default: { source: "env" },
      teamstore: { source: "store" },
      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,
      },
      "team-secrets": {
        source: "exec",
        pluginIntegration: {
          pluginId: "acme-secrets",
          integrationId: "secret-store",
        },
      },
    },
    defaults: {
      env: "default",
      file: "filemain",
      exec: "vault",
      store: "teamstore",
    },
  },
}
```

Provider aliases are source-specific. A matching explicit provider entry wins; if an `env` or `store` default alias is also used by an entry for another source, that source's built-in provider wins. Non-default aliases and `file` or `exec` providers must resolve to an explicit entry with the matching source.

Read-only inspection recognizes valid `store` bindings without opening the database. That is configuration evidence, not proof that the value exists: credential availability stays unknown until runtime resolution.

<details>
<summary>Env provider</summary>

- Optional exact-name allowlist via `allowlist`. A matching explicit env provider enforces this list even when it is the selected default. Omit the list to allow any name; use `[]` to deny every name.
- Missing or empty env values fail resolution. An explicit env SecretRef remains authoritative and does not fall through to another credential or auth profile.

</details>

<details>
<summary>File provider</summary>

- Reads the local file at `path`.
- `mode: "json"` (default) expects a JSON object payload and resolves `id` as a JSON pointer.
- `mode: "singleValue"` expects ref id `"value"` and returns the raw file contents (trailing newline stripped).
- Path must be a private regular file with one hard link and pass ownership/permission checks. Symlinks and hardlinked files are rejected; `timeoutMs` (default 5000) and `maxBytes` (default 1 MiB) bound the read.
- Windows fail-closed: if ACL verification is unavailable for the path, resolution fails. Move the secret to a path whose ACLs OpenClaw can verify; there is no provider-level bypass.

If an upgrade reports `must not be hardlinked`, copy the contents into a new private
file and replace the configured path. Changing permissions alone does not break
hardlinks. On Linux or macOS, run this as the Gateway user, using the actual
credential path in an existing private directory:

```bash
(
  set -eu
  umask 077
  credential_path="$HOME/.openclaw/secrets.json"
  replacement="$(mktemp "${credential_path}.XXXXXX")"
  trap 'rm -f "$replacement"' EXIT
  cat "$credential_path" > "$replacement"
  chmod 600 "$replacement"
  mv -f "$replacement" "$credential_path"
)
```

This preserves the configured path and contents while creating a single-link,
`0600` file. Other names for the old inode remain unchanged. Run
`openclaw secrets reload` for a running Gateway; if startup failed, repair the file
before starting the Gateway again. See [activation behavior](https://funcoding.ai/agents/openclaw/gateway/secrets/operations/#activation-triggers).
The [1Password integration](https://funcoding.ai/agents/openclaw/gateway/secrets/integration-examples/#1password) retains
its separate, explicit allowance for broker-token hardlinks.

</details>

<details>
<summary>Exec provider</summary>

- Runs the configured absolute binary path directly, no shell.
- `command` must not be a symlink, must not be group- or world-writable, and on POSIX must be owned by the current user. For package-manager shims, resolve the real binary path (for example with `realpath "$(command -v vault)"`) and configure that absolute path. Use `trustedDirs` to restrict executables to approved directories.
- [`config validate`](https://funcoding.ai/agents/openclaw/cli/config/#config-validate) checks every manual exec command path without executing providers. Config writes and dry runs check only changed or newly referenced providers, so an unrelated inactive provider does not block repairs. These are path trust checks, not proof that a provider can execute or return a secret.
- Supports `timeoutMs` (default 5000), `noOutputTimeoutMs` (default equals `timeoutMs`), `maxOutputBytes` (default 1 MiB), `env`/`passEnv` allowlist, and `trustedDirs`.
- `jsonOnly` defaults to `true`. With `jsonOnly: false` and a single requested id, plain non-JSON stdout is accepted as that id's value.
- Windows fail-closed: if ACL verification is unavailable for the command path, resolution fails. Use a command path whose ACLs OpenClaw can verify; there is no provider-level bypass.
- Plugin-managed exec providers can use `pluginIntegration` instead of a copied `command`/`args`. OpenClaw resolves the current command details from the installed plugin manifest during startup/reload; if the plugin is disabled, removed, untrusted, or no longer declares the integration, active SecretRefs on that provider fail closed.

Request payload (stdin):

```json
{ "protocolVersion": 1, "provider": "vault", "ids": ["providers/openai/apiKey"] }
```

Response payload (stdout):

```jsonc
{ "protocolVersion": 1, "values": { "providers/openai/apiKey": "<openai-api-key>" } }
```

Optional per-id errors:

```json
{
  "protocolVersion": 1,
  "values": {},
  "errors": { "providers/openai/apiKey": { "code": "NOT_FOUND" } }
}
```

`code` is an optional machine-readable diagnostic. OpenClaw displays the recognized
codes `NOT_FOUND` and `AMBIGUOUS_DUPLICATE_KEY` with the provider and ref id. Other
codes and free-form fields such as `message` are accepted for protocol-v1 compatibility
but are not displayed because resolver output can contain credential material.

</details>

<details>
<summary>Store provider</summary>

- Reads values from OpenClaw's shared state SQLite database.
- The provider has no connection settings. `secrets.defaults.store` selects its default alias.
- Only team scope is resolved. Identity scope is not supported yet.

</details>
