# OpenClaw

[OpenClaw](https://docs.openclaw.ai) is a local-first agent gateway — it runs on
your own machine or server, connects a model to real tools, and holds the
credentials for every provider and channel it talks to. Those credentials are
what this integration is about.

```bash
openclaw plugins install npm:@seekrit/openclaw-plugin
openclaw plugins enable seekrit
seekrit openclaw init --write
```

After that, a credential in your gateway config is a **reference** rather than a
value:

```json5
// before — a key at rest in openclaw.json, readable by anything that can read the file
{ models: { providers: { openai: { apiKey: "sk-proj-…" } } } }

// after — a name OpenClaw resolves at startup, from your seekrit environment
{ models: { providers: { openai: { apiKey: { source: "exec", provider: "seekrit", id: "OPENAI_API_KEY" } } } } }
```

## How it works

OpenClaw supports four SecretRef sources — `env`, `file`, `store`, and `exec`.
`exec` runs a command, hands it the ids it needs on stdin, and reads the values
back on stdout. seekrit plugs in there, which means **the decryption happens on
your side of the line**: the resolver runs as a short-lived child process, uses
your service token, and exits when the batch is answered. The gateway never holds
a data key, and seekrit's API never sees a plaintext.

## Which ids to use

Two shapes, mirroring 1Password's `op://vault/item/field`:

| id | Resolves against |
| --- | --- |
| `OPENAI_API_KEY` | the environment your credential already points at |
| `billing-api/production/STRIPE_SECRET_KEY` | that application and environment |

A bare name is the common case, and the only one a service token needs — a token
is already bound to one environment, so there is nothing to disambiguate.

`seekrit openclaw ref` prints either shape, ready to paste:

```bash
seekrit openclaw ref OPENAI_API_KEY
seekrit openclaw ref STRIPE_SECRET_KEY --app billing-api --env production
```

## The credential the resolver uses

An exec provider runs with only the environment its config allowlists, which is
a feature — the resolver gets the credential it needs and not the rest of the
gateway's environment. Any of these work:

| Credential | Set | Good for |
| --- | --- | --- |
| Service token | `SEEKRIT_TOKEN=skt_…` | a gateway; scoped to one environment |
| Machine credentials | `SEEKRIT_CLIENT_ID` + `SEEKRIT_CLIENT_SECRET` | CI and containers; the CLI mints and caches a token |
| A login session | `seekrit login` | a workstation gateway you sit in front of |

Scope the token to exactly the environment the gateway needs — see
[service tokens](/docs/guides/service-tokens). It is the ceiling on everything
this integration can reach.

## Without the plugin

The plugin is the better shape, because OpenClaw re-reads the resolver's location
from its manifest at every startup — so a plugin upgrade that moves a file keeps
working, where a path copied into your config is pinned forever. But it is not
required. `--exec` generates a provider that points straight at the CLI:

```bash
seekrit openclaw init --exec
```

> **Note:** The generated `command` is an absolute path to the Node binary, not `seekrit`. OpenClaw refuses a symlinked command, and the `seekrit` on your `PATH` is a package-manager shim that symlinks into `node_modules` — so pointing at it produces a gateway that fails to start. `seekrit openclaw init` resolves the real paths for you.

## Skills and MCP tools

Separately from secrets, OpenClaw reads the [Agent Plugins](https://agent-plugins.org)
bundle format, which is what seekrit's [agent plugin](/docs/guides/ai-agents/plugin)
already ships. So the skills and both MCP servers install with no seekrit-specific
wiring at all:

```bash
openclaw plugins install git:github.com/seekritdev/agent-plugin
```

That gives the agent the three seekrit skills — so it knows *when* to reach for a
secret — plus the local crypto-plane server and the hosted metadata-plane server.
See [AI agents](/docs/guides/ai-agents) for what each server does.

## Two things that will surprise you

**Resolution is eager.** OpenClaw reads every active ref once, at startup and at
config reload, into one in-memory snapshot. It does not re-resolve per request.
So after you rotate a secret:

```bash
openclaw secrets reload
```

Without that, the gateway keeps serving the old value until it restarts. If you
rotate on a schedule, put the reload in the same automation — see
[rotation](/docs/guides/rotation).

**A linked plugin is inert.** OpenClaw only honours a plugin's secret-provider
integration when the plugin's origin is `bundled` or `global`. A development copy
installed with `--link` shows up in `openclaw plugins list` and resolves nothing.
Install from npm.

## Check your work

```bash
openclaw secrets audit --check --allow-exec
```

That reports unresolved refs and any plaintext left behind. Migrating a
credential to a SecretRef does not remove the copy already sitting in
`openclaw.json`, `models.json`, or the gateway's SQLite store — the audit is how
you find them.

## What this does and does not protect

A SecretRef keeps plaintext **out of your config files**. It does not stop the
agent from reading a value once resolved: OpenClaw is explicit that its own
egress sentinels are not a process boundary, and the same is true here. The
resolved key lives in the gateway's memory, and the gateway is what the model
drives.

If the requirement is that the agent must *never* hold the key, that is a
different mechanism:

| Requirement | Use |
| --- | --- |
| No plaintext in config or on disk | this page |
| The workload never holds the key at all | [egress proxy](/docs/guides/agent-proxy) — the agent gets a `{{seekrit:NAME}}` placeholder |
| Bound to specific hosts, paths, and methods | [agent access policy](/docs/guides/agent-proxy/policy) |

The two compose: resolve the proxy's own credential through a SecretRef, and give
the gateway placeholders for everything downstream.

## Troubleshooting

| Symptom | Cause |
| --- | --- |
| Startup fails on an unresolved ref | The resolver could not authenticate. Check `SEEKRIT_TOKEN` is in the provider's `passEnv` *and* in the gateway's own environment. |
| `openclaw plugins list` shows seekrit but refs fail | The plugin is installed but not enabled (`openclaw plugins enable seekrit`), or it was installed with `--link`. |
| A rotated secret still serves the old value | Eager resolution — run `openclaw secrets reload`. |
| `NOT_FOUND` for an id | No secret by that name in the target environment. `seekrit secrets list` to check the spelling. |
| The provider seems to exist but is never called | `command` in the manifest must be exactly `${node}`; OpenClaw silently declines any other value. |

## Reference

- [`seekrit openclaw init`](/docs/reference/cli#seekrit-openclaw-init) — generate the provider block
- [`seekrit openclaw ref`](/docs/reference/cli#seekrit-openclaw-ref) — print a SecretRef
- [`seekrit openclaw resolve`](/docs/reference/cli#seekrit-openclaw-resolve) — the exec protocol handler OpenClaw calls
