Skip to content
Anlyon
Esc
↑↓navigate↵open⌘Jpreview
On this page

Secrets

A write-only vault. Actions reference a secret by name and Anlyon resolves it at dispatch, so the credential never enters the model's context.

Secrets are the reason the execution boundary is worth having. An action references a credential as {{secret:NAME}}. Anlyon decrypts it at the moment of dispatch, interpolates it into the request it is about to send, and sends it. The value is never returned to the caller, never written to a log, and never present in the context the model reads.

There is no read endpoint

Secrets go in. Nothing gets them out.

await ops.secrets.put('STRIPE_KEY', { value: process.env.STRIPE_KEY!, allowedHosts: ['api.stripe.com'] });

// Metadata only. There is no method that returns a value, because there is
// no endpoint behind one.
const { data } = await ops.secrets.list();
ops.secrets.put("STRIPE_KEY", value=os.environ["STRIPE_KEY"], allowed_hosts=["api.stripe.com"])

# Metadata only.
result = ops.secrets.list()

secrets.get(name) returns the name, description, the binding described below, and when it was last used. It does not return the value, and neither does any other operation on any surface. This is not an ACL you could misconfigure. It is an endpoint that was never built.

Writing a secret needs secrets:write. Referencing one needs secrets:read, because a reference is a use: a key that can put {{secret:STRIPE_KEY}} into an action is a key that can cause that secret to be sent somewhere.

Where a secret may go

Two constraints apply, one always and one when you configure it.

Always: a secret-bearing action has a literal destination

An action that references a secret must have a fixed scheme and host. You cannot interpolate {{input.*}} into the host of a URL that carries a credential, because the invoker would then choose the recipient of your key. A secret placed in the URL’s authority is refused outright: it would be handed to DNS resolution before the request was made.

This holds for every secret-bearing action with no configuration on your part.

Optionally: bind the secret itself

A secret can also carry its own policy, independent of any action:

Field Meaning
allowedHosts Hosts this secret may be sent to. A leading dot matches subdomains, so .stripe.com admits api.stripe.com. Null or omitted means any public host.
allowedPlacements Where in the request it may appear: url, header or body. Null or omitted means anywhere. A credential in a header is the intended use. The same value in a URL is usually a mistake or an exfiltration.

Both are checked when an action is authored. allowedHosts is checked again at fire time against the host actually being called, so an action that predates a tightened host list stops working on its next call rather than on its next edit. allowedPlacements is checked again at dispatch on a declared action. On an action with no declaration, a tightened placement applies at the next edit. Omitting allowedHosts on a rotation leaves the existing value alone, so rotating a secret never quietly widens where it may go.

Secrets in a governed action

A governed action persists the request it will send, shows it to the reviewer and hashes it into the approval’s digest. None of those holds a credential.

  • A declared action keeps every {{secret:NAME}} reference as a placeholder in the stored request. The reviewer sees Authorization: Bearer {{secret:MAILER_TOKEN}}, and the digest covers that text.
  • Anlyon resolves the placeholders at dispatch, when the request is sent. The host and placement bindings of each secret are checked again at that point.
  • The read-back of a verify rule goes out with the action’s own headers, so it uses the same secret under the same bindings.
  • An input that spells a secret reference is refused. An input can never cause a secret to be resolved.
  • An adapter action names one vault secret as credentialSecret. The adapter checks the key’s shape.

A binding tightened after review is enforced at dispatch. The approved effect is refused, and nothing is sent:

{ "stage": "rejected", "rejectionReason": "credential_unavailable", "grade": "refused" }

Rotation

secrets.put() on an existing name rotates it. The next dispatch resolves the new value. In-flight approvals resolve whatever is current when they finally execute, not what was current when the human clicked approve. An approval snapshot freezes the request that was reviewed. It does not preserve the right to use a credential that has since been revoked.

How it behaves

  • The vault covers credentials stored in it, used by actions Anlyon dispatches. A key your own process holds stays in your process, including behind the local approval gate.
  • A limit, an approval and a receipt apply to calls routed through the action. Keep the key in the vault and out of the agent’s process.
  • A credential is resolved at dispatch. A secret rotated, removed or rebound after review is resolved as it stands then.
  • The vault holds operator secrets, not end-user tokens.
  • The delivery-signing secret is workspace-wide. A signed delivery names its environment inside the signature, and the key material and its rotation are shared across the environments of a workspace. Action invocations carry no Anlyon delivery signature. A notification webhook carries no Anlyon-Signature. It carries X-Anlyon-Signature: sha256=<hex> when a webhook secret is set, and nothing otherwise.
  • Environments are logical isolation. Secrets belong to an environment through an enforced column in shared storage.
  • The Resend adapter takes a full access key, as of 2026-10-03. The read-back and the lookup use it.

Where to go next

Was this page helpful?