Raxx · internal docs

internal · gated ↑ index

Agent bot-token setup runbook

Purpose: Wire up programmatic Infisical access so dispatched agents authenticate as raxx-{dev,ops,pm}-bot instead of the operator's PAT.

Audience: Kristerpher (operator) — run once after the GitHub Apps are provisioned per docs/ops/runbooks/github-app-provisioning.md and the secrets are stored in Infisical.

Time: ~10 minutes (5 of those waiting for Infisical UI to load).

Tracking: Issue #335.


How it works

[Your shell env]                    [Infisical]                      [GitHub]
INFISICAL_CLIENT_ID                  /MooseQuest/raxx-dev-bot/         App 3501352
INFISICAL_CLIENT_SECRET                  APP_ID                            ↓
INFISICAL_PROJECT_ID                     INSTALLATION_ID                Installation
                                         PRIVATE_KEY_PEM                token (1hr)

         ↓                                    ↓                              ↓
         └─── Universal Auth login ────────────────────┘                      │
              ↓                                                                │
              session token (~10 min)                                          │
              ↓                                                                │
              GET /api/v3/secrets/raw?path=/MooseQuest/raxx-dev-bot ──────────┘
              ↓
              [APP_ID, INSTALLATION_ID, PRIVATE_KEY_PEM]
              ↓
              sign 9-min JWT (RS256)
              ↓
              POST /app/installations/{id}/access_tokens
              ↓
              GH_TOKEN=ghs_...

The mint script makes one Infisical login + one secrets fetch + one GitHub token mint per invocation. ~500ms-1s per gh call when used as a one-shot. For agents making ≥3 gh calls in a row, use the session pattern (eval "$(... --export)") to reuse the GitHub token for its 1-hour lifetime.

The only thing on your laptop is the Universal Auth client credentials — three env vars. Bot keys never leave Infisical except in transit.


Step 1 — Create the Infisical Machine Identity

A "Machine Identity" with Universal Auth is the agent dispatcher's identity in Infisical. Create one:

  1. Open Infisical → Access ControlIdentitiesCreate Identity
  2. Name: raxx-agent-dispatcher
  3. Auth method: Universal Auth
  4. After creation, on the identity's page click Create Client Secret — copy the client ID and client secret (the secret is shown ONCE).

Grant the identity access to the bot paths

Still on the identity page → Project Roles → assign access to your project:

The identity needs ONLY read on the bot paths. Don't grant write — the rotation pipeline writes; the dispatcher only reads.


Step 2 — Verify the bot secrets are in Infisical

Each bot's path should hold three secrets with these exact names:

/MooseQuest/raxx-dev-bot/
  APP_ID            = 3501352
  INSTALLATION_ID   = <numeric installation id>
  PRIVATE_KEY_PEM   = -----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----

(Same shape for raxx-ops-bot and raxx-pm-bot.)

If you stored them under different names earlier (e.g., app-id lowercase per the original provisioning runbook), rename them — POSIX env var conventions break with hyphens, and the mint script expects APP_ID / INSTALLATION_ID / PRIVATE_KEY_PEM exactly.


Step 2.5 — (Self-hosted Infisical only) Cloudflare Access service token

If your Infisical instance is behind Cloudflare Access (e.g., Raxx's vault.raxx.app), the API requests need to bypass CF Access too. Skip this step if you're using Infisical Cloud at app.infisical.com.

  1. Cloudflare Zero Trust dashboard → AccessService AuthService TokensCreate Service Token
  2. Name: raxx-agent-vault-access
  3. Duration: 1 year (matches the bot key rotation cadence)
  4. Copy the Client ID and Client Secret (the secret is shown ONCE)
  5. Apply to your Access app: AccessApplications → click the app fronting Infisical (e.g., vault.raxx.app) → PoliciesAdd policy - Action: Service Auth - Include: Service Token → pick raxx-agent-vault-access - Save
  6. Set two more env vars in your shell (per Step 3 below): bash export CF_ACCESS_CLIENT_ID='<service-token-client-id>' export CF_ACCESS_CLIENT_SECRET='<service-token-client-secret>'

If the mint script sees both CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET in env, it adds CF-Access-Client-Id and CF-Access-Client-Secret headers to every Infisical API call. If only one is set, the script ignores both (would 403 anyway).

Gotcha: Service Auth policies cannot be created inline from the Access app's policy builder — you have to create the policy as type "Service Auth" first in Access → Service Auth, then attach it to the Access app's Policies tab. Cloudflare's UI doesn't make this obvious.

Step 3 — Set the env vars in your shell

These env vars need to be in any shell that dispatches agents. Pick whichever pattern you already use:

.envrc in the repo root (gitignored — it's already in .gitignore patterns):

# Infisical Universal Auth
export INFISICAL_CLIENT_ID='<machine-identity-client-id>'
export INFISICAL_CLIENT_SECRET='<client-secret-shown-once>'
export INFISICAL_PROJECT_ID='<your-infisical-project-id>'

# Self-hosted Infisical override (if using vault.raxx.app):
export INFISICAL_HOST='https://vault.raxx.app'

# Cloudflare Access bypass (only if Infisical is behind CF Access):
export CF_ACCESS_CLIENT_ID='<cf-service-token-id>'
export CF_ACCESS_CLIENT_SECRET='<cf-service-token-secret>'

# Optional defaults:
# export INFISICAL_ENV='prod'                          # default
# export INFISICAL_PATH_PREFIX='/MooseQuest/'          # default

direnv allow after writing.

Option B — ~/.zshrc / ~/.bashrc

Same export lines but in your shell rc. Means every shell you open has the dispatcher creds. Slightly less secure than direnv (broader env exposure) but simpler if you're not already using direnv.

Option C — 1Password CLI / op secret references

If you already use 1Password for shell secrets:

export INFISICAL_CLIENT_ID="$(op read 'op://Private/raxx-agent-dispatcher/client-id')"
export INFISICAL_CLIENT_SECRET="$(op read 'op://Private/raxx-agent-dispatcher/client-secret')"
export INFISICAL_PROJECT_ID='<project-id>'  # not a secret

op will prompt for biometric on first use per session.


Step 4 — Smoke test

# In a shell where the env vars are set:
scripts/agents/with_bot_token.sh raxx-dev-bot gh api /user

Expected: JSON for the bot user (look for "login": "raxx-dev-bot[bot]").

Common failures:

Symptom Cause Fix
INFISICAL_* env vars not set; falling back to operator PAT Env vars aren't reaching the wrapper Reload your shell, or direnv reload, or check option A/B/C
Infisical login failed: HTTP 302 Cloudflare Access is in front of the host but no CF Access service token is configured Set CF_ACCESS_CLIENT_ID + CF_ACCESS_CLIENT_SECRET per Step 2.5
Infisical login failed: HTTP 401 Wrong client ID/secret, or identity not in the right project Verify creds; check the identity's project assignments
Infisical login failed: HTTP 403 CF Access service token is set but not authorized for the host's Access policy In Cloudflare → Access → Applications → Policies, attach the Service Auth policy that includes the agent-dispatcher service token
Infisical fetch failed at /MooseQuest/raxx-dev-bot: HTTP 403 Identity has the wrong scopes / not granted access to that path Add read permission on the bot path in Infisical
bot secrets at /MooseQuest/raxx-dev-bot/ missing keys: PRIVATE_KEY_PEM Secret stored under a different name Rename in Infisical to APP_ID / INSTALLATION_ID / PRIVATE_KEY_PEM
GitHub API returned 401 App revoked, or Installation ID is wrong, or PEM corrupted Check the App's installation page on GitHub; re-download the PEM if needed

If you can't decode the failure, run the mint script directly without --quiet:

python3 scripts/agents/mint_github_token.py --bot raxx-dev-bot

That prints the full error chain to stderr.


Rotating credentials

Bot key (PEM) rotation: Per the rotation SOP at docs/ops/runbooks/rotation/github-app-installation-token.md. Update the PEM in Infisical at the bot's path. Next agent dispatch picks up the new key automatically — no local change needed. This is the whole point of the Infisical pull.

Universal Auth client secret rotation: The Machine Identity's client secret should rotate ~365 days. Procedure:

  1. In Infisical: identity page → Create Client Secret (a second one) → copy
  2. Update INFISICAL_CLIENT_SECRET in your shell config (direnv / zshrc / 1password)
  3. Reload shell, smoke-test
  4. Once the new secret works, revoke the old client secret in Infisical

You can have two client secrets active simultaneously to make the rotation seamless.


Security notes


References