Secrets
Store org secrets securely and inject them into runs.
Every Fiveonefour org has a managed secret store for sensitive values your experiments need at runtime: API tokens, database URLs, GitHub PATs, and similar credentials that must reach the sandbox but must not live in YAML or git.
Saving secrets
Secrets are stored as key-value pairs. When you save a secret, you must provide a slug and a value (a description is optional):
- The
slugis a unique identifier (for exampleprod-gh) used to reference the secret in your experiments. Slugs are kebab-case: lowercase letters, digits, and dashes, starting and ending with a letter or digit. - The
valueis the sensitive value you want to store.
To save a secret you can either:
- Go to the Secrets page in the web app and click
+Add Secret(s). - Use the AX CLI directly, via
ax secret(prompt or stdin; the value is never echoed):
Run `ax learn secrets` and help me add a secret to my org.You can also use the MCP tools secret_set, secret_list, and secret_delete in the current MCP org. See the full ax secret reference for every subcommand.
Who can access secrets?
Right now, all members of the org can reference a saved secret by slug in their experiments. There is no per-secret access control at the moment, but it is expected in the future.
Reference secrets in experiments
You wire a stored secret into your sandboxed runs by referencing the secret's slug in an environment_variables entry in your experiment YAML.
When you declare the environment variable:
value: ax://secrets/<slug>points at the secret in the org store. The<slug>is the secret's unique identifier that you provided when you saved the secret (e.g.prod-gh). (Legacyaxp://secrets/<slug>is also accepted.)nameis the environment variable the sandbox gets (for exampleGITHUB_TOKEN). When the sandbox runs, agents, tests, and scripts read$GITHUB_TOKEN, which is securely resolved from the secret store at runtime.
schema_version: 2
id: github-example
name: "GitHub example"
environment_variables:
- { name: LOG_LEVEL, value: debug } # literal, injected as-is
- { name: GITHUB_TOKEN, value: "ax://secrets/prod-gh" } # $GITHUB_TOKEN ← secret prod-gh
agents: [claude]
prompts: ["…"]
tests:
- { name: noop, script: "true" }
limits: { max_time_seconds: 120 }So prod-gh is the store key; GITHUB_TOKEN is the env-var name you choose for this experiment. A plain string value (for example debug) is a literal and is injected as written.
name must follow the env-var format rules, and there are a few special names that are reserved for internal AX use: see reserved names.
Reusing secrets across experiments
Because the store slug and the env-var name are independent, one stored secret can power many experiments:
- The same slug under different names:
prod-ghasGITHUB_TOKENin one experiment and asGH_TOKENin another. - The same name backed by different slugs across environment or product variants: for example, bind
GITHUB_TOKENtoax://secrets/prod-ghon a prod setup and toax://secrets/staging-ghon a staging setup so every variant still reads$GITHUB_TOKEN, but each hits the credential for its target.
Malformed and missing references
| Problem | Example | What happens |
|---|---|---|
| Reserved scheme that is not a secrets reference | ax://files/config, ax://secret/prod-gh | Rejected by ax experiment validate |
| Secrets reference with a bad slug (not kebab-case) | ax://secrets/prod/GH, ax://secrets/Prod-GH | Rejected by ax experiment validate |
| Valid shape, slug missing from the org store | ax://secrets/missing-slug | Setup fails with Error status before the agent starts |
Secrets in tests
Test scripts receive the resolved environment_variables, so a test can use a secret-backed value the same way the agent does: read $NAME. A test that verifies real side effects against an external system can authenticate with the same secret the agent used:
environment_variables:
- { name: GITHUB_TOKEN, value: "ax://secrets/prod-gh" }
tests:
- name: pr-created
description: The agent opened a pull request on the fixture repo.
script: |
curl -sf -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/repos/acme/fixture/pulls?state=open" \
| jq -e 'length > 0'Do not leak secrets in test output
Test output is captured in run data. Use secret values without printing them: never echo $GITHUB_TOKEN or dump the environment in a test script.
Forwarding secrets to MCP servers
A declared environment_variables entry can also reach the MCP servers a setup exposes, referenced by name: stdio servers take it as an env entry, HTTP/SSE servers as a ${NAME} placeholder in a header value. Only names and placeholders appear in the YAML; values wire in at runtime.
environment_variables:
- { name: GITHUB_TOKEN, value: "ax://secrets/prod-gh" }
setups:
- name: with-mcp
mcp_servers:
- name: github
type: stdio
command: /workspace/github-mcp
env:
- GITHUB_TOKEN # forwarded under the same name
- name: internal-api
type: http
url: http://localhost:8080/mcp
headers:
- name: Authorization
value: "Bearer ${GITHUB_TOKEN}" # placeholder, resolved at runtimeFull schema and validation rules: MCP servers.
Local runs
The org secret store works on ax experiment run --local too: when you are signed in, store references resolve from your active org the same way they do on remote runs.
The difference is precedence. Local resolution checks your machine first, by env-var name (for example GITHUB_TOKEN), using the same local sources as every other environment variable (.env, --env-file, host env, and --env). Only if no local source supplies that name does the CLI fetch ax://secrets/<slug> from the org store.
- Local sources always win over the store. A local value is never re-resolved: a
.enventry ofEXAMPLE_SECRET=ax://secrets/xis injected as that string, not fetched from the org store. - A name missing from both local sources and the store fails preflight.
The CLI reports provenance without values: axp: loaded secrets from /path/to/.env when ./.env supplied a referenced name, plus a names-only stderr line per resolved store reference (variant id, env-var name, local sources or org secret store).
Remote runs have no host overrides: only the YAML and the org store apply. To change a value there, update the stored secret or the YAML literal.
Model credentials
Model provider credentials are not org secrets. See Model providers.