Skip to docs content
Browse documentation
On this page

Secrets and host approval

Secrets

Credential setup uses saved secrets (the Secrets page , or secrets.set when a token already exists inside trusted code) and OAuth integrations (the Integrations page ).

Use search first to discover saved integrations and capabilities before switching to execute.

During execute, await minion.secrets.list() returns metadata only: names, descriptions, approved hosts, expiresAt, and whether the secret may be used for webhooks or JWT signing. It never returns plaintext values. Expired secrets stay in the list. Fetch placeholders treat them as missing, so Minion stops sending the value.

minion.secrets.set(...) persists a value that is already available inside execution (for example an API key the package just minted). It does not return secret values. Do not use it for OAuth access or refresh tokens: integrations keep those on the connection. Optional expiresAt is a UTC ISO timestamp (or YYYY-MM-DD). Pass null to clear it. secrets.setMany writes 1 to 50 secrets in one transaction and rolls the whole batch back if any entry fails. Only owners and admins can write secrets, and host approvals can't be changed from code.

A connection authorizes your agent, and any code you run or install, to act as you on that provider. Minion does not control or supervise what your agent does with the access you grant; scope connections deliberately and revoke unused ones.

Placeholders in fetch

Outbound fetch can include placeholders such as {{secret:tokenName}} in the URL, headers, or body. The host resolves them for approved destinations.

Placeholder names start with a letter and use letters, digits and underscores. Placeholders are not general-purpose string interpolation. They only work in fetch, and not in the hostname.

For integrations, {{integration:name}} works the same way, in a header you set yourself. Prefer minion.integrations.fetch or createAuthenticatedFetch, which attach the token for you.

Signing JWTs with saved secrets

Use minion.secrets.jwtSign(...) when a workflow needs a JWT signed by a key stored in a saved secret. It returns { jwt, algorithm } and never returns key material. An owner or admin must first allow JWT use for that secret on the Secrets page.

The caller supplies the claims (and optionally typ and kid header fields), then performs any provider-specific token exchange with ordinary fetch. Pass the saved secret as keySecret. For service-account JSON secrets, pass keyField: "private_key" to sign with that field. Claims are limited to 16 KB. Supported algorithms:

  • HMAC: HS256, HS384, HS512. The key encoding is base64 (default), base64url or utf8, and the key must be at least as long as the hash size.
  • RSA PKCS#1: RS256 (default), RS384, RS512
  • RSA-PSS: PS256, PS384, PS512
  • ECDSA: ES256, ES384, ES512
  • EdDSA: Ed25519 PKCS#8 keys
const { jwt } = await minion.secrets.jwtSign({
  keySecret: 'serviceAccount',
  keyField: 'private_key',
  algorithm: 'RS256',
  claims: { iss: email, aud: tokenUrl, exp: Math.floor(Date.now() / 1000) + 300 },
})

Mentioning placeholders without resolving them

Resolution runs on the final outbound request (URL, headers and body), so a literal placeholder assembled by any means, including string concatenation, will resolve. Do not place resolvable placeholder tokens into user-visible or third-party-visible content such as issue bodies, comments, prompts, logs, or returned strings.

To mention the syntax in prose or docs, write {{secret:<name>}}. Angle brackets are outside the placeholder name charset, so this form is inert everywhere.

Host approval

If a request fails because a host is not approved for that secret, send an owner or admin to the Secrets page to approve it. An approved host is an exact hostname such as api.github.com, or *.example.com for any subdomain (not the bare domain). Values that aren't hostname-shaped are rejected, and a pasted URL is reduced to its hostname.

Saving a secret does not by itself approve new hosts, and nothing approves one automatically. An empty host allowlist blocks secret-bearing fetch for every caller, including packages you wrote yourself. If you're blocked on a person, minion.waiting.list() shows what is pending (see Waiting ).

Who can use a secret

Secrets belong to the organization. Every package, job, webhook handler and execute call in it can refer to a secret by name, but only toward the hosts that were approved for it. There is no per-package grant. Members can use secrets from code; only owners and admins can create, change or delete them.

Secrets vs package storage

Secrets hold credentials, not application records. Durable package data (rows, documents, checkpoints) lives in the package's own storage: packageStorage() for key/value state and sql() for tables. See Package storage and SQL .

Named state

Durable facts and preferences belong in memories. Package runtime state and knobs belong in packageStorage(). Longer working documents belong in notes. Credentials belong in secrets. OAuth client IDs and client secrets, and OAuth access and refresh tokens, live on the integration: they do not appear in the Secrets list. PATs, API keys, and provider-issued webhook HMAC secrets live in the secret store. A package-owned webhook signing secret (the GitHub style, generated when you mint) lives on the webhook, not in the Secrets list.