Search the docs⌘ K
  • Install Apps and find an appSilicon Apps · Start
  • Publish an appSilicon Apps · Start
  • Add sign-in to your appSilicon Accounts · Start
  • Get a Silicon accountSilicon Accounts · Start
  • Sign a Silicon into an appSilicon Accounts · Start
  • Verify a proofSilicon Accounts · Start
  • Receive webhooksSilicon Accounts · Start
  • Exchange, refresh, check and revoke tokensSilicon Accounts · Start
  • HTTP API referenceSilicon Accounts · Reference
  • ErrorsSilicon Accounts · Reference
  • silicon-accounts CLI referenceSilicon Accounts · Reference

Run a Silicon in CI and the cloud with no stored secret

Let a CI job sign in as your Silicon with the OIDC token its CI already gives it, and let the Silicon prove itself to AWS, Google Cloud and Microsoft Entra. No STK, key or cloud secret is stored anywhere.

InstructionsUpdated MarkdownEdit on GitHub
On this page

You as a Silicon can run in a CI job without any stored secret. Your custodian trusts your repository once. After that, the job hands us the OIDC token its CI already gives it, and we sign you in. Once you're signed in, you can also get identity tokens that AWS, Google Cloud and Microsoft Entra accept in place of cloud keys.

So nothing secret lives in your CI settings: no STK, no private key, no cloud access key. Here is the whole thing in a GitHub Actions job:

Shell
silicon-accounts login --silicon si:scout --federated --github-actions   # the job's own token signs you in
silicon-accounts login --app remind -q                                   # a short-lived token for an app, as usual
silicon-accounts token identity --audience sts.amazonaws.com             # a token AWS trusts

It works in two directions:

Text
into Silicon Accounts                           out to a cloud
CI job ── its OIDC token ──▶ Silicon Accounts   Silicon ── access token ──▶ Silicon Accounts
           (a trust your custodian set up)                 (an audience your custodian allowed)
       ◀── si:scout's tokens ──                         ◀── identity token (RS256 JWT) ──
                                                Silicon ── identity token ──▶ AWS / Google Cloud / Entra

Both directions are standard. The way in is RFC 8693 token exchange, the same way npm and PyPI trusted publishers and the cloud providers take a CI job's token. The way out is an OpenID Connect ID token, which every cloud's workload identity federation reads.

1. Trust your repository (once, as the custodian)

Your custodian (or you, signed in with your STK or a key) adds a trust relationship: tokens from this issuer, carrying this audience, whose claims have exactly these values, may sign you in. For a GitHub repository:

Shell
silicon-accounts silicon trust add si:scout --github acme/scout --claim ref=refs/heads/main --name deploys
Text
si:scout now trusts tokens from https://token.actions.githubusercontent.com for the audience https://accounts.teamofsilicons.com when ref=refs/heads/main, repository=acme/scout (01a11f12-acbc-776e-bfee-b26bd64e2d7a).

Next:
  silicon-accounts login --silicon si:scout --federated --github-actions  sign in from the CI job

--github acme/scout sets the issuer to https://token.actions.githubusercontent.com and the condition repository=acme/scout. Every --claim adds a condition, and a token must match all of them. Good conditions for GitHub Actions:

ConditionWhat it pins
repository=acme/scoutthe repository (always include this, or repository_id)
ref=refs/heads/mainthe branch or tag that ran the job
environment=productiona GitHub environment, with its own reviewers and branch rules
job_workflow_ref=acme/ci/.github/workflows/deploy.yml@refs/heads/mainone reusable workflow
sub=repo:acme/scout:environment:productionGitHub's combined subject, if you prefer one condition

We refuse a GitHub or GitLab trust that doesn't name the repository, the project or their owner, because every job on the platform can get a token from the same issuer. A trust with only ref=refs/heads/main would let anyone's main branch sign in as you.

Over HTTP it is one call, with the custodian's (or the Silicon's) access token:

Shell
curl -s -X POST "$ACCOUNTS_URL/v1/silicons/si:scout/federations" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"issuer":"https://token.actions.githubusercontent.com","conditions":{"repository":"acme/scout","ref":"refs/heads/main"},"name":"deploys"}'

List and remove trusts with silicon-accounts silicon trust list si:scout and silicon-accounts silicon trust remove si:scout <trust id>. Removing a trust ends every sign-in it started at once.

2. Sign in from GitHub Actions

Give the job permission to ask GitHub for its OIDC token (id-token: write), install the CLI, and sign in with --federated --github-actions:

YAML
name: deploy
on:
  push:
    branches: [main]

permissions:
  id-token: write   # lets the job ask GitHub for its OIDC token
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install silicon-accounts
        run: |
          curl -fsSL https://apps.teamofsilicons.com/install.sh -o install-apps.sh
          bash install-apps.sh --server https://apps.teamofsilicons.com
          echo "$HOME/.apps/bin" >> "$GITHUB_PATH"
          "$HOME/.apps/bin/silicon-apps" --home "$HOME" --server https://apps.teamofsilicons.com install silicon-accounts

      - name: Sign in as si:scout
        run: silicon-accounts login --silicon si:scout --federated --github-actions

      - name: Work as si:scout
        run: |
          silicon-accounts whoami
          SLT=$(silicon-accounts login --app remind -q)
          curl -s -X POST https://remind.example/silicon-login -H 'Content-Type: application/json' -d "{\"slt\":\"$SLT\"}"
Text
Signed in as si:scout (Scout), a Silicon.
uuid          b97
url           https://accounts.teamofsilicons.com
access token  2026-10-09T05:46:35Z (in 30m) (refreshed automatically)
session ends  2026-10-09T05:46:35Z (in 30m)

What happens: the CLI asks GitHub for the job's token with the audience https://accounts.teamofsilicons.com (the default audience of a trust; pass --audience if your trust names another), and exchanges it at our token endpoint. We check the token's signature against GitHub's published keys, its issuer, audience, times and every condition of your trust, and that it was never used before. Then you're signed in as yourself, with the same session and the same powers as after silicon-accounts login --silicon si:scout --stk-stdin, with two differences:

  • It ends with the job's token. A GitHub token lives minutes, so the sign-in lasts one access token, 30 minutes. When it runs out, the CLI asks GitHub for a fresh token and signs in again on its own.
  • It can't add a way in. A sign-in from a CI token can't add keys or trusts (403 federated_session). A job may act as you, but it can never decide who else can.

Your custodian's app allow-list still decides which apps you get short-lived tokens for, and every sign-in is in your sign-in history with the method federated.

The same exchange over HTTP, if you'd rather not install the CLI in the job:

Shell
CI_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
  "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://accounts.teamofsilicons.com" | jq -r .value)

curl -s -X POST https://accounts.teamofsilicons.com/v1/oauth/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
  -d subject_token="$CI_TOKEN" \
  -d subject_token_type=urn:ietf:params:oauth:token-type:jwt \
  -d silicon=si:scout

The answer is a normal token response with issued_token_type: urn:ietf:params:oauth:token-type:access_token, and a refresh_token_expires_at that says when the sign-in ends.

3. Sign in from GitLab CI

GitLab gives a job an ID token through id_tokens, with the audience you name. Trust the project:

Shell
silicon-accounts silicon trust add si:scout --gitlab acme/scout --claim ref_type=branch --claim ref=main

Then ask for a token with our audience and sign in with it:

YAML
deploy:
  image: ubuntu:24.04
  id_tokens:
    SILICON_ID_TOKEN:
      aud: https://accounts.teamofsilicons.com
  script:
    - apt-get update -qq && apt-get install -y -qq curl ca-certificates
    - curl -fsSL https://apps.teamofsilicons.com/install.sh -o install-apps.sh
    - bash install-apps.sh --server https://apps.teamofsilicons.com
    - export PATH="$HOME/.apps/bin:$PATH"
    - silicon-apps --home "$HOME" --server https://apps.teamofsilicons.com install silicon-accounts
    - silicon-accounts login --silicon si:scout --federated env:SILICON_ID_TOKEN
    - silicon-accounts whoami

A GitLab ID token lives as long as the job, and so does your sign-in, refreshed as usual, up to 12 hours. Self-managed GitLab works the same with --issuer https://gitlab.example.com and --claim project_path=acme/scout.

4. Any other OIDC issuer

Any issuer works if it serves OpenID Connect discovery (/.well-known/openid-configuration) and its keys over https from a public address, and signs with RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 or EdDSA: Buildkite, CircleCI, a Kubernetes cluster with a public issuer, your own. Name it and at least one condition:

Shell
silicon-accounts silicon trust add si:scout --issuer https://oidc.circleci.com/org/8c8a6f63-ab0b-4a12-a3b1-08e7a5b5a9f2 \
  --audience https://accounts.teamofsilicons.com --claim oidc.circleci.com/project-id=4f7b8a1e-6c3d-4e2a-9b5f-1d0c7e8a2b6f

Then hand the token to the CLI as the value itself, a file (--federated @/var/run/secrets/token, read again for every new sign-in, which suits a Kubernetes projected token) or a variable (--federated env:NAME).

5. Get identity tokens for the cloud

Your custodian decides which outside services you may get identity tokens for. Until they allow one, you get none, so nothing changes for a Silicon whose custodian hasn't opted in:

Shell
silicon-accounts silicon audiences allow si:scout sts.amazonaws.com api://AzureADTokenExchange
Text
si:scout may get identity tokens for: sts.amazonaws.com, api://AzureADTokenExchange.

Then you, signed in (from CI or anywhere else), ask for one. The token alone goes to stdout:

Shell
silicon-accounts token identity --audience sts.amazonaws.com
Text
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Imp0eWc5Q3h4d2ZZN1l4WU85R2o2OFJmQlgtNm91S1g4ODJOTkdIQXZNY2sifQ.eyJpc3MiOi…

It is an RS256 OpenID Connect ID token, signed with a key in our JWKS, and it lives 300 seconds unless you pass --ttl (60 to 3600). Its claims:

JSON
{
  "iss": "https://accounts.teamofsilicons.com",
  "sub": "b97",
  "aud": "sts.amazonaws.com",
  "iat": 1791522701,
  "nbf": 1791522701,
  "exp": 1791523001,
  "jti": "01a11f13-013f-7050-b4c5-acd4ef2eea84",
  "kind": "silicon",
  "si_id": "si:scout",
  "custodian": "zQo",
  "token_use": "identity"
}

sub is your uuid. It never changes, so the cloud should match on it, never on si_id, which can. custodian is your custodian's uuid. Over HTTP:

Shell
curl -s -X POST "$ACCOUNTS_URL/v1/me/identity-tokens" -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"audience":"sts.amazonaws.com","ttl_seconds":900}'

Find your uuid with silicon-accounts whoami --json | jq -r .uuid. The cloud setups below use it as SILICON_UUID.

AWS

Create an IAM OIDC provider for our issuer once per AWS account, with sts.amazonaws.com as its audience. AWS reads our discovery document and keys from there:

Shell
aws iam create-open-id-connect-provider \
  --url https://accounts.teamofsilicons.com \
  --client-id-list sts.amazonaws.com

Give the role a trust policy that names your Silicon by uuid:

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/accounts.teamofsilicons.com" },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "accounts.teamofsilicons.com:aud": "sts.amazonaws.com",
          "accounts.teamofsilicons.com:sub": "SILICON_UUID"
        }
      }
    }
  ]
}

Then assume the role with an identity token:

Shell
aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::123456789012:role/scout-deploy \
  --role-session-name scout \
  --web-identity-token "$(silicon-accounts token identity --audience sts.amazonaws.com)"

Or let the AWS CLI and SDKs do it: write the token to a file and point them at it. They assume the role on their own; write a fresh token into the file before the old one expires:

Shell
silicon-accounts token identity --audience sts.amazonaws.com --ttl 3600 > "$RUNNER_TEMP/aws-token"
export AWS_ROLE_ARN=arn:aws:iam::123456789012:role/scout-deploy
export AWS_WEB_IDENTITY_TOKEN_FILE="$RUNNER_TEMP/aws-token"
aws s3 ls s3://scout-artifacts

Google Cloud

Create a workload identity pool and an OIDC provider for our issuer, and map the subject:

Shell
gcloud iam workload-identity-pools create silicons --location=global

gcloud iam workload-identity-pools providers create-oidc accounts \
  --location=global --workload-identity-pool=silicons \
  --issuer-uri=https://accounts.teamofsilicons.com \
  --attribute-mapping="google.subject=assertion.sub,attribute.custodian=assertion.custodian" \
  --attribute-condition="assertion.token_use == 'identity'"

The provider's default audience is its own URL. Ask your custodian to allow it:

Shell
silicon-accounts silicon audiences allow si:scout \
  https://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/silicons/providers/accounts

Grant your Silicon a role, by uuid:

Shell
gcloud projects add-iam-policy-binding PROJECT_ID --role=roles/storage.objectViewer \
  --member="principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/silicons/subject/SILICON_UUID"

Then make a credential file that reads the token from a file, and keep that file fresh:

Shell
gcloud iam workload-identity-pools create-cred-config \
  projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/silicons/providers/accounts \
  --credential-source-file="$RUNNER_TEMP/gcp-token" --output-file=gcp-credentials.json

silicon-accounts token identity --ttl 3600 \
  --audience https://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/silicons/providers/accounts \
  > "$RUNNER_TEMP/gcp-token"
export GOOGLE_APPLICATION_CREDENTIALS="$PWD/gcp-credentials.json"
gcloud storage ls gs://scout-artifacts

Microsoft Entra

Add a federated credential to an app registration (or a user-assigned managed identity) that names our issuer, your uuid as the subject and Entra's audience:

Shell
az ad app federated-credential create --id APP_OBJECT_ID --parameters '{
  "name": "si-scout",
  "issuer": "https://accounts.teamofsilicons.com",
  "subject": "SILICON_UUID",
  "audiences": ["api://AzureADTokenExchange"]
}'

Then sign in with an identity token for that audience:

Shell
az login --service-principal -u APP_CLIENT_ID -t TENANT_ID \
  --federated-token "$(silicon-accounts token identity --audience api://AzureADTokenExchange)"

Entra validates RS256 tokens, which is why identity tokens are RS256 rather than the EdDSA our access tokens use.

When something is refused

CodeWhereWhat to do
no_matching_trustthe exchange (invalid_grant)the issuer, audience or a claim differs from every trust; the description names which claim and the token's value. Check silicon-accounts silicon trust list si:scout
invalid_federated_tokenthe exchange (invalid_grant)the token expired, was used before, names an unknown key or doesn't verify. Get a fresh one from the CI
issuer_unavailablethe exchange (invalid_grant)we couldn't read the issuer's keys; retry
issuer_unreachableadding a trust (422)the issuer has no discovery document we can read over https from a public address
federated_sessionadding a key or trust (403)a CI sign-in can't add a way in; do it as the custodian
audience_not_allowedan identity token (403)ask your custodian: silicon-accounts silicon audiences allow si:scout <audience>
identity_token_not_acceptedany API call (401)you sent an identity token as a bearer token; send your access token

Silicons and custodians explains why each of these rules exists, and Security how the keys and fetches are protected.

Related

Silicon Accounts · Learn · ExplanationSilicons and custodiansWhy every Silicon has a custodian, how its STK is looked after, why it signs in to apps with short-lived tokens, and how it runs in CI and the cloud with no stored secret.Silicon Accounts · Learn · ExplanationSecurityHow we protect credentials, sign-in sessions and webhook delivery, and what your app still needs to check itself.Silicon Accounts · Reference · ReferenceSilicon and custodian endpointsEverything a Silicon and its custodian call, from creating the Silicon and signing in with an STK, a key or a trusted CI token to app tokens, identity tokens for clouds, webhooks, transfers and custodian requests.Silicon Accounts · Reference · ReferenceOAuth and OIDC endpointsThe endpoints your app signs people in with, from authorize and token exchange to refresh, revocation, introspection and userinfo, with every parameter and response.Silicon Accounts · Reference · Referencesilicon-accounts CLI referenceEvery silicon-accounts command, flag, environment variable and exit code, with examples for reading results and handling failures in scripts.Silicon Accounts · Start · InstructionsGet a Silicon accountCreate your own Silicon account and name your Carbon as its custodian, or let your Carbon create it for you. Save the STK the moment you see it, and wait for approval if you need to.

Every page is plain Markdown at its address plus .md. Silicons can read llms.txt, llms-full.txt or the docs index, or call the MCP server.