seekrit
Docs/CLI commands

CLI commands

seekrit <command> [options]. At runtime a service token selects the org, app, and environment — so seekrit run/export need no config file. Management commands select their target with --org/--app/--group/--env flags (plus --branch for an ephemeral branch config), falling back to the optional seekrit.json written by seekrit init.

Environment variables

VariablePurpose
SEEKRIT_TOKENService token (skt_…). Carries its bound org + app + environment. Also accepts a CLI session token (skc_…) if you'd rather pass one explicitly than save it.
SEEKRIT_CLIENT_ID / SEEKRIT_CLIENT_SECRETMachine (M2M) credentials. When set (and no SEEKRIT_TOKEN is), an admin token is minted from them automatically and cached — the seamless path for autonomous agents.
SEEKRIT_API_URLAPI base URL. Overrides saved config; defaults to https://api.seekrit.dev.
SEEKRIT_PASSPHRASEPassphrase to unlock your private key non-interactively. Set it (or pipe the passphrase in) for anything scripted: with no passphrase and no terminal, a decrypting command exits non-zero rather than returning an empty value.
SEEKRIT_CACHESet to 1 to enable the last-known-good cache for run / export without passing --cache.
SEEKRIT_CACHE_DIRWhere cached responses live. Defaults to $XDG_CACHE_HOME/seekrit, else ~/.cache/seekrit.
SEEKRIT_CACHE_MAX_AGEHow stale a cached response may be and still be used. Defaults to 24h.

Precedence: environment variables override values saved by seekrit login in ~/.config/seekrit/config.json. seekrit run additionally reads these from its .env file(s) (below process.env, above saved config) — see Running & exporting.

Output conventions

Everything the CLI can do from the dashboard, it can do from the terminal, and every listing reads the same way:

  • Listings print an aligned table with a header when stdout is a terminal, and plain tab-separated rows with no header when it is piped — so seekrit app list | cut -f1 keeps working.
  • --json on any read command prints the API's own response instead. Use it when a table would flatten something you need (ids, nested config, timestamps).
  • Data goes to stdout; everything else goes to stderr — confirmations (created app …), "nothing to list" notes, and pagination hints. A command's stdout is always safe to pipe.
  • Destructive commands ask first. Anything that deletes or revokes prompts for confirmation at a terminal, and refuses outright when there is no terminal unless you pass -y/--yes (or, for sync enable, --acknowledge-decryption). Two commands deliberately skip the question: org invite rm, because rescinding an unaccepted invitation costs one re-send, and agents revoke, because revoking is the safe direction and a run about to be killed is not the moment to ask.
  • list is aliased as ls, and rm as delete, wherever both read naturally.

Auth & identity

seekrit login

With no flags, signs you in through your browser: it prints a URL and a pairing code, opens the URL when you press [Enter], and waits while you authorize the device in the dashboard (re-entering your authenticator code if you have one). The session it saves authenticates as you — every org you belong to, at your role — and lasts 90 days. Nothing to copy and paste, and no org, app, or environment to choose.

Passing a credential stores that instead and skips the browser entirely — the path machines and CI use.

FlagDescription
--token <token>Service token (skt_…). Stored as-is; replaces any browser session.
--client-id <id> / --client-secret <secret>Machine (M2M) credentials (given together). An admin token is minted from them automatically on next use and cached.
--api-url <url>API base URL.
--no-browserPrint the sign-in URL instead of opening it (SSH sessions, headless machines).

A browser session carries no key material, so reading a secret's value still unlocks your private key with your passphrase (SEEKRIT_PASSPHRASE, else prompted) — exactly as the dashboard does. Service tokens carry their own key and don't prompt, which is why they remain the right credential for unattended jobs.

seekrit logout

Forget the saved credentials. A browser-authorized session is also revoked server-side, so the token can't be used again; a service token is only removed locally (other machines may hold it — revoke it with seekrit token revoke). Machine (M2M) credentials are left in place.

seekrit whoami

Show the authenticated identity. For a service token, prints its bound org/app/env scope; for a browser session, names the session so you can revoke it.

seekrit doctor

[--json] — check the setup failures that would otherwise be silent.

This is deliberately not a general health sweep. The CLI already explains the loud failures where they happen: a blank SEEKRIT_TOKEN reports as "no credentials" rather than as a 401, a transport failure names the address it tried and why it failed, and seekrit whoami says when key setup is pending. What doctor covers is the class of problem that produces no symptom at all:

CheckThe failure it catches
Credential in useWhich of the configured credentials actually wins, and where it comes from.
Shadowed credentialsA SEEKRIT_TOKEN still exported in your shell outranking the seekrit login you just did. Nothing is wrong, nothing says anything, and you are operating on the wrong org.
Blank variablesA SEEKRIT_* variable set to the empty string. The CLI treats blank as absent — correct, but it presents as a missing setting rather than an error.
ClockSkew large enough to expire a token that has not expired. The refusal blames the credential, not the clock.
OpenClaw providerA command that resolves through a symlink (OpenClaw rejects it outright), a resolver path a CLI reinstall moved, or a SEEKRIT_* variable set in the gateway's environment but missing from passEnv, so the resolver never sees it.
HTTPS_PROXYPointed at a local port with nothing listening — every outbound request fails at the transport, and the error names the upstream it never reached.

It reports and never repairs; each finding names the command that fixes it. A failure sets a non-zero exit code, so it works as a CI preflight. Credential values never appear in the output.

seekrit keys setup

Generate your P-256 keypair and upload your public key plus a passphrase-encrypted private key. Run once per account. Honors SEEKRIT_PASSPHRASE, otherwise prompts.

Resources

seekrit init

--org <slug> --app <slug> — write seekrit.json naming default org + app for management commands. Environment-independent and safe to commit — it never pins an environment (the token does that at runtime).

Organizations (seekrit org)

CommandDescription
seekrit org listEvery organization you can reach, with your role in each.
seekrit org show [slug]One org: slug, id, your role, and how many applications, groups, members, and service tokens it holds. A count your role can't read prints as —.
seekrit org tree [--org <slug>]The whole org as a tree — every application and group with their environments. The fastest "what is in here?".
seekrit org create --name <name> --slug <slug>Create an organization (you become owner).
seekrit org rename [--org <slug>] --name <name>Change the display name. Slugs are permanent identifiers and never change.
seekrit org member list [--org <slug>]Members, their roles, and whether each has finished key setup. Someone showing pending cannot be granted an environment key yet.
seekrit org invite list [--org <slug>]Outstanding invitations.
seekrit org invite add <email> [--role admin|member]Invite someone. Membership is created when they first sign in, so they will not appear in member list until then.
seekrit org invite rm <inviteId>Rescind an invitation.
seekrit org mfa [--set required|optional]Show — or change — the org-wide second-factor requirement. Reports "no identity provider configured" on a deployment without one.

Applications (seekrit app)

CommandDescription
seekrit app list [--org <slug>]Applications in an org.
seekrit app show [slug]One application, its environments, and whether you hold a key for each — no key is why secrets get would fail there. Lists live branches too.
seekrit app create [--org <slug>] --name <name> --slug <slug>Create an application.
seekrit app rename [slug] --name <name>Change the display name.
seekrit app rm <slug> [--yes]Delete an application, its environments, and every secret in them. Names the number of environments before asking. Cannot be undone.

Environments (seekrit env)

CommandDescription
seekrit env list [--app <slug>]An application's environments and your access to each.
seekrit env show --env <slug> [--app <slug>]One environment: its composed groups in precedence order, who holds a key for it (admins only), its secret count, and its branches.
seekrit env create [--org <slug>] --app <slug> --name <name> --slug <slug>Create an application environment. Generates the environment's data key locally and wraps it to your public key.
seekrit env rm --env <slug> [--app <slug>|--group <slug>] [--yes]Delete an environment and every secret in it. Names the secret count before asking. Works for group environments too, via --group.
seekrit env diff --from <slug> --to <slug> [--app <slug>] [--values]Compare the secrets declared on two environments, or on a branch and the environment it overlays. See below.

seekrit env diff

--from <slug> --to <slug> [--org <slug>] [--app <slug>] [--values] [--json] — what differs between two environments. Either side may name a branch instead of an environment, which is the comparison branches make necessary: a branch overlays its parent rather than copying it, so seekrit secrets list --env pr-142 shows the override set with nothing to compare it against.

By default the diff is name-level and decrypts nothing — each name lands in only in <from>, only in <to>, or declared in both, with each side's version. That works with any credential that can list secrets.

--values decrypts both sides and splits the shared names into different values and identical values. The values themselves are never printed. An identical row between two environments is worth looking at rather than skipping past: it means one credential spans both, so revoking it hits both.

Two limits, stated in the output as well as here:

  • It compares what each environment declares, not what it resolves. A secret contributed by a composed group belongs to the group environment, so it is not on either side of this comparison. seekrit env show lists the groups composed into an environment.
  • --values compares stored values, not expanded ones. A ${OTHER_SECRET} reference expands against the full composed set, which a declared-only diff does not gather — so the comparison is of what seekrit secrets set wrote.

seekrit branch create

<slug> [--org <slug>] [--app <slug>] --from <env> [--name <name>] [--ttl <duration>] [--no-share] — fork an application environment into an ephemeral branch (a per-PR / preview config). The branch inherits its parent's secrets by layering at read time — nothing is copied or re-encrypted — and stores only what you override on it. Generates the branch's own data key locally and, unless --no-share, wraps it to everyone who already holds a grant on the parent.

--ttl accepts 30m, 12h, 7d, 2w, … or never (default 7d, max 30 days). Expired branches are deleted automatically, along with their overrides, key grants, and any service token bound to them.

seekrit branch list

[--org <slug>] [--app <slug>] [--env <slug>] — list branches in an application, or just those of one environment. Prints slug, id, and expiry.

seekrit branch delete

<slug> [--org <slug>] [--app <slug>] [--yes] — tear down a branch and every value it overrode. The environment it overlays is untouched. Aliased as branch rm.

Shared groups (seekrit group)

A group is a reusable secret bag shared across applications, holding one environment per slug (its variants).

CommandDescription
seekrit group list [--org <slug>]Groups in an org.
seekrit group show <slug>One group and the environments it holds, with your access to each.
seekrit group create [--org <slug>] --name <name> --slug <slug>Create a group.
seekrit group rename <slug> --name <name>Change the display name.
seekrit group rm <slug> [--yes]Delete a group, its environments, and their secrets. Every application environment composing it stops receiving these values on its next resolve.
seekrit group env list --group <slug>The group's environments.
seekrit group env create --group <slug> --name <name> --slug <slug>Create a group environment (a per-slug value set / variant). Generates its data key locally.

Composition (env groups)

Compose shared groups into an application environment. At resolve time each group is matched to the environment whose slug matches the app environment's (or a --with override).

CommandDescription
seekrit env groups add --app <slug> --env <slug> --group <slug> [--position <n>]Compose a group (higher position wins).
seekrit env groups list --app <slug> --env <slug>List composed groups, lowest precedence first.
seekrit env groups rm --app <slug> --env <slug> --group <slug> [--yes]Remove a group. Everything it contributed disappears from that environment on the next resolve.

Secrets

Every secrets command targets one environment: an application environment (--app --env, or the config's app + --env) or a group environment (--group --env). --org is inferred from seekrit.json or a lone org.

With a service token, --env is optional — the token is bound to exactly one environment, so seekrit secrets list on its own is the whole command in CI and for agents. Pass it and it is checked against the binding rather than ignored. Naming --app or --group opts back into explicit targeting, and a logged-in user always passes --env.

CommandDescription
seekrit secrets list [--env <slug>] [--app <slug>|--group <slug>]List secret names, versions, update times (no values). --json prints the same metadata — never a value, and never the stored ciphertext.
seekrit secrets get <name> [--env <slug>] [--raw] [--version <n>] [--pretty] …Decrypt and print one value. ${OTHER_SECRET} references are expanded against that environment's own secrets; --raw prints the stored text. --version prints an earlier version instead of the current one, always as stored. --pretty re-indents the value if it is JSON and leaves it untouched if it is not — for reading, not for piping.
seekrit secrets set <name> [value] [--env <slug>] [--file <path>] [-m <note>] …Encrypt and store a value. Reads stdin if value is omitted or -; --file reads it from a file. Both drop one trailing newline and store the rest byte-for-byte — the way to store a JSON credential or a PEM key without shell quoting.
seekrit secrets import [file] [--env <slug>] [--dry-run] [-m <note>] …Bulk-import a .env file (default .env; - reads stdin). Each KEY=VALUE is encrypted and stored; existing names are overwritten. Aborts before writing if any name is invalid; --dry-run lists what would change (marking each new/update) without writing.
seekrit secrets history <name> [--limit <n>] [--env <slug>] …List the secret's versions — when each was saved, who saved it, and which ones were restores. Never prints values (default 20, max 200).
seekrit secrets restore <name> <version> [--env <slug>] [-m <note>] …Roll the secret back to an earlier version.
seekrit secrets rm <name> [--env <slug>] [--yes] [-m <note>] …Delete a secret, and with it every earlier version. Cannot be undone — secrets restore cannot bring back a deleted secret.

Every write command takes -m, --note <text>. It is ignored unless the environment requires approval, in which case the write is queued rather than performed and the note is what reviewers see:

queued for review: set STRIPE_KEY — needs 2 approvals (chg_…)

See approval workflows.

JSON and multi-line values

A value is an opaque string to seekrit — a service-account key, a PEM block, or a certificate chain is stored and delivered byte-for-byte. The only thing that needs care is getting it in without a shell or a .env file mangling it first. Read it from a file and neither is involved:

seekrit secrets set GOOGLE_SERVICE_ACCOUNT --file ./service-account.json \
  --app storefront --env production
cat ./key.pem | seekrit secrets set TLS_KEY - --app storefront --env production

Both drop a single trailing newline and store everything else exactly as written. To read one back formatted:

seekrit secrets get GOOGLE_SERVICE_ACCOUNT --pretty --app storefront --env production

--pretty only re-indents values that parse as JSON; anything else prints unchanged. Without it, output stays byte-exact, which is what you want when piping to a file or another tool.

.env syntax

seekrit secrets import, the --env-file overlays, and the dashboard's paste .env tab all use one parser — as does the seekrit-run launcher, byte-for-byte:

SyntaxMeaning
KEY=valueUnquoted: single-line, and a trailing # comment is dropped.
KEY='value'Single quotes: literal. No escapes are interpreted, so a \n inside stays two characters.
KEY="value"Double quotes: \n, \r, \t, \", and \\ are interpreted.
export KEY=valueThe export prefix is ignored.
# commentWhole-line comments and blank lines are skipped.

A quoted value may span lines — it runs to its closing quote, wherever that lands. That is what makes a pretty-printed credential storable:

GOOGLE_SERVICE_ACCOUNT='{
  "type": "service_account",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQ…\n-----END PRIVATE KEY-----\n"
}'

Single quotes are the right choice for JSON: the value is full of double quotes and \n escapes that must survive as written, and single quotes pass them through untouched.

caution

An unquoted value stops at the end of its line. Paste a multi-line JSON credential without quotes and the lines below it are read as further assignments — you get KEY={ plus a handful of garbage names, not an error. Quote it, or skip the file entirely and use --file.

seekrit export --format dotenv writes this same syntax and quotes as needed, so exporting and re-importing a value round-trips exactly, escapes included.

Point-in-time restore

Every write appends to the secret's history, so a bad value is always one command away from being undone:

seekrit secrets history DATABASE_URL --app storefront --env production
VERSION  WHEN                      BY                  NOTE
v3       2026-07-24T18:22:05.994Z  user:usr_KvxTL40S…  current
v2       2026-07-24T18:19:41.769Z  user:usr_KvxTL40S…
v1       2026-07-24T18:02:13.539Z  user:usr_KvxTL40S…

Check what you're rolling back to, then roll back:

seekrit secrets get DATABASE_URL --version 2 --app storefront --env production
seekrit secrets restore DATABASE_URL 2 --app storefront --env production

Restoring is a roll-forward: v2's value is written back as v4, so nothing in the history is lost and the rollback is itself undoable. Because the server simply replays ciphertext it already stores, no key is involved — a machine (M2M) credential that cannot decrypt the value can still roll it back. The restore is audited as secret.restored.

note

Deleting a secret deletes its history with it. After seekrit secrets rm there is nothing left to restore.

Running & exporting

Both resolve a layered environment for the current principal:

group secrets  <  app-env secrets  <  .env file  <  process env   (highest wins)

With a service token, org/app/env come from the token. As a logged-in user, pass --app --env (and --org if ambiguous).

Once the layers are merged, ${OTHER_SECRET} references inside values are expanded — locally, in the same process that decrypted them.

FlagApplies toDescription
--branch <slug>run, exportRead an ephemeral branch of the target environment. Defaults to SEEKRIT_BRANCH (process env, then .env).
--with <group>=<slug>run, exportResolve one group at a different slug for this invocation (repeatable).
--env-file <path>run, exportA .env file to overlay; repeatable; defaults to .env.
--explainrun, exportPrint each variable's source layer to stderr (never values). Marks variables whose ${OTHER_SECRET} references were expanded, and lists references that matched nothing.
--no-interpolaterun, exportLeave ${OTHER_SECRET} references as literal text.
--cacherun, exportKeep a last-known-good copy of the encrypted response and fall back to it when the API is unreachable. Off by default.
--cache-dir <path>run, exportWhere to keep it. Defaults to $XDG_CACHE_HOME/seekrit, else ~/.cache/seekrit.
--cache-max-age <duration>run, exportHow stale that copy may be and still be used — 15m, 24h, 7d. Defaults to 24h.

seekrit run -- <command…>

Run a command with the resolved environment injected. Everything after -- is the command.

SEEKRIT_TOKEN=skt_… seekrit run -- ./start-server
# swap only the auth group to its staging slice for this boot:
seekrit run --with auth-providers=staging -- pnpm dev

run reads its .env file(s) before authenticating, so SEEKRIT_TOKEN and SEEKRIT_API_URL can live in .env — resolved as process.env > .env > saved config, matching the seekrit-run launcher. Drop a token into a project's .env and seekrit run uses it with no global login.

Resolving seekrit secrets is best-effort: if no credentials are configured or seekrit can't be reached (network, auth, or decryption failure), run logs a warning to stderr and still launches the command with just the .env overlay and process.env. This mirrors the seekrit-run launcher. (seekrit export does not degrade — it errors if it can't resolve the secrets.)

run is a transparent wrapper: it forwards termination signals (SIGINT from Ctrl-C, plus SIGTERM/SIGHUP/SIGQUIT) to the command and every process below it, then stays alive until the command exits and exits with its status (signal death is re-raised, so Ctrl-C yields 130). Relaying down the whole process tree, rather than to the one process, is what makes Ctrl-C reliably tear down commands that spawn their own children (npm/pnpm scripts running nodemon, for example), since those don't always pass the signal on themselves.

The command keeps your terminal: run does not put it in a separate session, so it stays visible in ps as usual, still gets the terminal's own Ctrl-C and its hangup when you close the window, and can still prompt on /dev/tty for things like a git credential or an SSH passphrase.

Nothing the command started outlives run. Once the command itself has exited, anything still alive underneath it has both ignored the signal and lost the process that started it, so nothing is left to stop it — not the terminal either, which only ever hangs up its own foreground group. Rather than trust it, run escalates: SIGTERM, then SIGKILL two seconds later, printing the pids it had to force-kill. A process that ignores both is usually a signal handler in the app that cleans up but never exits — in Node, process.on("SIGINT", …) replaces the default action, so the process stops dying on Ctrl-C unless the handler itself exits. Watch for that if you see the message; it names the pids. A command that exits on its own is never chased this way, so if it deliberately leaves a daemon running in the background, that keeps working.

The compiled seekrit-run launcher replaces itself with the command (exec), so signals and the process tree behave exactly as if you had run the command directly — with no wrapper left to escalate, cleaning up a command that ignores signals is up to the command. See its exit-code notes below.

seekrit export

Print the resolved secrets (managed layers + .env, without process.env). --format <dotenv|json|shell> (default dotenv).

Use shell if a shell is going to evaluate the output, not dotenv. The shell format single-quotes every value and escapes embedded quotes, so eval "$(seekrit export --format shell)" reproduces each value byte-for-byte. dotenv is written to be read back by a .env parser — seekrit's own, seekrit-run's, or your framework's — and a value carrying a $ or a backtick is not escaped against shell expansion, because a parser must not see an escape that isn't part of the value. source-ing a .env is a shell evaluating it.

seekrit-run launcher

seekrit-run is a separate, compiled single-file binary — a dependency-free seekrit run for machines (containers, CI, agents). It is service-token only and reproduces seekrit run's precedence and .env parsing exactly. See the launcher guide for install and container usage.

seekrit-run [OPTIONS] [--] <command> [args...]
seekrit-run run [OPTIONS] [--] <command> [args...]   # `run` is optional
FlagDefaultDescription
-t, --token <skt_…>SEEKRIT_TOKEN (env or .env)Service token.
--api-url <url>SEEKRIT_API_URL or https://api.seekrit.devAPI base URL.
-e, --env-file <path>.envA .env file to overlay (repeatable).
--no-env-fileDo not load the default .env.
--branch <slug>SEEKRIT_BRANCH (env or .env)Read an ephemeral branch of the bound environment.
--with <group=env>Override one composed group's slice (repeatable).
--explainPrint each variable's source to stderr (names only).
--no-interpolateLeave ${OTHER_SECRET} references as literal text.
--cacheoffKeep a last-known-good copy of the encrypted response and fall back to it when the API is unreachable. Also SEEKRIT_CACHE=1.
--no-cacheOverride SEEKRIT_CACHE=1 for this run.
--cache-dir <path>SEEKRIT_CACHE_DIR, else $XDG_CACHE_HOME/seekritWhere to keep it.
--cache-max-age <d>24hHow stale that copy may be and still be used.

Like seekrit run, it degrades gracefully: a missing/malformed token or an unreachable API is logged to stderr, and the command runs with just .env + the live environment. Exit codes: 2 usage error, 1 a local failure (an unreadable explicit --env-file, or a reference cycle in one), 127 command not found; otherwise the command's own exit code (Unix exec). Honors HTTPS_PROXY / ALL_PROXY. Its default API URL is the hosted https://api.seekrit.dev — the same default the Node seekrit run uses when nothing else is configured.

Last-known-good cache

Off by default. With --cache, a successful resolve is written to disk and a later run falls back to it when the seekrit API cannot be reached — so a deploy, a CI job, or an agent boot still works during an outage.

seekrit run --cache -- ./start-server
seekrit-run: could not reach the seekrit API: … — using cached secrets fetched 6m ago

Only the encrypted response is stored — ciphertext plus your token's wrapped data keys, the same bytes the API serves. Decrypting still requires the service token's private key, so the file is no more sensitive than the token sitting beside it; it is written 0600 inside a 0700 directory. The zero-knowledge invariant is untouched: nothing is written in plaintext, and nothing new is sent to the server.

How it behaves:

  • Live first, always. Every invocation tries the API before touching the cache, so a recovered network is picked up immediately — the cache never serves a request that could have been answered fresh.
  • A refused resolve does not fall back. If the API answers 401/403/404, the entry is deleted and the command fails as it would have anyway. Revoking a token still takes effect on the next run. Only "the API is unreachable" — a network failure, a 5xx, or a rate limit — uses the cache.
  • Bounded by --cache-max-age (default 24h). Past that, the entry is ignored and pruned.
  • Scoped to the exact request. The entry is keyed by API URL, token, --branch, and --with overrides, so a different environment or override never reads another's copy.
  • Shared with seekrit-run. Both use the same file format and directory, so whichever one runs first warms the cache for the other.

Two trade-offs worth stating plainly, both consequences of a copy that outlives the network:

  • A revoked token keeps working offline until the entry expires. That window is exactly --cache-max-age, and it only applies while the API is unreachable (a reachable API that refuses the token clears the entry immediately). Set a shorter max-age if that window matters more to you than the outage coverage.
  • A run served from cache makes no resolve call, so it produces no env.resolve_denied audit entry. If you monitor for revoked credentials still in use, a cached client is invisible to that signal until it next reaches the API.

For the long-lived integrations, the same cache is configured in their own way: [cache] enabled = true in the egress proxy's config, and cache.enabled=true in the Kubernetes chart. Both also retry in the background and switch to live secrets as soon as the API answers.

Access

Who can decrypt an environment. Every command targets the environment with --env plus --app or --group.

CommandDescription
seekrit grant --env <slug> --user <email>|--token <tokenId>Give a member or service token the environment's key. The key is unwrapped on your machine and re-wrapped to the recipient, so the API only ever sees ciphertext. Members must have finished key setup (seekrit org member list shows who has).
seekrit grant list --env <slug>Who currently holds a key, by email or token name rather than raw ids. Admins only.
seekrit grant rm --env <slug> --user <email>|--token <tokenId> [--yes]Take the key away. This removes their wrapped copy — anything they already decrypted stays decrypted, so rotate the value too if it may have leaked. Aliased as grant revoke.

seekrit grant --user … --env … is the bare grant verb and behaves exactly as it always has; list and rm are subcommands beside it.

Service tokens

CommandDescription
seekrit token create --name <name> --app <slug> --env <slug> [--allow <group>=<slug>] [--no-grant]Mint a runtime token bound to an app environment; prints it once. Auto-grants that env's key and every composed group's matching slice. --allow pre-authorizes an alternate group slice for run --with; --no-grant skips granting.
seekrit token create --name <name> --admin [--org <slug>]Mint an admin token: org-scoped, no env binding, passes admin-gated routes (create apps/groups/envs, compose, grant, mint tokens). For headless provisioning by agents/automation. Only an admin caller may create one.
seekrit token list [--org <slug>]List tokens with role, status, and last-used time.
seekrit token revoke <tokenId> [--yes] [--org <slug>]Revoke a token. Reversible-safe: a revoked token authenticates nothing and frees its plan slot, but stays in the list.
seekrit token delete <tokenId> [--yes] [--org <slug>]Delete a token and drop the keys granted to it. Only allowed once the token has been revoked. Cannot be undone.

An admin token can also be bound to an environment (pass --admin --app --env) to both provision structure and decrypt that environment.

Change approval (changes)

Review gate in front of a protected environment. See the approval workflows guide.

CommandDescription
seekrit changes list [--status <status>] [--all] [--limit <n>] [--org <slug>]List change requests, open ones by default. Aliased as changes ls.
seekrit changes show <id> [--org <slug>]One change request and its reviews: what is changing, who proposed it, which version it replaces. Never a value — reviewing is keyless.
seekrit changes approve <id> [-m <comment>] [--org <slug>]Approve. The approval that meets the threshold applies the change in the same call, so this can answer applied — or superseded, if the secret moved on since the change was proposed.
seekrit changes reject <id> [-m <comment>] [--org <slug>]Reject. One rejection closes the change; there is no rejection quorum.
seekrit changes withdraw <id> [--org <slug>]Take your own proposal off the queue without deciding it. Admins may withdraw anyone's.
seekrit changes protection show [--app <slug>] [--env <slug>]Whether this environment requires approval, and how many changes are waiting.
seekrit changes protection on [--env <slug>] [--approvals <n>] [--allow-self-approval]Require approval before secrets here change. Admin only, audited.
seekrit changes protection off [--env <slug>]Stop requiring approval. Queued changes stay queued. Admin only, audited.

Approving requires a signed-in admin: a service token cannot review, because whatever holds one can already write.

Honey tokens

Decoy credentials that unlock nothing and alert your admins the moment anyone presents one. See the honey tokens guide.

CommandDescription
seekrit honey-token create --name <name> [--placement <note>] [--org <slug>]Mint a decoy credential and print it once (stdout), so it can be piped straight into the file or variable you're baiting. --placement records where you planted it and is repeated back in the alert email.
seekrit honey-token list [--org <slug>]List decoys with trip count, placement, and the time and source IP of the last trip. Aliased as honey-token ls.
seekrit honey-token delete <honeyTokenId> [--yes] [--org <slug>]Delete a decoy, which stops it alerting — pull the planted bait too. Trips already recorded stay in the audit log. Aliased as honey-token rm.

A decoy is byte-for-byte indistinguishable from a real skt_ token, and presenting one returns exactly the same 401 unknown service token an unregistered token gets — so an attacker probing credentials can't tell bait from a typo. Plant them where a thief would look, never anywhere your own tooling reads: a deploy script that tries one by mistake trips the alarm just as loudly.

Agent integration

seekrit mcp

Run an MCP server over stdio so AI agents (Claude Code and other MCP clients) can drive seekrit as tools. It reads the same credentials as every other command — a SEEKRIT_TOKEN, machine credentials (SEEKRIT_CLIENT_ID + SEEKRIT_CLIENT_SECRET, which auto-mint an admin token), or the saved config.

# Register with Claude Code (token selects the org; admin token enables provisioning):
SEEKRIT_TOKEN=skt_… claude mcp add seekrit -- seekrit mcp

For a fully autonomous agent, pass machine credentials instead — the server mints and caches its own admin token from them (see the AI agents guide):

claude mcp add seekrit \
  --env SEEKRIT_CLIENT_ID=… --env SEEKRIT_CLIENT_SECRET=… -- seekrit mcp

All decryption happens locally, in this process — the server exposes the tools that touch plaintext (secret values, data keys, decryption-capable grants), which is why it runs on your machine rather than a hosted endpoint. Call get_started first — an in-protocol tool (and the server's instructions, shown to the model on connect) that returns the recommended first-project recipe so an agent landing here mid-context can orient itself. Other tools include create_org/create_app/create_env, set_secret/get_secret, create_token, grant_env, and run_command (inject secrets into a subprocess without returning their values). Because stdin is the transport, user-auth sessions that decrypt need SEEKRIT_PASSPHRASE set (token auth needs nothing extra). Prefer run_command over get_secret with reveal:true so plaintext never enters the agent's context.

seekrit paperclip init

Attach the seekrit MCP servers to a Paperclip agent, and print the environment variables to paste into its adapter config.

Paperclip is a control plane: its adapter launches the runtime, and the runtime is what speaks MCP. So there is no Paperclip-side switch that means "give this agent the seekrit tools" — the servers go in a project-scoped .mcp.json in the working directory the agent runs in, which is what this writes.

seekrit paperclip init --dir /srv/paperclip/workspaces/storefront \
  --preset anthropic --preset openai
FlagDefaultDescription
-d, --dir <path>.The agent's working directory (its Configuration tab shows it).
--preset <id…>Upstreams this agent calls, for the printed adapter env. Same catalogue as seekrit proxy presets.
--mode <forward|reverse>forwardWhich proxy mode the printed env assumes.
--listen <addr>127.0.0.1:8080Reverse-mode proxy address.
--forward-listen <addr>127.0.0.1:8081Forward-mode proxy address.
--no-mcpPrint the adapter env only, without writing .mcp.json.
--forceReplace a seekrit entry that already differs.

An existing .mcp.json is merged, not replaced: other servers and unknown top-level keys survive, and a seekrit entry someone customised (a pinned version, an added env) is left alone unless you pass --force.

Every printed value is a plain string — a placeholder is not a secret, so none of it needs a Paperclip secret_ref and none of it trips PAPERCLIP_SECRETS_STRICT_MODE. The proxy config itself stays with seekrit proxy init, which the output points you at.

seekrit openclaw init

Add the seekrit exec SecretRef provider to an OpenClaw gateway, so openclaw.json names credentials instead of holding them.

seekrit openclaw init --write
FlagDefaultDescription
--config <path>$OPENCLAW_HOME/openclaw.jsonWhich gateway config to target.
--pluginonPoint at an installed @seekrit/openclaw-plugin.
--execPoint straight at this CLI, with no plugin installed.
--writeWrite the file instead of printing the block to paste.
--forceReplace a seekrit provider entry that already differs.

openclaw.json is JSON5, so a file carrying comments or trailing commas is printed to, never written — a rewrite through a JSON round trip would delete every comment in it. A file that is also valid strict JSON is merged: other providers and unknown top-level keys survive, and a seekrit entry someone customised is left alone unless you pass --force.

With --exec, the generated command is an absolute path to the Node binary and the first argument is this CLI's own entry file. That is deliberate: OpenClaw refuses a symlinked command, and the seekrit on your PATH is a package-manager shim that symlinks into node_modules.

seekrit openclaw ref

Print the SecretRef object to paste in place of a plaintext credential.

seekrit openclaw ref OPENAI_API_KEY
seekrit openclaw ref STRIPE_SECRET_KEY --app billing-api --env production
FlagDescription
--app <slug>Address a specific application (with --env).
--env <slug>Address a specific environment (with --app).

seekrit openclaw resolve

The exec SecretRef protocol handler: reads a JSON request on stdin and writes the resolved values on stdout. OpenClaw calls this — there is no reason to run it by hand, and it takes no flags precisely because the gateway passes none.

It resolves each distinct environment once per batch, skips .env overlays entirely (the working directory belongs to the gateway, so a stray .env must not override a gateway credential), and reports a failure as a per-id error code with no message — resolver output may contain credentials, so OpenClaw declines to display it.

seekrit hermes init

The hermes commands wire seekrit into a Hermes Agent as a secret source. The source itself is Python and ships in the Python SDK (pip install seekrit); these two commands cover the parts that are not.

Print the secrets: block for ~/.hermes/config.yaml, and optionally scaffold the plugin directory.

seekrit hermes init
seekrit hermes init --mapped \
  --bind OPENAI_API_KEY=OPENAI_KEY \
  --bind STRIPE_SECRET_KEY=billing/STRIPE_SECRET_KEY \
  --token billing=SEEKRIT_TOKEN_BILLING
FlagDefaultDescription
--mappedUse the seekrit_refs source (explicit VAR → secret bindings) instead of whole-environment seekrit.
--token-env <var>SEEKRIT_TOKENVariable holding the service token.
--bind <VAR=NAME…>A mapped binding. VAR=alias/NAME resolves through a second token.
--token <alias=VAR…>Name a second token — the value is a variable name, never a token.
--plugin-dirAlso scaffold $HERMES_HOME/plugins/seekrit, for a Hermes install that cannot see the SDK's entry point.
--home <path>$HERMES_HOME or ~/.hermesWhere Hermes lives.

config.yaml is printed, not written: it is YAML belonging to somebody's agent, with comments and anchors this CLI carries no parser for. The scaffolded plugin files are written, because those two files are entirely ours — and __init__.py re-exports register from the installed SDK rather than copying the implementation, so it never has to be regenerated.

seekrit hermes ref

Print the skt:// reference to bind a Hermes variable to.

seekrit hermes ref OPENAI_KEY
seekrit hermes ref STRIPE_SECRET_KEY --token-alias billing
FlagDescription
--token-alias <alias>Resolve through a second token named in the source's tokens: map.

A reference names a token and a secret, not an app and environment: a skt_ service token is bound to one environment, so an skt://app/env/NAME spelling would be a reference nothing on the read path could resolve.

Temporary Postgres credentials (pg)

Mint short-lived database logins that auto-expire. Minting happens client-side: the password and its SCRAM verifier are generated on your machine and only the verifier is sent, so the plaintext never reaches seekrit or Postgres at rest. See Temporary access.

CommandDescription
seekrit pg target add --name <name> --host <h> --database <db> [--access readonly|readwrite|custom] [--schema public] [--port 5432] [--executor in_do|remote] [--provisioner-url <url>] [--hmac-key <base64>] [--admin-url <url>] [--create-statement <sql>]…Register a provisioning target. --access (default readonly) sets what leased credentials can do; for the presets the command prints a one-time group-role setup query to run as admin. custom uses --create-statement/--revoke-statement. For --executor in_do, the admin connection string (or SEEKRIT_PG_ADMIN_URL) is wrapped to the broker locally; for --executor remote, pass the shared HMAC key via --hmac-key (or SEEKRIT_PROVISIONER_HMAC_KEY) instead — see the self-hosted provisioner guide. (admin)
seekrit pg target list [--org <slug>]List targets (id, name, connection, access level, executor).
seekrit pg target setup-sql <targetId> [--org <slug>]Reprint the group-role setup SQL for a preset target.
seekrit pg target rm <targetId> [--yes] [--org <slug>]Remove a target.
seekrit pg lease <target> [--role <name>] [--ttl 1h] [--json]Mint a credential; prints a ready-to-use postgres:// URL (the password is shown once and stored nowhere). --ttl accepts 30m/1h/7d.
seekrit pg leases [--org <slug>]List the lease ledger (status, role, expiry).
seekrit pg revoke <leaseId> [--yes] [--org <slug>]Revoke a lease now (drops the role immediately).
# Register a read-only target (prints the group-role setup SQL to run once):
SEEKRIT_PG_ADMIN_URL=postgres://admin:…@db.example.com:5432/app \
  seekrit pg target add --name prod-db --host db.example.com --database app --access readonly

# Lease a 30-minute credential and hand the URL straight to psql:
psql "$(seekrit pg lease prod-db --ttl 30m)"

Temporary MySQL / MariaDB credentials (mysql)

The MySQL/MariaDB analog of pg. Minting is client-side too: the password and its mysql_native_password hash are generated on your machine and only the hash is sent, so the plaintext never reaches seekrit or MySQL at rest. Presets apply their GRANTs inline per user (no one-time setup step), and there is no account-level expiry — the broker drops the user at the deadline. See Temporary access.

CommandDescription
seekrit mysql target add --name <name> --host <h> --database <db> [--access readonly|readwrite|custom] [--user-host %] [--port 3306] [--executor in_do|remote] [--provisioner-url <url>] [--hmac-key <base64>] [--admin-url <url>] [--create-statement <sql>]…Register a provisioning target. --access (default readonly) sets what leased credentials can do; custom uses --create-statement/--revoke-statement. --user-host is the host part of created accounts ('name'@'<host>', default %). For --executor in_do, the admin connection string (or SEEKRIT_MYSQL_ADMIN_URL) is wrapped to the broker locally; for --executor remote, pass the shared HMAC key via --hmac-key (or SEEKRIT_PROVISIONER_HMAC_KEY) instead — see the self-hosted provisioner guide. (admin)
seekrit mysql target list [--org <slug>]List MySQL targets (id, name, connection, access level, executor).
seekrit mysql target rm <targetId> [--yes] [--org <slug>]Remove a target.
seekrit mysql lease <target> [--user <name>] [--ttl 1h] [--json]Mint a credential; prints a ready-to-use mysql:// URL (the password is shown once and stored nowhere). --ttl accepts 30m/1h/7d.
seekrit mysql leases [--org <slug>]List the MySQL lease ledger (status, user, expiry).
seekrit mysql revoke <leaseId> [--yes] [--org <slug>]Revoke a lease now (drops the user immediately).
# Register a read-only target (no setup SQL needed — grants apply inline):
SEEKRIT_MYSQL_ADMIN_URL=mysql://admin:…@db.example.com:3306/app \
  seekrit mysql target add --name prod-db --host db.example.com --database app --access readonly

# Lease a 30-minute credential and hand the URL straight to the mysql client:
mysql "$(seekrit mysql lease prod-db --ttl 30m)"

Temporary Redis credentials (redis)

The Redis (6+) analog of pg/mysql. Minting is client-side too: the password and its SHA-256 digest are generated on your machine and only the digest is sent (ACL SETUSER … on #<digest>), so the plaintext never reaches seekrit or Redis at rest. Presets apply their ACL rules inline per user (no one-time setup step), and there is no account-level expiry — the broker deletes the ACL user at the deadline. See Temporary access.

CommandDescription
seekrit redis target add --name <name> --host <h> [--access readonly|readwrite|custom] [--port 6379] [--db <index>] [--executor in_do|remote] [--provisioner-url <url>] [--hmac-key <base64>] [--admin-url <url>] [--create-statement <cmd>]…Register a provisioning target. --access (default readonly) sets what leased credentials can do; custom uses --create-statement/--revoke-statement (each a Redis command line, {{name}}/{{verifier}} templated). --db is the logical database index used in the printed URL. For --executor in_do, the admin redis:///rediss:// connection string (or SEEKRIT_REDIS_ADMIN_URL) is wrapped to the broker locally; for --executor remote, pass the shared HMAC key via --hmac-key (or SEEKRIT_PROVISIONER_HMAC_KEY) instead — see the self-hosted provisioner guide. (admin)
seekrit redis target list [--org <slug>]List Redis targets (id, name, connection, access level, executor).
seekrit redis target rm <targetId> [--yes] [--org <slug>]Remove a target.
seekrit redis lease <target> [--user <name>] [--ttl 1h] [--json]Mint a credential; prints a ready-to-use redis:// URL (the password is shown once and stored nowhere). --ttl accepts 30m/1h/7d.
seekrit redis leases [--org <slug>]List the Redis lease ledger (status, user, expiry).
seekrit redis revoke <leaseId> [--yes] [--org <slug>]Revoke a lease now (deletes the ACL user immediately).
# Register a read-only target (ACL rules apply inline, no setup step):
SEEKRIT_REDIS_ADMIN_URL=rediss://default:…@cache.example.com:6379 \
  seekrit redis target add --name prod-cache --host cache.example.com --access readonly

# Lease a 30-minute credential and hand the URL straight to redis-cli:
redis-cli -u "$(seekrit redis lease prod-cache --ttl 30m)"

Agent egress proxy (proxy)

Fetch, configure, and run seekrit-proxy without a Rust toolchain and without hand-writing a config file. The proxy itself is unchanged by these commands — they resolve the released binary (verifying its SHA-256) and generate the same TOML you would otherwise write.

CommandDescription
seekrit proxy runFetch the binary if needed and run it. With --preset/--host/--agent it generates a config on the fly and leaves nothing behind.
seekrit proxy initWrite a reviewable seekrit-proxy.toml.
seekrit proxy presetsList the ready-made upstream presets.
seekrit proxy composePrint a docker compose sidecar snippet for a generated config.
seekrit proxy installDownload the binary and print its path.
seekrit proxy whereShow which binary run would use, without fetching it.
seekrit proxy approvalsList requests a running proxy is holding for approval.
seekrit proxy approve <id>Let a held request through.
seekrit proxy deny <id>Refuse a held request.
seekrit proxy always <id>Let it through and stop asking about that host and method for the rest of the run.

Generating a config

init and run share these flags. Pass presets, ad-hoc hosts, or an agent identity — the last takes the rules from published policy and cannot be combined with the first two, because server-policy mode rejects local rules rather than silently ignoring them.

FlagDefaultDescription
--preset <name>A preset from seekrit proxy presets (repeatable).
--host <host[=SECRET,…]>Ad-hoc rule: a bare hostname and, optionally, the secrets that may reach it (repeatable). Omitting =SECRET permits the operation without letting a credential travel with it.
--base-url <url>Upstream base URL for an OpenAI-compatible gateway. Required by the openai-compatible preset.
--secret <NAME>preset's ownOverride a preset's secret name.
--prefix <path>preset's ownOverride a preset's route prefix.
--agent <slug>Take the rules from published agent policy (server mode).
--agents <slug>the --agent oneAdditional identities this proxy may serve, for session tickets (repeatable).
--org <slug>Organization, for --agent.
--mode <reverse|forward|both>reverseWhich data plane(s) to configure.
--listen <addr>127.0.0.1:8080Reverse-proxy address.
--forward-listen <addr>127.0.0.1:8081Forward-proxy address.
--unmatched <tunnel|deny>tunnelWhat to do with an unruled host in forward mode.
--ca-cert <path> / --ca-key <path>seekrit-proxy-ca[-key].pemInterception CA paths (forward mode).
--cacheoffAdd a [cache] block so the proxy can start during a seekrit outage.
--cache-max-age <dur>24hHow stale a cached resolve may be (implies --cache).
--refresh <dur>30s file / 10s serverRe-resolve (and, in server mode, re-fetch) interval.
--control <addr|path>Add a [control] listener for per-agent session tickets. A path makes it a Unix socket — the only transport whose caller can be attested — and the generated block carries a commented allow_binary line.
--tasksoffAdd a [tasks] block, so the proxy honours runs dispatched with seekrit agents dispatch.
--tasks-cache-ttl <dur>30sHow long an introspected task is reused (implies --tasks). This bounds how long a revoked run keeps working, so it is capped at 5 minutes.
--activityoffAdd an [activity] block, so the proxy reports aggregate decisions for seekrit agents review.
--activity-interval <dur>60sHow often counts are flushed (implies --activity). Minimum 10s.

init additionally takes -o, --out <path> (default ./seekrit-proxy.toml), --print to write to stdout instead, and --force to overwrite.

run additionally takes -c, --config <path>, --proxy-version <version>, and --print-config. With no generation flags it runs the config file as-is.

Deciding a held request

[approval] in the proxy's config marks operations that stop for a human before they are dispatched — see stop for a human. A proxy attached to a terminal prompts there; these commands are for every other case.

seekrit proxy approvals
ID            OPERATION        REQUEST                              CARRIES              EXPIRES
apr_00000003  money movement   POST api.stripe.com/v1/charges       STRIPE_SECRET_KEY    98s
seekrit proxy approve apr_00000003
FlagDefaultDescription
--control <url|path>http://127.0.0.1:9090The proxy's [control] listener. A filesystem path (or unix:/path) dials its Unix socket instead, which is the transport whose caller can be attested.
--jsonMachine-readable output.

They talk to the proxy's [control] listener, so that block has to be configured and SEEKRIT_PROXY_CONTROL_TOKEN has to be set to the same value the proxy was started with. The token is read from the environment and has no flag: it is what decides whether a held request goes through, and a flag would put it in shell history beside the operation it is guarding.

note

The agent cannot approve its own request. The pending id is never sent to the workload, and the listener needs a token the workload does not have — though an agent spawned by the orchestrator inherits its environment, so on a shared machine pair this with caller attestation.

Resolving the binary

VariableDefaultPurpose
SEEKRIT_PROXY_BINPath to a binary you already have. Skips the download entirely.
SEEKRIT_PROXY_VERSIONthe CLI's pinned versionVersion to fetch. latest is re-resolved every run rather than cached.
SEEKRIT_PROXY_BASE_URLhttps://proxy.seekrit.devWhere artifacts come from.

Downloads are cached under $XDG_CACHE_HOME/seekrit/proxy/v<version>/<target>/, so the fetch happens once per version. A binary already on PATH is deliberately not used: silently running a different version than the one the CLI pins is the kind of surprise that costs an afternoon.

note

seekrit proxy never sees a secret value — it generates config and launches a process. The proxy resolves and decrypts on its own, with its own SEEKRIT_TOKEN, which is also why that token must live somewhere the workload cannot read.

Agent access policy (agents)

Author, review, and publish the agent access policy the proxy above enforces. Everything the dashboard's Agents screen does, from a terminal — including signing, which still happens on this machine with your own key, never on the server.

CommandDescription
seekrit agents listAgent identities, their live policy version, and when a proxy last fetched it.
seekrit agents create <name> --slug <slug>Create an identity. It holds no key material, so creating one grants nothing.
seekrit agents show <ref>An identity, its live rules, and the hosts a forward proxy would intercept.
seekrit agents disable <ref>Stop serving this identity's policy — the revocation path.
seekrit agents enable <ref>Serve it again.
seekrit agents rm <ref>Delete an identity and its published history.
seekrit agents signerYour signing thumbprint, with the [policy] signers line to pin.
seekrit agents simulate <ref>Ask what a policy would decide, without sending a request.
seekrit agents dispatch <ref>Mint task-scoped authority for one agent run.
seekrit agents tasks <ref>Runs dispatched for an identity.
seekrit agents revoke <taskId>End a run's authority now.
seekrit agents activity <ref>What the agent actually did — aggregate decisions, not a request log.
seekrit agents review <ref>Compare published policy against observed activity, and propose changes.

<ref> is a slug or an id, so nova works everywhere agt_… does.

Dispatching a run

Policy says what an agent may do; a task says what this run may do, and expires on its own. The token is printed to stdout and everything else to stderr, so the common thing needs no parsing:

TASK=$(seekrit agents dispatch nova --scope GITHUB_TOKEN --ttl 15m --label "triage 412")

The run then presents it in x-seekrit-ticket, the same header a locally-minted proxy ticket uses — an enforcement point tells them apart by prefix (skd_ was dispatched by seekrit, skp_ was minted by the proxy's own control listener).

CommandDescription
seekrit agents dispatch <ref>Mint task-scoped authority for one run. Prints the token once.
seekrit agents tasks <ref>Runs dispatched for an identity — live ones by default, --all for the history.
seekrit agents revoke <taskId>End a run's authority now.
FlagDefaultDescription
--scope <NAME>the agent's full policyNarrow this run to one secret (repeatable). Only names the published policy already permits; anything else is refused by name.
--ttl <duration>15mHow long the run stays authorized (1m–12h).
--label <text>What this run is for. Recorded in the audit trail — never put a secret in it.
--allFor tasks: include expired and revoked runs.

The token is shown once. seekrit stores only its hash, so a lost token cannot be recovered — dispatch another and revoke the first.

caution

Dispatching needs an admin service token, an M2M client, or an org admin: a runtime (member) token is refused, so an agent cannot dispatch its own authority. The flip side is that whoever can dispatch can dispatch for any identity their credential reaches — so the dispatching credential belongs to the orchestrator, not to the agents it starts.

Policy as code (agents policy)

CommandDescription
seekrit agents policy list <ref>Published versions, newest first.
seekrit agents policy show <ref>The rules in a version (live by default).
seekrit agents policy pull <ref>Write a version's rules to a JSON file you can edit, review, and commit.
seekrit agents policy publish <ref> -f <file>Sign a rule file with your own key and publish it.
seekrit agents policy rollback <ref> <version>Republish an earlier version's bundle as the newest one.
seekrit agents policy fetch <ref>The bundle a proxy would fetch, with its signer re-derived locally.

The loop the pull/publish pair exists for:

seekrit agents policy pull nova -o nova.policy.json

Edit it, commit it, get it reviewed like any other change, then:

seekrit agents policy publish nova -f nova.policy.json --ttl 7d

publish prints a diff against the live version and asks before signing (-y skips the question). -f - reads the rules from stdin. A rule file is either a bare JSON array or the { "rules": [...] } object pull writes, validated against the same schema the dashboard and API use — so a typo fails before anything is signed.

FlagDefaultDescription
--ttl <duration>7dHow long the bundle stays valid (1h–90d). Expiry bounds how long revoked policy keeps working.
--version <n>liveFor show and pull: a specific published version.
-o, --out <path>stdoutFor pull: where to write the rules.
-f, --file <path>For publish: the rule file, or - for stdin.
--bundleFor fetch: print the raw ap1. envelope and nothing else.

Reviewing a grant against what happened

Policy drifts in both directions: rules nobody exercises stay forever, and rules the agent needs never get added. review closes that loop from the evidence a proxy reported.

seekrit agents review nova --days 14
nova — policy v7, 4,983 decision(s) over 14d

proposals
- rule 1 (api.openai.com) permits ANTHROPIC_API_KEY, never injected in 4102 permitted request(s)
- rule 3 (api.github.com) never matched a request, while other rules did
? 12 request(s) to hooks.slack.com matched no rule (POST) — the agent wants this and cannot have it

2 narrowing change(s) can be applied; 1 need a human decision

- is a narrowing review will apply; ? needs a person. Write the narrowed rules out and publish them through the normal path:

seekrit agents review nova -o nova.policy.json
FlagDefaultDescription
--days <n>14How far back to look (1–90).
-o, --out <path>Write the narrowed rules to a file, ready for policy publish.

seekrit agents activity nova shows the raw evidence — hosts, methods, decisions, which rule decided, and which secrets were actually injected.

caution

Widening is never applied. A ? proposal is surfaced and never written to the file, even with --out. A review that could broaden policy from observed traffic would let a prompt-injected agent earn its own permissions by knocking often enough. Narrowing is safe to automate; widening is a human decision — and publishing still needs a human's signing key either way.

note

With no permitted traffic on the record, review proposes nothing at all: an empty window is evidence about nothing, and the alternative would be proposing to delete a working policy the day you switch reporting on. A rule that never matched is only called unused when other rules did.

Checking a policy before an agent hits it

simulate runs the same evaluator the proxy compiles in, so a dry run and a real refusal agree — including which rule decided and which constraint refused.

seekrit agents simulate nova --host api.stripe.com --method POST --path /v1/charges --secret STRIPE_SECRET_KEY

It exits non-zero on a denial, which is what makes it usable as a policy test in CI:

DENY  POST api.stripe.com/v1/charges [STRIPE_SECRET_KEY]
  v3: this host has rules, but none cover this path
caution

Publishing needs a human's signing key: a service token is refused, so an agent cannot widen its own policy. Which means anything that can read SEEKRIT_PASSPHRASE can sign — leave it unset on any machine an agent shares, and let the passphrase be typed.

HashiCorp Nomad (nomad)

Helpers for the Nomad secret provider plugin — the seekrit-nomad-secrets binary a Nomad client execs to resolve a job's secret block. The plugin does the decrypting; these commands install it, print the configuration, and prove it works.

caution

Nomad fails silently on every mistake here: a plugin under the wrong filename, an agent with no common_plugin_dir, or one that has not restarted since the install all produce a provider that simply does not exist. That is what seekrit nomad check is for.

seekrit nomad init

Print the agent stanza, the plugin config, and a job snippet — filled in for the machine you run it on.

seekrit nomad init --profile billing-prod --namespace billing
seekrit nomad init --profile billing-prod --namespace billing --write
FlagDefaultDescription
--plugin-dir <path>first existing of /opt/nomad/plugins, /var/lib/nomad/pluginsThe agent's client.common_plugin_dir.
--profile <name>defaultProfile name jobs reference in path.
--token-file <path>/etc/seekrit/tokens/<profile>Where the profile's service token lives.
--namespace <ns>Restrict the profile to a Nomad namespace. Repeatable; a trailing * globs.
--job <id>Restrict the profile to a Nomad job id. Repeatable.
--config <path>/etc/seekrit/nomad.jsonPlugin config path.
--writeWrite the config file (mode 0600) instead of printing it.

Without --namespace or --job, the profile is reachable by every job on that client — which is a weaker boundary than a job file suggests. The config is written 0600 because it names token files and decides which jobs reach them; the plugin refuses to read it if it is group- or world-writable.

seekrit nomad install

Download the plugin onto this Nomad client, verify its SHA-256, and place it where Nomad will find it.

sudo seekrit nomad install
FlagDefaultDescription
--plugin-dir <path>autodetectedThe agent's client.common_plugin_dir.
--version <version>the version this CLI pinsPlugin version to install.
--base-url <url>https://nomad.seekrit.devArtifact host.

The binary is installed as <plugin-dir>/secrets/seekrit, not under its artifact name: Nomad matches a job's provider value against the executable's filename. The checksum is verified before anything is written, and the file is renamed into place atomically so Nomad can never fingerprint a partial download.

Restart the agent afterwards — plugins are fingerprinted at client startup.

seekrit nomad check

Run the installed plugin exactly the way Nomad does, including the namespace and job id it would pass.

seekrit nomad check --path billing-prod --namespace billing --job billing-api
FlagDefaultDescription
--plugin-dir <path>autodetectedThe agent's client.common_plugin_dir.
--path <path>(empty)The path a job would use: <profile> or <profile>/<NAME>.
--namespace <ns>Pretend to be a job in this Nomad namespace.
--job <id>Pretend to be this Nomad job id.
--config <path>the installed oneCheck a config file before deploying it.

Reports whether the plugin is installed, whether it fingerprints, and how many secrets a fetch returned — a count, never names or values. Exits non-zero if any step fails, so it drops into a provisioning run.

Self-hosted provisioner (provisioner)

Helpers for the remote executor — the seekrit-provisioner daemon that runs a target's provisioning SQL inside your own network, so seekrit never sees the database admin credential.

CommandDescription
seekrit provisioner keygenGenerate a shared HMAC key (base64) for a remote target. Use the same value for --hmac-key when registering the target and for the daemon's SEEKRIT_PROVISIONER_HMAC_KEY.

Temporary SSH access (ssh)

Issue short-lived SSH certificates. seekrit acts as a certificate authority: target add generates a CA keypair locally (only the private half is wrapped and uploaded) and prints the public key to install on your hosts. Minting generates an ephemeral keypair on your machine and sends only the public key; the signed certificate comes back and the private key never leaves. See Temporary access.

CommandDescription
seekrit ssh target add --name <name> [--host <h>] [--user <login>] [--principal <name>]… [--extension <name>]… [--max-ttl <dur>]Create an SSH CA target. Generates the CA locally, wraps its private key to the broker, and prints the CA public key + one-time host setup (TrustedUserCAKeys). --principal allow-lists which login users a cert may request (blank = any); --host/--user seed the printed ssh command. (admin)
seekrit ssh target list [--org <slug>]List SSH targets (id, name, host, allowed principals).
seekrit ssh target setup <targetId> [--org <slug>]Reprint the host setup instructions for a target.
seekrit ssh target rm <targetId> [--yes] [--org <slug>]Remove a target.
seekrit ssh lease <target> [--principal <name>]… [--ttl 1h] [--out <dir>] [--json]Mint a certificate; writes id_ed25519 + id_ed25519-cert.pub and prints a ready-to-run ssh command. --principal defaults to the target's user/allow-list. --ttl accepts 30m/1h/8h.
seekrit ssh leases [--org <slug>]List the SSH lease ledger (status, expiry).
seekrit ssh revoke <leaseId> [--yes] [--org <slug>]Mark a lease revoked in the ledger. The issued certificate stays valid until it expires (short TTLs are the control).
# Create a CA target and install the printed CA key on your hosts:
seekrit ssh target add --name prod-fleet --host bastion.example.com --user deploy --principal deploy

# Issue an 8-hour cert, then run the ssh command it prints:
seekrit ssh lease prod-fleet --ttl 8h

Temporary AWS credentials (aws)

Mint short-lived AWS credentials via STS AssumeRole (a tier-2 provider — the credential comes back wrapped to your machine's ephemeral key, never in the clear through seekrit). target add registers one assumable IAM role and wraps a base IAM credential (needs only sts:AssumeRole) to the broker. Minting generates an ephemeral P-256 keypair locally, sends only the public key, and unwraps the returned credential on your machine. See Temporary access.

CommandDescription
seekrit aws target add --name <name> --role-arn <arn> --region <region> [--external-id <id>] [--session-policy <file>] [--max-ttl <dur>] [--access-key-id <id>] [--secret-access-key <secret>]Register an AWS role target. The base IAM credential comes from the flags or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY (and AWS_SESSION_TOKEN if set) and is wrapped to the broker locally. --session-policy further restricts the leased credential; --max-ttl clamps requested lifetime. Prints the IAM trust policy the role needs. (admin)
seekrit aws target list [--org <slug>]List AWS targets (id, name, region, role ARN).
seekrit aws target trust <targetId> [--org <slug>]Reprint the IAM trust-policy setup for a target.
seekrit aws target rm <targetId> [--yes] [--org <slug>]Remove a target.
seekrit aws lease <target> [--ttl 1h] [--json]Mint credentials; prints export AWS_… lines (or JSON with --json). --ttl accepts 15m–12h (STS minimum is 15 minutes; clamped to the role's MaxSessionDuration).
seekrit aws leases [--org <slug>]List the AWS lease ledger (status, expiry).
seekrit aws revoke <leaseId> [--yes] [--org <slug>]Mark a lease revoked in the ledger. The issued credential stays valid until it expires (short TTLs are the control).
# Register a role (base creds from your AWS env vars), then source a 1h credential:
seekrit aws target add --name prod-deploy \
  --role-arn arn:aws:iam::123456789012:role/seekrit-deploy --region us-east-1

eval "$(seekrit aws lease prod-deploy --ttl 1h)"
aws sts get-caller-identity

Temporary GCP credentials (gcp)

Mint short-lived GCP access tokens via IAM generateAccessToken (a tier-2 provider — the token comes back wrapped to your machine's ephemeral key, never in the clear through seekrit). target add registers one impersonable service account and wraps a base service-account key (needs roles/iam.serviceAccountTokenCreator on the target) to the broker. Minting generates an ephemeral P-256 keypair locally, sends only the public key, and unwraps the returned token on your machine. See Temporary access.

CommandDescription
seekrit gcp target add --name <name> --service-account <email> [--scope <scope>…] [--delegate <email>…] [--max-ttl <dur>] [--key-file <path>]Register a GCP service-account target. The source SA key JSON comes from --key-file or GOOGLE_APPLICATION_CREDENTIALS and is wrapped to the broker locally. --scope (repeatable) sets the OAuth scopes (default cloud-platform); --max-ttl clamps requested lifetime. Prints the IAM binding the target needs. (admin)
seekrit gcp target list [--org <slug>]List GCP targets (id, name, service account).
seekrit gcp target setup <targetId> [--org <slug>]Reprint the IAM setup for a target.
seekrit gcp target rm <targetId> [--yes] [--org <slug>]Remove a target.
seekrit gcp lease <target> [--ttl 1h] [--json]Mint a token; prints export CLOUDSDK_AUTH_ACCESS_TOKEN / export GOOGLE_OAUTH_ACCESS_TOKEN lines (or JSON with --json). --ttl accepts 1m–12h (tokens over 1h need the credential-lifetime-extension org policy).
seekrit gcp leases [--org <slug>]List the GCP lease ledger (status, expiry).
seekrit gcp revoke <leaseId> [--yes] [--org <slug>]Mark a lease revoked in the ledger. The issued token stays valid until it expires (short TTLs are the control).
# Register a service account (source key from GOOGLE_APPLICATION_CREDENTIALS),
# then source a 1h token:
seekrit gcp target add --name prod-deploy \
  --service-account deploy@my-project.iam.gserviceaccount.com

eval "$(seekrit gcp lease prod-deploy --ttl 1h)"
gcloud storage ls

Temporary MongoDB credentials (mongodb)

Mint short-lived MongoDB users (a tier-2 provider — MongoDB hashes the password server-side, so the broker generates it, runs createUser, and returns the credential wrapped to your machine's ephemeral key, never in the clear through seekrit). target add registers one cluster and wraps an admin mongodb:// connection string (needs userAdmin on the database) to the broker. Minting generates an ephemeral P-256 keypair locally, sends only the public key, and unwraps the returned credential on your machine. Unlike AWS, revoke is real — it drops the user. See Temporary access.

CommandDescription
seekrit mongodb target add --name <name> --database <db> [--uri <uri>] [--access readonly|readwrite|custom] [--role <role@db>…] [--auth-source <db>] [--max-ttl <dur>] [--no-tls]Register a MongoDB target. The admin connection string comes from --uri or SEEKRIT_MONGODB_ADMIN_URL and is wrapped to the broker locally. --access picks the built-in read/readWrite role on --database (or custom with one or more --role role@db); --max-ttl clamps requested lifetime. Prints the provisioning-user setup. (admin)
seekrit mongodb target list [--org <slug>]List MongoDB targets (id, name, host:port, database, access).
seekrit mongodb target rm <targetId> [--yes] [--org <slug>]Remove a target.
seekrit mongodb lease <target> [--ttl 1h] [--json]Mint a user; prints export MONGODB_URI='…' (or JSON with --json). --ttl accepts 60s–7d, clamped to the target's --max-ttl.
seekrit mongodb leases [--org <slug>]List the MongoDB lease ledger (status, expiry).
seekrit mongodb revoke <leaseId> [--yes] [--org <slug>]Revoke a lease now — drops the MongoDB user immediately.
# Register a cluster (admin creds from SEEKRIT_MONGODB_ADMIN_URL), then lease a 1h user:
export SEEKRIT_MONGODB_ADMIN_URL='mongodb://admin:pw@mongo.example.com:27017/?authSource=admin'
seekrit mongodb target add --name prod-app --database app --access readonly

eval "$(seekrit mongodb lease prod-app --ttl 1h)"
mongosh "$MONGODB_URI"

Managed keys (kms)

Client-side managed keys for application-layer encryption and signing. Material is generated locally and wrapped to grantees; the server never sees it. See the Managed keys guide.

CommandDescription
seekrit kms create --name <name> --purpose <encrypt|sign> [--org <slug>] [--app <slug>|--group <slug>] [--grant-user <email>…] [--grant-token <id>…]Create a key. Generates material locally, self-grants, and optionally grants others. encrypt = AES-256-GCM; sign = ECDSA P-256 (its public key is published). Scope to an app or group, or leave org-wide.
seekrit kms ls [--org <slug>]List keys you can see (admins see all; members see granted keys).
seekrit kms grant --key <name> (--user <email>|--token <id>) [--org <slug>]Grant a principal the current version.
seekrit kms revoke --key <name> (--user <email>|--token <id>) [--yes] [--org <slug>]Revoke a principal from the key (all versions).
seekrit kms rotate --key <name> [--org <slug>]Add a new version and re-wrap it for every current grantee. Old versions stay valid.
seekrit kms disable --key <name> [--yes] [--org <slug>]Block all use: encrypt, decrypt, sign, and new grants/rotations.
seekrit kms delete --key <name> [--yes] [--org <slug>]Delete a key — hides it from every listing and read path; the name frees up for reuse. Any ciphertext or signature still relying on it becomes unrecoverable.
seekrit kms encrypt --key <name> [--context <ctx>] [--org <slug>]Encrypt stdin → a ce1. blob. --context is bound as AAD and must match on decrypt.
seekrit kms decrypt --key <name> [--context <ctx>] [--org <slug>]Decrypt a ce1. blob from stdin.
seekrit kms generate-data-key --key <name> [--org <slug>]Print { plaintextBase64, wrapped } — an envelope data key for large payloads.
seekrit kms open-data-key --key <name> [--org <slug>]Recover a data key from a dk1. blob on stdin (prints base64).
seekrit kms sign --key <name> [--org <slug>]Sign stdin with a signing key → an sg1. signature.
seekrit kms verify --key <name> --signature <sg1…> [--org <slug>]Verify an sg1. signature over stdin (exit 0 = valid). Needs only the published public key.
# Encrypt a field, bound to its context, then read it back:
echo -n "$SSN" | seekrit kms encrypt --key pii --context "field=ssn" > ssn.enc
seekrit kms decrypt --key pii --context "field=ssn" < ssn.enc

# Sign a release and verify it:
echo -n "release-v1.2.3" | seekrit kms sign --key release-signer > r.sig
echo -n "release-v1.2.3" | seekrit kms verify --key release-signer --signature "$(cat r.sig)"

Secret rotation (rotation)

Managed, scheduled replacement of a stored secret's value — and, for the database kinds, of the password on the account it belongs to. (admin). Enabling rotation wraps the environment's data key to your org's rotator key locally; see the rotation guide and Secret rotation.

Every command accepts a rotation id (rot_…) or the secret's name when that name rotates in only one environment. Durations accept s/m/h/d suffixes (minimum 5 minutes, maximum 365 days).

CommandDescription
seekrit rotation enable <SECRET_NAME> --env <slug> --kind <generated|postgres|mysql|redis> --every <duration> [--app <slug>|--group <slug>] [--target <idOrName>] [--username <name>] [--user-host <host>] [--length <n>] [--alphabet <set>] [--now] [--org <slug>]Configure rotation for an existing secret. --target + --username are required for the database kinds and name an account that already exists. --alphabet (generated only) is alphanumeric, hex, base64url, or printable. --now also rotates immediately. Prints the rot_… id. (admin)
seekrit rotation list [--org <slug>] [--json]List policies: id, secret, kind, cadence, status, next run. Never values. (admin)
seekrit rotation show <rotationOrSecret> [--org <slug>]One policy in full, including lastError from the last failed attempt. (admin)
seekrit rotation rotate <rotationOrSecret> [--org <slug>]Rotate now — the same path the scheduler uses. Prints the new version. (admin)
seekrit rotation pause <rotationOrSecret> [--org <slug>]Stop rotating, keeping the policy. (admin)
seekrit rotation resume <rotationOrSecret> [--org <slug>]Resume rotating; also clears a failed streak. (admin)
seekrit rotation set-interval <rotationOrSecret> --every <duration> [--org <slug>]Change the cadence. (admin)
seekrit rotation disable <rotationOrSecret> [--yes] [--org <slug>]Remove the policy. The secret and its versions are untouched; the rotator's key grant is dropped when the environment has no rotation left. (admin)
# Re-key an existing Postgres role weekly, against a registered target:
seekrit rotation enable DATABASE_PASSWORD --app web --env production \
  --kind postgres --target prod-db --username app_user --every 7d --now

# A value only your own code checks — nothing external is contacted:
seekrit rotation enable API_SIGNING_KEY --app web --env production \
  --kind generated --every 30d

seekrit rotation list
seekrit rotation rotate DATABASE_PASSWORD   # e.g. after a suspected exposure

Customer-controlled recovery (recovery)

Org-scoped M-of-N recovery. The recovery key is generated and split locally; seekrit stores only its public half and shares it cannot open. Management is (admin); any custodian can approve a ceremony. See the recovery guide.

CommandDescription
seekrit recovery setup --threshold <M> --custodian <email|skt_id>… [--org <slug>]Enable recovery: split a fresh recovery key across the custodians, then cover the environments you can decrypt. (admin)
seekrit recovery status [--org <slug>]Show the threshold, custodians, and environment coverage. (admin)
seekrit recovery sync [--org <slug>]Recovery-protect environments you can decrypt but that aren't yet covered. (admin)
seekrit recovery rotate --threshold <M> --custodian <email|skt_id>… [--org <slug>]Rotate to a fresh recovery key and custodian set; re-wraps the environments you can decrypt. (admin)
seekrit recovery disable [--yes] [--org <slug>]Remove the recovery key and every recovery grant. (admin)
seekrit recovery request [--target-user <email>|--target-token <id>] [--reason <text>] [--org <slug>]Start a recovery ceremony (defaults to recovering access for yourself). Prints an rrq_… id. (admin)
seekrit recovery approve <requestId> [--org <slug>]As a custodian, unwrap your share and contribute it, re-wrapped to the target.
seekrit recovery complete <requestId> [--org <slug>]As the target, reconstruct the recovery key and restore your environment access. (admin)
seekrit recovery cancel <requestId> [--yes] [--org <slug>]Cancel an open recovery request. (admin)
# Enable 3-of-5 recovery, then check coverage:
seekrit recovery setup --threshold 3 \
  --custodian alice@example.com --custodian bob@example.com --custodian carol@example.com \
  --custodian dan@example.com --custodian erin@example.com
seekrit recovery status

# Run a ceremony: start it, custodians approve, then the target completes.
seekrit recovery request                 # prints rrq_…
seekrit recovery approve rrq_XXXXXXXX     # each custodian, until the quorum is met
seekrit recovery complete rrq_XXXXXXXX

Break-glass export (archive)

The whole org as one signed file, and the offline tools to open it. create is the only subcommand that talks to the API; info, verify, share, decrypt, and decryptor never touch the network and need no credentials or config, so they work on an air-gapped machine. See the export guide.

CommandDescription
seekrit archive create [--out <file>] [--no-versions] [--no-audit] [--audit-limit <n>] [--json] [--org <slug>]Download a signed archive of everything seekrit stores for the org, verify it, and write it to a file (default seekrit-<org>-<date>.json). (admin)
seekrit archive info <file> [--json]Manifest, per-section row counts, and verification. Offline.
seekrit archive verify <file> [--key-id <id>] [--skip-signature] [--json]Recompute every section digest and check the signature; exits non-zero on failure. Offline.
seekrit archive share <file> [--out <file>] [--token <skt_id>] [--key-file <path>]As a recovery custodian, unwrap your Shamir share from the archive for an offline quorum. Offline.
seekrit archive decrypt <file> (--out <dir>|--stdout) [--env <label>] [--format <dotenv|json|shell>] [--token <skt_…>] [--key-file <path>] [--share <file>…] [--yes]Decrypt what your key reaches — one file per environment, or stdout. Offline.
seekrit archive decryptor [--out <file>]Write the standalone HTML decryptor (no install, no network) to keep beside your archives. Offline.

decrypt takes its key from --token, --key-file, a quorum of --share files, or (by default) the archive's own keyMaterial unlocked with your passphrase — prompted, or SEEKRIT_PASSPHRASE. Environments your key holds no grant on are skipped, not failed.

# Export, verify against the published signing key, and open it offline.
seekrit archive create --out acme.json
seekrit archive verify acme.json --key-id 1ab3bd8b98170773
seekrit archive decrypt acme.json --out ./plaintext

# Break glass: two custodians each unwrap a share, then a quorum opens everything.
seekrit archive share acme.json --out share-ana.json          # custodian 1
seekrit archive share acme.json --token skt_… --out share-ci.json  # custodian 2
seekrit archive decrypt acme.json --share share-ana.json --share share-ci.json --out ./recovered

Your account

Your devices and your email preferences. Both are per-user, not per-org, so neither takes --org.

CommandDescription
seekrit session list [--all]The devices seekrit login has authorized, newest first, with the one you're calling from marked. Shows only live sessions unless you pass --all (which includes revoked and expired ones — the ledger is kept).
seekrit session revoke <sessionId> [--yes]Sign a device out; its token stops authenticating on its next request (revoking drops the cached copy as it commits, and any copy that outlives it is re-checked within a minute of use). Revoking the session you're currently using is allowed, and says so before it asks.
seekrit notificationsYour per-type email preferences, on or off.
seekrit notifications set <type> <on|off>Turn one notification on or off. An unknown type lists the valid ones.

Audit

seekrit audit

Print the org's audit trail — the append-only record of every mutation. Admins only.

FlagDescription
--org <slug>Which organization.
--limit <n>Entries per page (default 50, max 200).
--action <action>Only this action, e.g. env.key_granted. An unrecognized action is rejected rather than silently returning nothing.
--resource-type <type>Only this resource type, e.g. environment.
--cursor <cursor>Continue from the cursor a previous page printed.
--allPage through the whole trail rather than stopping after one page.
--metadataAdd each entry's metadata as a JSON column.

When more entries remain, the next cursor is printed to stderr so it doesn't corrupt piped output.

seekrit audit actions

List every action the trail can record — the vocabulary for --action.

# Everything that changed who can decrypt something, as JSON:
seekrit audit --action env.key_granted --all --json

Audit export (log-sink)

Ship every audit row to your own OTLP/HTTP collector (SIEM) within about a minute of it being written. Admins only. Header values are write-only — encrypted at rest and never returned — so show reports only the header names.

CommandDescription
seekrit log-sinkThe configured endpoint, whether it's enabled, the header names, and delivery health (last success, last attempt, last error).
seekrit log-sink set <endpoint> [--header "Name: value"] [--clear-headers] [--disabled]Point the export at an OTLP/HTTP logs endpoint. --header is repeatable and replaces the whole header set, so pass all of them each time. Passing none leaves the stored headers alone — that's how you change the endpoint without re-entering the credential; --clear-headers removes them.
seekrit log-sink testSend a probe and report the result. Exits non-zero on failure, so it works as a health check.
seekrit log-sink rm [--yes]Stop exporting and forget the endpoint.
seekrit log-sink set https://collector.example.com/v1/logs \
  --header "Authorization: Bearer $SIEM_TOKEN"
seekrit log-sink test

Third-party sync

Push an environment's resolved secrets to a platform that keeps its own copy: Vercel project env vars, Cloudflare Worker secret bindings, Cloudflare Pages env vars, Cloudflare Secrets Store secrets, Railway service variables, AWS Secrets Manager secrets, AWS SSM parameters, a Render service or environment group, a Fly.io app's secrets, a Northflank secret group, DigitalOcean App Platform variables, Heroku config vars, Netlify site variables, Bunnyshell environment or project variables, GitHub Actions secrets, or Google Secret Manager secrets. Admins only. Each destination has its own guide under Third-party sync.

caution

Sync is the one place seekrit's servers hold plaintext: a destination needs the value and runs when nobody is logged in, so the sync engine decrypts in memory for the length of a push. Enabling it for an environment is therefore an explicit, audited decision — sync enable requires --acknowledge-decryption (or a yes at the prompt), and who acknowledged is recorded. Where the runtime lets you decrypt on your own side instead, prefer seekrit run, the proxy, or the SDKs.

A connection is a destination account; a binding is one environment syncing to one place in it.

CommandDescription
seekrit sync connectionsDestination accounts, with status and last error.
seekrit sync connect --name <name> [--provider <kind>] [--team-id <id>] [--account-id <id>] [--token-kind <kind>] [--region <region>] [--access-key-id <id>] [--base-url <url>] [--project-id <id>] [--langgraph-region <region>] [--langgraph-tenant <id>] [--azure-tenant-id <id>] [--azure-client-id <id>] [--azure-cloud <cloud>]Register an account. The credential is read from stdin (or prompted) — never a flag, so it can't land in shell history — and is wrapped to the connection's public key before it is sent. --team-id is Vercel's (omit for a personal account); --account-id is required for every Cloudflare provider and for Netlify, where it is the team slug (or account ID) whose environment variables the connection writes; --token-kind is Railway's (account or project — Railway sends the two in different headers); --region and --access-key-id for every AWS one; --project-id is gcp-secret-manager's, the project whose Secret Manager the connection writes; --base-url is GitHub's and LangGraph Platform's, and only for a self-hosted install — a GitHub Enterprise Server appliance (https://github.acme.com/api/v3) or a self-hosted LangSmith control plane (https://langsmith.acme.com/api-host) — omit it for github.com, Enterprise Cloud, and a LangChain-hosted LangSmith, and note it must be https, since the URL carries the token; --langgraph-region picks which of LangChain's four control-plane hosts a langgraph-platform connection addresses (us by default, then eu, apac, aws-us) and matters because a key minted in one region is refused by another with a bare 401, so the wrong one looks exactly like a bad credential — it is mutually exclusive with --base-url; --langgraph-tenant is the LangSmith workspace UUID, needed only for an organization-scoped key, which reaches several workspaces and is refused without it; render, fly, northflank, digitalocean, heroku, bunnyshell, huggingface-spaces, and claude-managed-agents take none of them — a Render API key is user-scoped, a Northflank token names its own team, a DigitalOcean token its own account, a Heroku token its user's access to every app they can reach, a Bunnyshell token its user's access to every organization they belong to, a Hugging Face user access token its owner's write access to every Space they and their organizations own, an Anthropic API key its one workspace (where the vault must live), and a Fly token is pasted whole, FlyV1 prefix included, because seekrit reads which auth scheme it takes from the token itself. On AWS the credential read from stdin is the secret access key — the access key id is an identifier, not a secret, so it is stored in the clear where the dashboard can show it. On GCP it is the whole service-account key JSON, the same credential shape seekrit gcp target add takes, so it is usually piped from the key file (… < key.json). azure-key-vault splits the same way: --azure-tenant-id and --azure-client-id are the Entra directory and application IDs (both GUIDs, both identifiers), the credential read from stdin is the service principal's client secret, and --azure-cloud (public by default, or usgov / china) sets both the sign-in authority and the vault's DNS suffix — nothing in either ID reveals which cloud it belongs to.
seekrit sync verify <connection> [--provider <kind>] <destination flags>Check the stored credential against a destination. Takes the same destination flags as sync enable. Exits non-zero on failure.
seekrit sync disconnect <connection> [--yes]Delete an account, its bindings, and its keypair. Values already pushed stay on the destination.
seekrit sync bindingsWhat is syncing where, with mode, last run, and last error.
seekrit sync enable …Start syncing one environment (see below).
seekrit sync pause <bindingId> / resume <bindingId>Stop and restart pushing without deleting the binding.
seekrit sync disable <bindingId> [--yes]Delete a binding and revoke seekrit's key for that environment. Aliased as sync rm.
seekrit sync run <bindingId>Push now, synchronously, and report what landed. Exits non-zero unless the run fully succeeded — partial is a real outcome.
seekrit sync runs [--binding <id>]The run history.

seekrit sync enable takes the environment (--env, plus --app), the connection (--connection), and the destination:

FlagDescription
--connection <name>Destination account, by name or id.
--provider <kind>vercel (default), cloudflare-workers, cloudflare-pages, cloudflare-secrets-store, railway, aws-secrets-manager, aws-parameter-store, render, fly, northflank, digitalocean, heroku, netlify, bunnyshell, github-actions, gcp-secret-manager, langgraph-platform, azure-key-vault, huggingface-spaces, openai-vault, or claude-managed-agents.
--project <id>vercel: project id (prj_…) or name. cloudflare-pages: project name. northflank: project id — the slug in its URL. bunnyshell: project ID — writes the project's variables, which every environment created in it afterwards inherits.
--target <list>Comma-separated deployment targets (default production). vercel: production, preview, development. cloudflare-pages: production, preview. netlify: production, deploy-preview, branch-deploy, branch, dev.
--git-branch <branch>vercel: restrict preview writes to one git branch. netlify: the branch a --target branch context applies to (required with it).
--script <name>cloudflare-workers: the Worker's name. A Wrangler environment is its own Worker — my-api --env staging is the Worker my-api-staging.
--store-id <id>cloudflare-secrets-store: the store ID (32 hex).
--scopes <list>cloudflare-secrets-store: comma-separated scopes for secrets seekrit creates (default workers).
--railway-project <id>railway: the project ID (a UUID).
--railway-environment <id>railway: the Railway environment ID (a UUID) — its deployment environment, not the seekrit one.
--service <id>railway: the service ID (a UUID). Omit to write the environment's shared variables. render: the service ID from its dashboard URL (srv-…, or crn-… for a cron job).
--skip-deploysrailway: stage values without triggering the redeploy that would put them live.
--path <path>aws-parameter-store: the hierarchy to write under, /prod/storefront/ (leading and trailing slash). aws-secrets-manager: an optional name prefix, prod/storefront/. azure-key-vault: a prefix on every secret name. Key Vault names are flat — no folders, and / is not a character it takes — so this is a naming convention rather than a path.
--layout <layout>aws-secrets-manager, gcp-secret-manager: secret-per-name (default) or json-bundle — every value as one JSON secret, the shape ECS and Lambda read with secret-arn:json-key::.
--secret-name <name>aws-secrets-manager, gcp-secret-manager: required with --layout json-bundle — the one secret to write.
--param-type <type>aws-parameter-store: SecureString (default) or String.
--tier <tier>aws-parameter-store: Standard (default), Advanced, or Intelligent-Tiering.
--kms-key-id <id>aws: a customer-managed KMS key, as an ID, ARN, or alias. Defaults to the AWS-managed key.
--env-group <id>render: the environment group ID (evg-…). Pass this or --service, not both — which flag you use picks the destination.
--fly-app <name>fly: the Fly app name (not --app, which is the seekrit application). One secret set per app, staged until Machines restart — fly secrets deploy rolls them out.
--secret-group <id>northflank: the secret group to write, by the slug in its URL. It must already exist.
--do-app <id>digitalocean: the App Platform app ID — the UUID in its dashboard URL, not the app's name.
--component <name>digitalocean: write one component's own variables instead of the app-level ones. A component-level name overrides an app-level one.
--env-scope <scope>digitalocean: RUN_TIME (default), BUILD_TIME, or RUN_AND_BUILD_TIME. Run time keeps values out of build logs and buildpacks.
--heroku-app <name>heroku: the Heroku app name, or its UUID (not --app, which is the seekrit application). One set of config vars per app; writing them cuts a release and restarts the app's dynos.
--netlify-site <id>netlify: the site's API ID — the UUID under Project configuration → General → Project information, not the site name or its .netlify.app address. Netlify resolves no site names on the environment variable endpoints, and a site it cannot resolve gets the variables written to the whole team.
--no-netlify-secretnetlify: create readable variables instead of write-only Netlify secrets. seekrit creates secrets by default; pass this if your plan has no Secrets Controller.
--bunnyshell-environment <id>bunnyshell: the environment ID, from bns environments list or the dashboard URL — its variables are inherited by every component in the environment. Omit it and pass --project to write the project's variables instead; passing both is an error, since a binding writes to one.
--no-bunnyshell-secretbunnyshell: create variables visible in Bunnyshell's dashboard instead of secret ones. seekrit marks them secret by default. Bunnyshell encrypts every variable at rest either way, so the flag decides who can read it, not whether it is stored in the clear.
--gh-repo <owner/name>github-actions: the repository, exactly as GitHub writes it (acme/storefront). Taken as one flag because that is how GitHub writes a repository everywhere; asking for it in two invites pasting the pair into one of them.
--gh-environment <name>github-actions: write to one deployment environment's secrets rather than the repository's. Needs --gh-repo. The environment must already exist — seekrit will not create one, because an environment is a deployment gate and creating an unprotected one from a typo would remove it.
--gh-org <login>github-actions: write organization secrets instead of a repository's. Mutually exclusive with --gh-repo and --gh-environment.
--gh-visibility <v>github-actions org secrets: all, private (CLI default), or selected. all includes repositories added later, and public ones.
--gh-repo-ids <ids>github-actions: comma-separated numeric repository IDs for --gh-visibility selected. GitHub's API takes IDs, not names — gh api repos/acme/storefront --jq .id.
--gcp-prefix <prefix>gcp-secret-manager: prepended to every secret ID, e.g. prod-storefront-. Not --path: a Secret Manager ID takes letters, digits, hyphens, and underscores — no slashes or dots.
--gcp-replication <policy>gcp-secret-manager: automatic (default) or user-managed. Set when a secret is created and immutable after.
--gcp-locations <list>gcp-secret-manager: comma-separated regions, required with --gcp-replication user-managed. Each is billed as its own active version.
--gcp-kms-key <name>gcp-secret-manager: a Cloud KMS key, as its full resource name (projects/…/cryptoKeys/…). KMS keys are regional, so one key covers automatic replication or a single location.
--langgraph-deployment <id>langgraph-platform: the deployment UUID, from its dashboard URL or the id in GET /v2/deployments. A deployment is the whole destination — its secrets belong to the deployment, so there is no narrower scope to name. Every write creates a new revision, which rebuilds and rolls out the Agent Server; seekrit reads first and sends nothing when nothing would change, so a stable environment never redeploys on the reconcile timer.
--hf-space <owner/name>huggingface-spaces: the Space ID — its owner and name, e.g. acme/support-demo, as shown under the Space's title. A pasted huggingface.co/spaces/… URL is accepted and reduced to the ID. A Space has one secret set shared by every replica, so there is no narrower scope to name; staging and production are separate Spaces. Values land as secrets, never variables — variables are public on the Hub. Changing a Space's configuration restarts it, so a push that rotates several secrets restarts it more than once before settling.
--openai-vault <id>openai-vault: the existing OpenAI Vault ID (vault_…). The connection carries an OpenAI project API key with api.vaults.read and api.vaults.write.
--openai-hosts <hosts>openai-vault: comma-separated exact HTTPS hostnames (for example, api.github.com,api.stripe.com) that may receive each synced credential. Create a new Agents API session after a value changes.
--gcp-prune-versionsgcp-secret-manager: destroy the version each push supersedes, keeping one active version per secret. Only ever a version seekrit itself wrote.
--vault <name>azure-key-vault: the vault name (acme-prod), not its URL. The vault must allow access from all networks — seekrit reaches it over the public endpoint. claude-managed-agents: the vault ID (vlt_…), not its display name; the connection's API key must come from the vault's workspace.
--allowed-hosts <list>claude-managed-agents: comma-separated hosts every pushed secret may be substituted on — hostnames (api.example.com), *. wildcards (which match subdomains but not the apex), or IPv4 addresses, up to 16 — or unrestricted. Applies to every name without its own --allowed-hosts-for.
--allowed-hosts-for <NAME=hosts>claude-managed-agents: hosts for one secret, by its name in the vault (after --prefix); repeat per name. Overrides --allowed-hosts. A name covered by neither is not pushed.
--injection-location <where>claude-managed-agents: header (default), body, or both — which parts of an outbound request the placeholder is substituted into.
--name-mode <mode>azure-key-vault: dash (default) or reject. Key Vault secret names allow only letters, digits, and hyphens, so DATABASE_URL cannot be stored as-is; dash writes it as DATABASE-URL, and reject fails those names instead so you can map them yourself. Two names that collide under the rewrite (A_B and A-B) both fail rather than one overwriting the other.
--prefix <prefix>Prepend this to every destination key name.
--include <globs> / --exclude <globs>Comma-separated name globs. include allows, exclude then removes; exclusion wins.
--on-delete delete|retainWhat happens on the destination when a secret is removed here (default delete).
--mode auto|manualPush on every write (default), or only on sync run.
--acknowledge-decryptionRequired non-interactively; confirms seekrit's servers may decrypt this environment.

Enabling wraps the environment's data key — and the key of every group it composes — to the connection, on your machine. You need a key for all of them, so run it as someone who can already read the environment.

printf '%s' "$VERCEL_TOKEN" | seekrit sync connect --name acme-vercel --team-id team_…
seekrit sync enable --connection acme-vercel --app storefront --env production \
  --project prj_… --target production --acknowledge-decryption
seekrit sync run syb_…

# Cloudflare: one scoped token per product, plus the account id
printf '%s' "$CLOUDFLARE_API_TOKEN" | seekrit sync connect --name acme-cf \
  --provider cloudflare-workers --account-id 0123456789abcdef0123456789abcdef
seekrit sync enable --connection acme-cf --provider cloudflare-workers \
  --script my-api --app storefront --env production --acknowledge-decryption

# Railway: three UUIDs address a variable, and the token kind picks the header
printf '%s' "$RAILWAY_TOKEN" | seekrit sync connect --name acme-railway \
  --provider railway --token-kind project
seekrit sync enable --connection acme-railway --provider railway \
  --railway-project 11111111-1111-4111-8111-111111111111 \
  --railway-environment 22222222-2222-4222-8222-222222222222 \
  --service 33333333-3333-4333-8333-333333333333 \
  --app storefront --env production --acknowledge-decryption

# AWS: the region and key id are config, the secret access key is the credential
printf '%s' "$AWS_SECRET_ACCESS_KEY" | seekrit sync connect --name acme-aws \
  --provider aws-secrets-manager --region us-east-1 --access-key-id AKIA…
seekrit sync enable --connection acme-aws --provider aws-secrets-manager \
  --path prod/storefront/ --app storefront --env production --acknowledge-decryption

# Render: one key for the whole workspace, then a service or an env group
printf '%s' "$RENDER_API_KEY" | seekrit sync connect --name acme-render --provider render
seekrit sync enable --connection acme-render --provider render \
  --service srv-abc123 --app storefront --env production --acknowledge-decryption
seekrit sync enable --connection acme-render --provider render \
  --env-group evg-xyz789 --app storefront --env staging --acknowledge-decryption

# Fly.io: the app name is the whole destination; nothing to set on the connection
printf '%s' "$(fly tokens create deploy -a storefront-production)" \
  | seekrit sync connect --name acme-fly --provider fly
seekrit sync enable --connection acme-fly --provider fly \
  --fly-app storefront-production \
  --app storefront --env production --acknowledge-decryption

# Northflank: one secret group, named by slug — the token carries its own team
printf '%s' "$NORTHFLANK_API_TOKEN" | seekrit sync connect --name acme-northflank \
  --provider northflank
seekrit sync enable --connection acme-northflank --provider northflank \
  --project default-project --secret-group app-secrets \
  --app storefront --env production --acknowledge-decryption

# DigitalOcean App Platform: app-level variables, written encrypted.
# Each push submits a new app spec, which starts a deployment.
printf '%s' "$DIGITALOCEAN_TOKEN" | seekrit sync connect --name acme-digitalocean \
  --provider digitalocean
seekrit sync enable --connection acme-digitalocean --provider digitalocean \
  --do-app 4f6c71e2-1e90-4762-9fee-6cc4a0a9f2cf \
  --app storefront --env production --acknowledge-decryption

# Heroku: config vars on one app — each push restarts its dynos
printf '%s' "$(heroku authorizations:create --short)" \
  | seekrit sync connect --name acme-heroku --provider heroku
seekrit sync enable --connection acme-heroku --provider heroku \
  --heroku-app storefront-production \
  --app storefront --env production --acknowledge-decryption

# Netlify: one site's variables, per deploy context — the team is on the connection
printf '%s' "$NETLIFY_AUTH_TOKEN" \
  | seekrit sync connect --name acme-netlify --provider netlify --account-id acme
seekrit sync enable --connection acme-netlify --provider netlify \
  --netlify-site 3970e0fe-8564-4903-9a55-c5f8de49fb8b \
  --target production,deploy-preview \
  --app storefront --env production --acknowledge-decryption

# Bunnyshell: one environment's variables — the connection carries no scope
printf '%s' "$BUNNYSHELL_TOKEN" \
  | seekrit sync connect --name acme-bunnyshell --provider bunnyshell
seekrit sync enable --connection acme-bunnyshell --provider bunnyshell \
  --bunnyshell-environment env-9f3a2b \
  --app storefront --env production --acknowledge-decryption

# Bunnyshell: the project instead, to seed ephemeral environments that don't exist yet
seekrit sync enable --connection acme-bunnyshell --provider bunnyshell \
  --project prj-4c8d1e \
  --app storefront --env preview --acknowledge-decryption

# GitHub Actions: prefer seekritdev/github-action, which decrypts in the run.
# Sync only for what an Action cannot reach — `with:` inputs of third-party
# actions, `secrets: inherit`, job-level container/services credentials.
printf '%s' "$GITHUB_TOKEN" \
  | seekrit sync connect --name acme-github --provider github-actions
# ...to one deployment environment's secrets, the narrowest scope GitHub has
seekrit sync enable --connection acme-github --provider github-actions \
  --gh-repo acme/storefront --gh-environment production \
  --app storefront --env production --acknowledge-decryption
# Google Secret Manager: the credential is the whole key file, so pipe it in
seekrit sync connect --name acme-gcp --provider gcp-secret-manager \
  --project-id acme-prod < key.json
seekrit sync enable --connection acme-gcp --provider gcp-secret-manager \
  --gcp-prefix prod-storefront- --gcp-prune-versions \
  --app storefront --env production --acknowledge-decryption

# Azure Key Vault: a service principal's client secret, one vault per binding
printf '%s' "$AZURE_CLIENT_SECRET" \
  | seekrit sync connect --name acme-azure --provider azure-key-vault \
      --azure-tenant-id 72f988bf-86f1-41af-91ab-2d7cd011db47 \
      --azure-client-id 0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9
seekrit sync enable --connection acme-azure --provider azure-key-vault \
  --vault acme-prod \
  --app storefront --env production --acknowledge-decryption

Billing

CommandDescription
seekrit billingThe org's plan, subscription status, current usage against what the plan includes, and which self-serve actions this deployment supports. While limits aren't enforced it says so — the numbers are informational.
seekrit billing entitlementsEvery entitlement the org resolves to, and where each value came from.
seekrit billing checkout <family>Start a self-serve upgrade. Prints a checkout URL to open.
seekrit billing portalPrint a billing-portal URL for payment methods and invoices.
seekrit billing cancel [--yes]Cancel the subscription and drop back to the Free plan.