Skip to main content

SecretRef & anima.json

A SecretRef is a declarative pointer to a secret. Instead of writing API_KEY=sk-... in a .env file and praying it doesn’t end up in a commit or an LLM’s context window, you write:
{ "source": "anima", "credentialId": "cred_abc123", "field": "apiKey.key" }
The Anima CLI resolves these refs at execution time — the agent composing the command, and the LLM reviewing its output, never see the actual value.

The three sources

Every SecretRef has a source field that determines where the value comes from. Only these three are supported, and that’s deliberate — every additional source is a new place a secret can be misused.

anima — Anima Vault

The default. Points at a credential in your vault.
{
  "source": "anima",
  "credentialId": "cred_01HXYZ...",
  "field": "apiKey.key",
  "agentId": "agent_optional"
}
FieldRequiredNotes
credentialIdyesThe cred_... id from the Anima console or anima vault list
fieldyesDot-path into the credential (e.g. apiKey.key, login.password)
agentIdnoExplicit agent scope; defaults to the CLI’s authenticated agent
Under the hood the CLI mints a single-use vtk_ token, exchanges it, and the resolved value never touches disk. Every resolution is written to the audit log with the actor’s key type recorded — reveals via a master key (mk_) show up distinctly from agent-scoped accesses.

env — Environment variable

Escape hatch for secrets that aren’t yet migrated into Anima (rotating dev tokens, one-off sandbox keys).
{ "source": "env", "name": "MY_DEV_TOKEN" }
FieldRequiredNotes
nameyesMust be present in the environment at resolve time
If the variable is unset, the CLI refuses to run. It does not fall back to an empty string — that pattern causes too many “it worked locally but 401’d in prod” bugs.

exec — Trusted binary

For dynamic secrets that come from a local tool (AWS STS, op read, gcloud auth print-access-token, short-lived GitHub App tokens).
{
  "source": "exec",
  "command": "aws",
  "args": ["sts", "get-session-token", "--output", "text"]
}
FieldRequiredNotes
commandyesBare binary name or absolute path; shell metacharacters (;, |, `, $, &) are rejected
argsnoString array. Spawned with shell: false so each element is a literal argv slot
passEnvnoEnv var names passed through to the subprocess — everything else is stripped
cwdnoWorking directory for the subprocess
Output is captured with a 15-second timeout and a 1 MB buffer cap. If the binary exits non-zero, the CLI refuses to run. The child process starts with a minimal environment: only the variables listed in passEnv, plus PATH so the binary can be found. Be explicit about what your provider needs — e.g. "passEnv": ["HOME", "AWS_PROFILE"] for the AWS CLI.

anima.json — putting it together

Drop an anima.json at the root of any project (or any ancestor directory — the CLI walks up from cwd). The shape:
{
  "$schema": "https://docs.useanima.sh/schemas/anima.json",
  "secrets": {
    "GH_TOKEN":    { "source": "anima", "credentialId": "cred_github", "field": "apiKey.key" },
    "DATABASE_URL":{ "source": "env",   "name": "DATABASE_URL" },
    "AWS_KEY":     {
      "source":  "exec",
      "command": "aws",
      "args":    ["sts", "get-session-token", "--output", "text"]
    }
  }
}
The keys (GH_TOKEN, DATABASE_URL, AWS_KEY) are the variable names that will be exposed to whatever command consumes the refs — typically environment variables passed to a child process. The $schema line is optional, but with it any JSON-Schema-aware editor (VS Code out of the box) validates refs and autocompletes fields as you type. The schema is published at docs.useanima.sh/schemas/anima.json.

Using it

# Resolve all refs and exec a subprocess with them in-env
anima vault exec -- gh api /user

# Proxy a single ref through an HTTPS injector
anima vault proxy --cred GH_TOKEN --allow-host api.github.com --port 19840

# Quick lookup (plaintext; requires `anima vault unlock` first)
anima vault get GH_TOKEN
If the CLI can’t resolve a ref (missing env var, failing exec, deleted vault credential) it refuses to run and emits a structured error — no partial execution, no silent fallback.

Why not just .env?

Three reasons:
  1. .env files are static. exec sources pull fresh values on every resolve — matters for STS tokens, SSO-minted GitHub App tokens, and anything else with a lifetime under an hour.
  2. .env files are untyped. A SecretRef is a JSON schema — you get IDE completion, and the CLI can refuse to run if the shape is wrong before the subprocess starts.
  3. .env files don’t participate in the audit log. Every anima-source resolution is attributed to an agent or user and shows up in the console Access Log. Agents that ingest a .env file by accident are a common leak vector; an anima.json commits only references — the values live exclusively in the vault.

Security rules the resolver enforces

These are non-negotiable — the resolver refuses to run if any are violated:
  • exec.command may not contain whitespace or ;, |, &, <, >, $, `, \. If you need a pipeline, write a wrapper script and point command at it.
  • exec child processes get a minimal environment: PATH, plus only the variables listed in passEnv. Everything else is dropped.
  • anima refs require an active CLI auth context — the resolver prompts for login rather than falling through silently.
  • Resolved values never reach disk. They live in process memory for the duration of the child, then are overwritten.
See also: Vault overview, Security.