Skip to content

Credentials#

A credential is a secret the platform holds on an agent's behalf, plus the rules for where it may be attached. The agent authenticates to real systems and never learns the secret.


Create a credential#

curl -fsS -X POST \
  -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
  -H "Content-Type: application/json" \
  https://agents.example.com/v1/projects/$PROJECT_ID/credentials \
  -d '{
    "name": "github-token",
    "value": "'"$GITHUB_TOKEN"'",
    "injections": [{
      "domain": "api.github.com",
      "headerName": "Authorization",
      "headerFormat": "Bearer {value}"
    }]
  }'
Create a credential called github-token in the production project.
Inject it on api.github.com as the Authorization header with the
format "Bearer {value}". I will paste the value.

Prefer the CLI with --value-stdin for real secrets — a prompt leaves the value in the agent's conversation history.

printf '%s' "$GITHUB_TOKEN" | nexusctl credential create \
  --project production \
  --name github-token \
  --value-stdin \
  --inject domain=api.github.com,header=Authorization,format="Bearer {value}"

Names are lowercase alphanumeric with hyphens. --inject is repeatable, so one secret can be attached to several domains.

Values are write-only. credential list and credential get return the metadata and never the secret.

Use --value-stdin rather than --value. The explicit form leaks the secret into your shell history.


Injection#

An injection says: on requests to this domain, set this header to this format.

Field Purpose
domain Target hostname.
headerName Header to set.
headerFormat Format string containing the {value} placeholder — Bearer {value}, token {value}, or the raw {value}.
rules Optional method and path restrictions. Omit to inject on every request to the domain.

Narrowing with rules#

An injection with no rules attaches the credential to every request to that host. Rules make it precise:

{
  "domain": "api.internal.example.com",
  "headerName": "X-API-Key",
  "headerFormat": "{value}",
  "rules": [
    { "method": "GET", "path": "/v1/reports/*" }
  ]
}

Now the credential is attached only to GET requests under /v1/reports/. A DELETE to the same host goes out unauthenticated and fails at the upstream — which is the point. The agent cannot use a read credential to write, because the write never carries it.

Paths are glob patterns. Omit method or path to match any.


Where the substitution happens#

sequenceDiagram
    participant A as Agent
    participant P as Egress proxy
    participant U as Upstream

    A->>P: Authorization: Bearer <placeholder>
    P->>P: Match domain, method, path
    P->>P: Substitute real value
    P->>U: Authorization: Bearer ghp_real...
    U-->>P: 200
    P-->>A: 200

The agent's environment holds a placeholder. The proxy substitutes the real value into the outbound request after policy has allowed it, using TLS interception via the sandbox's ephemeral certificate authority.

An agent that prints its environment, reads its filesystem, or is prompt-injected into exfiltrating its configuration leaks placeholders. There is no code path that puts the real value where the agent can read it.


Attaching credentials to agents#

A credential exists in a project. A policy grants it:

name: github-readonly
allowedDomains:
  - pattern: api.github.com
    verdict: allow
    transport: upstream
credentials:
  - credentialName: github-token
    envVarKey: GITHUB_TOKEN

envVarKey sets the environment variable the placeholder appears under, so tooling that expects GITHUB_TOKEN works unchanged.

Both parts are required. Allowing the domain without the credential gives an unauthenticated agent; attaching the credential without allowing the domain gives an agent that cannot connect.


Sandbox-owned credentials#

A credential only one agent needs can live on the sandbox itself rather than in the project catalog:

credentials:
  github-token:
    value: ${GITHUB_TOKEN}
    injections:
      - domain: api.github.com
        headerName: Authorization
        headerFormat: "Bearer {value}"

${VAR} is expanded from your environment at apply time, so the spec is committable. The credential is private to that sandbox and deleted with it.


Other credential paths#

Not everything is a header.

System Mechanism
Kubernetes Platform-signed requests, agent identity presented to the API server.
AWS SigV4 re-signing with temporary STS credentials.
MCP connectors Static bearer, OAuth client credentials, or per-actor OAuth authorization code.
Models Provider credential held by the install, never exposed to a policy.

All of them share the property that matters: the secret is applied outside the sandbox.


Storage#

Credential values are encrypted at rest with the install's encryption.key. Deleting a credential removes its header injections with it.