Skip to main content
Secrets are named values stored envelope-encrypted at the organization or project level and referenced by name from your .auto/ configuration. The plaintext never appears in YAML, prompts, or session transcripts — you write a value once, and the platform decrypts it when a sandbox launches, when a webhook is verified, or when you reveal a secret you deliberately marked unprotected. This page covers creating secrets, referencing them, and the storage, expiry, rotation, and edge-injection machinery behind them.

Creating and updating secrets

Secret names are resource names: 1–128 characters of A-Za-z0-9_.-.

From the web app

The Secrets settings page in the web app manages secrets interactively at both scopes: set or rotate a value, edit the description and protection, configure expiry and edge injection, and reveal secrets marked non-protected. No write ever echoes a value back. Alongside the value itself, every secret carries a description (up to 1024 characters), a protected flag (true by default — write-only; false allows deliberate plaintext reveal), the two expiry settings, and an optional edge-injection configuration.

From a session

Agents with the local auto tool can create secrets with auto.secrets.create. It takes exactly one of value (a plaintext the agent already holds) or generate — a bounded regex pattern such as [A-Za-z0-9]{48} from which the platform draws a cryptographically random value server-side. A generated value is never returned, so it stays out of the session transcript entirely. Generate patterns support printable-ASCII literals, character classes, \d, \w, groups, alternation, and bounded quantifiers ({n}, {n,m}, ?); unbounded quantifiers (*, +, {n,}) and . are rejected. Creation from a session is not an upsert: without overwrite: true, it fails when a live secret with the same name exists in the scope, or when a project-scope create would shadow an inherited organization secret.

Scoping and resolution

A secret lives at exactly one scope: the organization, or one project. At resolution time — when a session launches or a webhook trigger authenticates — the platform looks up the name at the project scope first, then falls back to the organization scope. A project secret with the same name shadows the organization one. This gives you a natural layering: put shared credentials at the organization scope, then override per project where a project needs its own value.

Referencing secrets

Agent and environment env

The env map on an agent accepts either a plain string or a $secret reference:
.auto/agents/chief-of-staff.yaml
string | object
required
Env var names must match [A-Za-z_][A-Za-z0-9_]*. A plain string is passed through literally; an object form references a secret.
string
required
The secret name to resolve at session launch.
boolean
When true and the secret does not exist, the env var is omitted instead of failing the launch, and apply-time validation does not require the secret to be set. Absent or false, a missing or expired secret fails the launch with Secret not found: <name> or Secret has expired: <name>.
An environment’s env map has exactly the same shape, so runtime fragments can carry their own secret references:
.auto/fragments/environments/agent-runtime.yaml

Webhook trigger auth

Custom webhook triggers authenticate inbound requests against a secret named by secretRef, resolved with the same project-then-organization fallback:
.auto/agents/watchdog.yaml
kind: hmac_sha256 verifies a request signature with the secret; kind: bearer_token compares the Authorization: Bearer value in constant time. See Cron and webhooks.

How values are stored

Secret values are envelope-encrypted with AES-256-GCM before they reach the database:
  1. Each write generates a fresh random 256-bit data-encryption key (DEK) and encrypts the value with it.
  2. The DEK is itself encrypted with the platform’s key-encryption key (KEK) and zeroed from memory once wrapped.
  3. Both encryptions bind additional authenticated data tying the ciphertext to its organization, secret name, and key version — a ciphertext copied to another org, name, or key version fails authentication rather than decrypting.
Metadata reads (list, get) return a fixed projection of non-secret columns; ciphertext fields are structurally excluded from every metadata response. OAuth tokens stored for MCP OAuth tool connections are encrypted with the same envelope scheme.

Launch-time delivery and auditing

When a session launches, the worker resolves every $secret reference in the merged agent and environment env, decrypts each value, and delivers it into the sandbox process environment. Every resolution is recorded: the secret’s lastAccessedAt is stamped and a secret.access audit entry is written naming the secret, its scope, the session, and the delivery mode.

Edge injection: keeping secrets out of the sandbox

By default a resolved secret lands in the sandbox as an ordinary env var, where the agent (and anything it runs) can read it. For HTTP API credentials there is a stronger option: mark the secret injected, and the plaintext never enters the sandbox at all. The env var carries the non-sensitive placeholder auto-injected-secret:<name>, and the sandbox provider’s network edge adds the real value as an HTTP header on requests to the destination hosts — outside the sandbox trust boundary.
Injection configuration for herenow-api-key
string[]
required
1–16 exact lowercase hostnames. Matching is exact: no wildcards, no schemes, ports, or paths, and a parent domain does not match its subdomains — name every host.
string
required
The HTTP header the value is injected into (a valid header-name token, up to 128 characters). connection, content-length, host, and transfer-encoding are refused — injecting into framing headers could break requests rather than authenticate them.
string
Template for the header value around the plaintext, e.g. Bearer {value} (must contain the {value} placeholder, up to 256 characters). Absent means the raw value.
Injection is applied to HTTPS requests, so TLS to the destination hosts is terminated by the provider’s injecting edge. Two injected secrets cannot claim the same host + header pair — the launch fails with a clear error instead of letting one silently shadow the other. Sandbox providers without an injection edge (local development) fall back to delivering the plaintext inline, where the host is the trust boundary anyway. Audit entries distinguish edge-injection from env delivery.

Write-only protection and reveal

Secrets are protected (write-only) by default: no surface returns the plaintext, ever. For secrets you deliberately mark non-protected, Reveal on the web Secrets page returns the decrypted value — gated on a dedicated permission, audited on every call, and never cached. Revealing a protected secret is refused outright. Reveal addresses the exact scope you name: it does not follow the project-to-organization fallback that runtime resolution uses.

Rotation

Rotating a secret replaces the value of an existing secret without echoing it and stamps lastRotatedAt. Rotation never creates a secret — rotating a missing name is a 404 — and leaves the description, protection flag, and injection config untouched. Sessions snapshot secret references, not values: the next session launch resolves the new value with no re-apply needed.

Expiry

Two independent expiry mechanisms, settable when the value is written or edited later:
ISO 8601 timestamp | null
An absolute deadline. null clears it.
integer | null
Expire after this long unused, from 1 second up to 10 years. “Used” means written or resolved — the idle clock restarts on every value write and every launch-time resolution. null clears it.
An expired secret resolves as absent everywhere: required references fail the launch with Secret has expired, optional references are omitted, and the project-to-organization fallback applies as if the expired row did not exist. Expired secrets stay visible in listings (with a computed expired: true) so operators can see and remove them. Writing a fresh value — a full PUT or a rotation — revives an expired secret: the idle clock resets, and an absolute deadline that has already passed is cleared. Editing expiry without a value is only permitted for non-protected secrets that have not expired yet; for a protected or already-expired secret, re-set it with the value.

Permissions

Listing metadata, writing values, and revealing non-protected plaintexts are three separately gated permissions. Session-launched resolution is performed by the platform itself and audited as the secret-resolver system principal, not as a user.