Handling Secrets

Orbs often need credentials for package registries, cloud providers, databases, and internal services. You can store a secret in Amp settings or let the orb use its temporary OIDC identity.

Use OIDC when the service supports it. OIDC gives the orb a temporary credential with a narrow scope, so you do not have to store a permanent key in Amp. Use Amp secrets for services that need a fixed token or password.

Secrets and Environment Variables

Add values from one of these settings pages:

  • Workspace settings apply to every orb in the workspace, including orbs for personal projects.
  • Project settings apply only to orbs for that project.
  • Personal settings apply only to your orbs.

Personal values override project values with the same name. Project values override workspace values.

Use an environment variable for configuration that is safe to display, such as a region or feature flag. Mark passwords, API tokens, private keys, and URLs with embedded credentials as secrets. Amp hides secret values after you save them.

Amp also hides every occurrence of a secret value in orb shell output. Do not store short values or common words, such as file or true, as secrets, because every occurrence of that word in output is hidden too. Settings warns you before it saves such a value as a secret.

An environment variable can have an empty value, for example GIT_CONFIG_VALUE_0= to reset a Git setting. A secret must have a value.

You can also ask Amp to manage values or use amp secrets from the CLI. When Amp needs a secret you already have, such as an API key, it asks you to enter it in a private dialog, so the value never appears in the thread. When Amp creates a new random value itself, such as a webhook signing secret, it stores that value directly without asking you. Run this command to see the available CLI operations:

amp secrets --help

Amp records who created and last changed each entry. The settings pages and amp secrets history show this history without showing the values.

Refresh Secrets

New orbs receive the current values when they start. To refresh a running orb, run this command from the orb’s Terminal:

amp orb restart-processes

Amp waits for the current command to finish. It then reloads the values and restarts the executor and managed services.

Amp Credentials

The amp CLI in an orb signs in to Amp with the orb’s thread identity, a short-lived credential that Amp rotates automatically. Commands such as amp threads, amp -x, and amp orb id-token need no setup. Git operations on Amp-hosted repositories use a Git-only token.

Amp is removing your account’s AMP_API_KEY from orb environments. If a setup script or tool reads $AMP_API_KEY directly, change it to run the amp CLI instead. If it must call the Amp API with an access token, create one in Security settings, store it as a personal secret under another name such as MY_AMP_ACCESS_TOKEN, and export it as AMP_API_KEY in that script. AMP_API_KEY itself is reserved and cannot be stored as a secret.

OIDC

How It Works

Amp orbs can mint signed OpenID Connect ID tokens. A cloud provider or private service can verify the token and grant temporary access based on the workspace, project, user, and thread that owns the orb.

Inside an orb, mint a token for one intended service:

amp orb id-token --audience https://logs.example.com

The command prints only the token to standard output, so it works with command substitution and credential helpers. The default lifetime is 10 minutes. You can request between 60 seconds and one hour:

amp orb id-token \
  --audience https://logs.example.com \
  --ttl-seconds 3600

The token uses RS256 and these public endpoints:

  • Issuer: https://ampcode.com/api/workload-identity
  • Discovery document: https://ampcode.com/api/workload-identity/.well-known/openid-configuration
  • JWKS: https://ampcode.com/api/workload-identity/jwks.json

The token contains the standard iss, aud, sub, iat, exp, and jti claims. It also contains these Amp claims:

ClaimMeaning
workspace_idThe Amp workspace. It is absent for a personal thread.
project_idThe Amp project. It is absent when the thread has no project.
user_idThe user who created the thread. Absent during project setup.
thread_idThe thread and orb identity. Absent during project setup.
email, email_verifiedThe account email and its state. Absent during project setup.
token_useThis is exchanged for tokens printed by amp orb id-token.

The default subject scope is thread. For a workspace-owned project, its subject is:

workspace:<workspace-id>:project:<project-id>:user:<user-id>:thread:<thread-id>

Each broader scope removes the narrower layers on the right:

ScopeSubject
threadworkspace:<workspace-id>:project:<project-id>:user:<user-id>:thread:<thread-id>
userworkspace:<workspace-id>:project:<project-id>:user:<user-id>
projectworkspace:<workspace-id>:project:<project-id>
workspaceworkspace:<workspace-id>

Amp omits unavailable workspace or project layers. The project scope for a personal project uses the owner-qualified subject described below.

For example, some providers only match the standard sub claim, such as Entra ID federated credentials with exact subject matching. Use --subject-scope workspace to produce a subject that is identical across all orbs in the same workspace:

amp orb id-token \
  --audience api://AzureADTokenExchange \
  --subject-scope workspace

The resulting sub is:

workspace:<workspace-id>

The workspace_id, project_id, user_id, and thread_id claims are still present as custom claims on the token. Only the sub shape changes. Tokens minted during project setup are the exception; see below.

Every orb in the same workspace can mint a token with this subject, including orbs for personal projects. The token does not encode the user or thread, so a provider that matches only sub cannot distinguish which member or orb produced it. Scope the provider’s role assignment to the minimum resource set.

Revocation is delayed: the token exchange endpoint trusts the orb’s existing request credential and does not recheck workspace membership at exchange time. A member removed from the workspace can continue minting workspace-scoped tokens until their request credential expires, which can take up to 24 hours.

Use --subject-scope project for a stable identity shared by all orbs for one project:

amp orb id-token \
  --audience https://logs.example.com \
  --subject-scope project

The subject includes the project owner so personal and workspace-owned projects cannot collide:

workspace:<workspace-id>:project:<project-id>
user:<owner-user-id>:project:<project-id>

Amp checks current project access each time it mints a project-scoped token. Moving a project to a different owner changes its subject. Update the identity configured at the receiving service when project ownership changes.

The workload identity request credential is available at /run/amp/workload-identity-request-token during project pre-clone, pre-setup, and .agents/setup scripts. During setup it can mint project- or workspace-scoped tokens only. These tokens identify the project, or the workspace that owns it, and nothing else: they have no user_id, thread_id, email, or email_verified claims, so a service cannot grant one user’s access to setup. The workspace scope during setup requires a workspace-owned project. Amp removes the file after setup and before saving a project snapshot. When Amp restores the snapshot, it writes a fresh runtime request credential before .agents/resume runs.

Project lifecycle scripts are trusted code. Amp removes its request credential, but it does not find or remove tokens, provider credentials, or login caches that a script copies elsewhere. Do not save exchanged tokens or provider login state in files that become part of the snapshot.

Design the Trust Policy

Choose a different audience for each service that trusts Amp. Configure Amp and the service with the exact same value. Use a URL or URN that names the integration, such as https://logs.example.com or urn:example:aws:logs:123456789012.

An audience is an identifier, not a password. Any orb can request the same audience, so the service must also restrict the identity claims:

  • Match workspace_id for workspace access.
  • Match workspace_id and project_id for one project.
  • Add user_id when only specific users should have access.
  • Match thread_id for one orb.
  • Require token_use to equal exchanged when the provider supports custom claim conditions.

Use immutable IDs instead of email for access rules. The sub claim works with providers that only expose standard OIDC claims. It identifies one thread by default. Use --subject-scope user, --subject-scope project, or --subject-scope workspace to share an identity across threads.

Inspect a token before you configure the provider:

TOKEN="$(amp orb id-token --audience test-only)"
TOKEN="$TOKEN" python3 - <<'PY'
import base64
import json
import os

payload = os.environ['TOKEN'].split('.')[1]
payload += '=' * (-len(payload) % 4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
PY
unset TOKEN

This command decodes the payload but does not verify it. A service must verify the signature, issuer, audience, and expiry before it trusts any claim.

MCP Servers

An HTTP MCP server in amp.mcpServers can receive an ID token for its own origin when a header contains ${amp:id-token}. See Amp ID Tokens. A local MCP server can run amp orb id-token itself when it needs a token for another service.

Google Cloud

Google Cloud Workload Identity Federation can exchange an Amp ID token for Google credentials. The following example gives every orb in one Amp project read only access to logs and metrics without a service account key.

Run this setup from a trusted workstation where you are signed in as a Google Cloud IAM administrator:

export GCP_IDENTITY_PROJECT_ID='<project-that-owns-the-pool>'
export GCP_IDENTITY_PROJECT_NUMBER='<numeric-project-number>'
export GCP_RESOURCE_PROJECT_ID='<project-containing-logs-and-metrics>'
export POOL_ID='amp-orbs'
export PROVIDER_ID='amp'
export AMP_WORKSPACE_ID='<amp-workspace-uuid>'
export AMP_PROJECT_ID='<amp-project-uuid>'
export AMP_GCP_AUDIENCE="urn:amp:gcp:${GCP_RESOURCE_PROJECT_ID}"

Create a pool and an OIDC provider. thread_id is used as google.subject because it is unique and stays below Google Cloud’s 127-byte subject limit; Amp’s composite sub can be longer.

gcloud iam workload-identity-pools create "$POOL_ID" \
  --project="$GCP_IDENTITY_PROJECT_ID" \
  --location=global \
  --display-name='Amp orbs'

gcloud iam workload-identity-pools providers create-oidc "$PROVIDER_ID" \
  --project="$GCP_IDENTITY_PROJECT_ID" \
  --location=global \
  --workload-identity-pool="$POOL_ID" \
  --issuer-uri='https://ampcode.com/api/workload-identity' \
  --allowed-audiences="$AMP_GCP_AUDIENCE" \
  --attribute-mapping='google.subject=assertion.thread_id,attribute.workspace_id=assertion.workspace_id,attribute.project_id=assertion.project_id,attribute.user_id=assertion.user_id' \
  --attribute-condition="assertion.workspace_id == '${AMP_WORKSPACE_ID}' && assertion.project_id == '${AMP_PROJECT_ID}' && assertion.token_use == 'exchanged'"

Grant only the roles the orbs need. Use the number of the project that owns the identity pool, even when the resources are in another project:

AMP_PROJECT_PRINCIPAL_SET="principalSet://iam.googleapis.com/projects/${GCP_IDENTITY_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/attribute.project_id/${AMP_PROJECT_ID}"

gcloud projects add-iam-policy-binding "$GCP_RESOURCE_PROJECT_ID" \
  --member="$AMP_PROJECT_PRINCIPAL_SET" \
  --role=roles/logging.viewer

gcloud projects add-iam-policy-binding "$GCP_RESOURCE_PROJECT_ID" \
  --member="$AMP_PROJECT_PRINCIPAL_SET" \
  --role=roles/monitoring.viewer

Commit a reviewed credential helper so Google client libraries can refresh the Amp token. For example, create scripts/amp-gcp-identity:

#!/usr/bin/env bash
set -euo pipefail

: "${AMP_GCP_AUDIENCE:?Set AMP_GCP_AUDIENCE}"
token="$(amp orb id-token --audience "$AMP_GCP_AUDIENCE")"

AMP_ID_TOKEN="$token" python3 - <<'PY'
import base64
import json
import os

token = os.environ['AMP_ID_TOKEN']
payload = token.split('.')[1]
payload += '=' * (-len(payload) % 4)
expiration = json.loads(base64.urlsafe_b64decode(payload))['exp']
print(json.dumps({
    'version': 1,
    'success': True,
    'token_type': 'urn:ietf:params:oauth:token-type:id_token',
    'id_token': token,
    'expiration_time': expiration,
}))
PY

Make it executable and create an external account credential file:

chmod +x scripts/amp-gcp-identity
mkdir -p .amp

export AMP_GCP_AUDIENCE='urn:amp:gcp:<resource-project-id>'
export GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1
export GOOGLE_APPLICATION_CREDENTIALS="$PWD/.amp/gcp-external-account.json"

gcloud iam workload-identity-pools create-cred-config \
  "projects/<identity-project-number>/locations/global/workloadIdentityPools/amp-orbs/providers/amp" \
  --executable-command="$PWD/scripts/amp-gcp-identity" \
  --subject-token-type=urn:ietf:params:oauth:token-type:id_token \
  --output-file="$GOOGLE_APPLICATION_CREDENTIALS"

gcloud auth login --cred-file="$GOOGLE_APPLICATION_CREDENTIALS"
gcloud logging read 'severity>=ERROR' \
  --project='<resource-project-id>' \
  --limit=20

Application Default Credentials libraries use GOOGLE_APPLICATION_CREDENTIALS directly. Set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1 only for a helper you control because the external account file grants permission to run that command.

If a Google API does not support direct federated principals, use service account impersonation. Grant the principal set roles/iam.workloadIdentityUser on a narrow service account and pass its email with --service-account=<service-account-email> when you create the credential file.

AWS

An AWS IAM OIDC provider can let selected Amp orbs assume roles with temporary credentials. This example gives every orb in one Amp project read only access to CloudWatch Logs without an AWS access key.

AWS IAM ignores custom claims in OIDC tokens, so its trust policy must restrict the standard aud and sub claims. Set these values on an authenticated administrator workstation:

export AWS_ACCOUNT_ID='<12-digit-account-id>'
export AWS_OIDC_AUDIENCE="urn:amp:aws:logs:${AWS_ACCOUNT_ID}"
export AMP_WORKSPACE_ID='<amp-workspace-uuid>'
export AMP_PROJECT_ID='<amp-project-uuid>'

Create the OIDC provider. The thumbprint list is optional in current IAM; IAM validates the issuer’s TLS certificate chain against trusted root certificate authorities.

aws iam create-open-id-connect-provider \
  --url 'https://ampcode.com/api/workload-identity' \
  --client-id-list "$AWS_OIDC_AUDIENCE"

An AWS account can have only one OIDC provider for an issuer. If the Amp provider already exists, add the audience to it:

aws iam add-client-id-to-open-id-connect-provider \
  --open-id-connect-provider-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/ampcode.com/api/workload-identity" \
  --client-id "$AWS_OIDC_AUDIENCE"

Create amp-logs-trust.json. AWS condition keys omit the https:// prefix but keep the issuer path:

cat > amp-logs-trust.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/ampcode.com/api/workload-identity"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "ampcode.com/api/workload-identity:aud": "${AWS_OIDC_AUDIENCE}",
          "ampcode.com/api/workload-identity:sub": "workspace:${AMP_WORKSPACE_ID}:project:${AMP_PROJECT_ID}"
        }
      }
    }
  ]
}
EOF

Create the role and attach the managed read only policy for CloudWatch Logs:

aws iam create-role \
  --role-name AmpOrbLogsReadOnly \
  --assume-role-policy-document file://amp-logs-trust.json

aws iam attach-role-policy \
  --role-name AmpOrbLogsReadOnly \
  --policy-arn arn:aws:iam::aws:policy/CloudWatchLogsReadOnlyAccess

The managed policy can read all CloudWatch Logs in the account. Replace it with an inline policy for specific log groups when the orbs need less access.

Inside an orb, mint a token into a private temporary file. AWS CLI v2 and the AWS SDK credential chain can use it with AssumeRoleWithWebIdentity:

TOKEN_FILE="$(mktemp)"
trap 'rm -f "$TOKEN_FILE"' EXIT
chmod 600 "$TOKEN_FILE"

export AWS_ACCOUNT_ID='<12-digit-account-id>'
export AWS_OIDC_AUDIENCE="urn:amp:aws:logs:${AWS_ACCOUNT_ID}"

amp orb id-token \
  --audience "$AWS_OIDC_AUDIENCE" \
  --subject-scope project \
  > "$TOKEN_FILE"

export AWS_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/AmpOrbLogsReadOnly"
export AWS_ROLE_SESSION_NAME='amp-orb-logs'
export AWS_WEB_IDENTITY_TOKEN_FILE="$TOKEN_FILE"
export AWS_REGION='<region>'
export LOG_GROUP_NAME='<log-group-name>'

aws sts get-caller-identity
aws logs tail "$LOG_GROUP_NAME" \
  --region "$AWS_REGION" \
  --since 1h \
  --format short

Do not turn on shell tracing around the token command. Mint a new token into the same temporary file before the next AWS credential refresh when the work lasts longer than the token.

Tailscale

Tailscale can exchange an Amp token for a temporary node registration credential. See Use Tailscale for how to install it in a project orb and connect the orb to your tailnet.