Runx skills declare what credential they need. Operators configure material once, and every entry surface resolves the same contract before execution. An agent should never guess an environment name, put a token on argv, or wrap a Runx call with custom dotenv logic.
Declare named requirements in X.yaml, then reference one from each runner
that reaches the provider:
credentials:
nitrosend:
provider: nitrosend
audience: https://api.nitrosend.com
auth:
api_key:
delivery:
env: NITROSEND_API_KEY
runners:
status:
default: true
type: graph
credential: nitrosendThe delivery name is part of the skill contract, not operator setup. A runner's
environment.required and environment.optional lists are for ordinary,
non-secret runtime configuration. The runtime carries credential delivery
separately and injects it only at the adapter boundary.
A provider may expose more than one auth mode:
credentials:
twitter-read:
provider: twitter
auth:
oauth1_user:
delivery:
env: TWITTER_USER_AUTH
bearer:
delivery:
env: TWITTER_BEARER_TOKENThe selected profile's auth_mode chooses one declared delivery. If more than
one declared environment value is present without a profile, resolution fails
as ambiguous rather than choosing silently.
Store a durable local profile by sending the secret on stdin:
printf '%s' "$NITROSEND_API_KEY" |
runx credential set nitrosend --profile account-one --from-stdin
printf '%s' "$TWITTER_BEARER_TOKEN" |
runx credential set twitter \
--profile twitter-app \
--auth-mode bearer \
--from-stdin
printf '%s' "$N8N_WEBHOOK_TOKEN" |
runx credential set n8n \
--profile workflow \
--auth-mode bearer \
--audience https://n8n.example.com \
--from-stdinThen run explicitly:
runx skill ./skills/nitrosend status --profile account-one --jsonrunx credential set makes the stored profile the provider's global default.
--audience binds material to one canonical HTTPS host. Use it for providers
whose destination is selected at runtime, such as a self-hosted n8n instance.
When both a skill and a profile declare an audience, their normalized hosts
must match; a profile can narrow an undeclared skill audience but can never
widen a skill-owned binding.
Use a project binding when a workspace should choose a different profile
without repeating --profile:
runx credential bind account-one --provider nitrosend
runx credential bind account-one --skill nitrosend --credential nitrosendThe first binding covers every matching provider requirement in the project. The second is narrower and wins for that named skill requirement.
Inspect or remove configuration without exposing material:
runx credential list --json
runx credential remove account-oneFor a declared runner requirement, Runx resolves exactly once per command in this order:
- Explicit
--profile. - Project binding in
<workspace>/.runx/credentials.json. - Provider default in
~/.runx/config.json(orRUNX_HOME). - A pre-resolved hosted credential-handle set supplied by the runtime host.
- The requirement's declared environment name from the workspace snapshot.
Profile, project, and global configuration contain selectors and encrypted references, not plaintext material. Hosted Connect grants remain provider execution authority; OSS consumes only pre-resolved opaque handles and never extracts hosted provider tokens.
Every executing Runx CLI command discovers the workspace root and parses its
exact .env file as data; help and version rendering do not depend on
workspace state. Runx does not source a shell. Exported process values win;
.env fills only missing names. One immutable snapshot is used for the whole
command or MCP server session.
This makes an ignored project .env a useful zero-setup development path:
NITROSEND_API_KEY=nskey_live_redactedIt is a fallback, not the preferred durable operator setup. Use stored profiles when one machine operates multiple accounts, project bindings when a repo needs a stable selection, and hosted handles when a provider grant is managed by a runtime host.
Local profiles are recorded under the Runx home. Metadata and defaults live in
config.json; material is encrypted in private local key files. Project
bindings contain profile names only and may be reviewed or committed when the
names are not sensitive.
This protects against accidental disclosure through config output, repository
files, command history, logs, and receipts. It is not a substitute for an OS
account boundary or a managed secrets service on a compromised machine. Do not
commit .env, Runx key files, or credential material.
Secrets never appear in:
- command arguments;
- skill inputs or agent prompts;
- project bindings;
- inspect/readiness output;
- receipts or captured stdout/stderr;
- pause checkpoints or resume answers.
Resume checkpoints persist only the selected profile name. runx resume
captures a new workspace snapshot and re-resolves current material, so profile
rotation takes effect immediately.
Local credential profiles and hosted Connect grants solve different problems. A profile delivers operator-owned material to a declared local adapter. A Connect grant keeps provider material in Cloud and authorizes only named provider operations.
Skills use native provider.read and provider.mutate tools for Connect. The
runtime authenticates the operator, lists bounded active grant metadata, and
selects the unique grant matching the skill's expected_provider and declared
scopes. Reads do not need human approval. Mutations are still gated at the
consequential graph step, and Cloud checks the tool's expected read/mutate
classification against the provider driver's authoritative descriptor before
dispatch.
The runx connect command manages grant setup, status, listing, and revocation;
it does not expose raw provider invocation. Provider operations stay inside a
skill run so admission, approval when required, projection, and receipt sealing
remain one path.
A skill may also declare expected_result to require exact top-level resource
identity fields and result_fields to allow only named provider result fields
into the receipt. This is mandatory for secret-adjacent operations and useful
whenever provider evidence must bind to one exact repository, message, post,
charge, or other resource.
provider.mutate additionally requires one native idempotency_key. Runx
injects that key into the credential-free provider payload and rejects skills
that duplicate it inside input, leaving one auditable source for retry
identity.
No grant id is needed when exactly one active grant matches. If multiple grants
match, select one with RUNX_PROVIDER_PERMISSION_GRANT_ID; Runx resolves its
current scopes from Connect rather than asking the skill or agent to attest
them. Provider tokens never enter .env, skill inputs, command arguments, or
receipts.
runx skill inspect ... --json reports the requirement, supported auth modes,
selected non-secret source/profile, and readiness. A run with no match returns
status: needs_credential plus exact setup commands before any provider process
starts.
runx mcp serve performs the same check for every served skill at startup and
holds the resulting delivery from its single workspace snapshot. Exported
Claude and Codex shims call runx skill directly, so they inherit this behavior
without credential-specific wrapper commands.
Managed-agent and public API tokens use the same stdin rule:
printf '%s' "$ANTHROPIC_API_KEY" |
runx config set agent.api_key --from-stdinNo Runx secret-setting command accepts raw material on argv.