Credentials¶
Note on runtime references (2026-05-20): Some sections below reference OpenClaw config paths (e.g.,
.openclaw/...) and Pi-E token-sync mechanics that pre-date the 2026-05-20 OpenClaw retirement. The credential entries themselves (Bitwarden names, admin accounts, rotation cadence) remain accurate; OpenClaw paths should be read as historical artifacts — current agents Pi-E and Volt-E (and iBuild-E on launchd) run onagent-runnerand use the token paths documented inagent-token-sync.md; Review-E now runs in the GCP k3s cluster (rig-agent-runtime) with a cluster-managed credential.
Credential inventory with Bitwarden entry names. NEVER store actual secrets in this file.
Admin Accounts¶
| Account | Purpose |
|---|---|
| invotekas@gmail.com | Invotek admin — Cloudflare login, Tailscale login, Cloudflare Access |
| codiedev42@gmail.com | Dev/project — Bitwarden, Xcode Cloud notifications, Google Drive |
| post@stigjohnny.no | Personal. Was the vCluster Platform login, until Loft was removed from the Dell on 2026-10-02 |
Bitwarden Configuration¶
| Setting | Value |
|---|---|
| Server | vault.bitwarden.eu (EU) |
| codiedev42@gmail.com | |
| Unlock | See memory MCP (get_context(project: "global", key: "bitwarden_unlock")) |
Credential Inventory¶
GitHub¶
| Bitwarden Entry | Purpose | Used By | Expiry |
|---|---|---|---|
| GitHub PAT (Claude) | Legacy PAT — EXPIRED | Was used by Docker runners | Expired |
| GitHub OAuth Token | OAuth token for gh CLI | Docker runners (.env as GITHUB_TOKEN) |
— |
| RELEASE_PAT | Review-E PAT for release PR auto-approval | dashecorp dashe-* + dashe-cutie (via OpenTofu in dashecorp/infra) |
2026-05-30 (EXPIRED — rotation pending, #23) |
— (rig-gitops credential-ops environment secret GHCR_READ_TOKEN, plus environment variable GHCR_READER_USER, the operator's login) |
GHCR read PAT: the operator's own classic PAT, read:packages only (rig-gitops#896 D1), because no machine user exists. Set 2026-10-06 |
Only C2 (c2-ghcr-pull-secrets.yml, which encrypts it into the GHCR pull secrets), and C8, which rotates it by re-running C2 (CI/CD) |
No expiry |
— (rig-gitops credential-ops environment secret DELL_TUNNEL_TOKEN; none at repo level) |
The Dell tunnel dashecorp-dell-v2's connector token. Written by OpenTofu (infra#658, from cloudflare/dashecorp.com state, no value printed). Set 2026-10-15 |
Only C4 (c4-c5-cloudflare-secrets.yml), after the operator approves each run; it encrypts it into apps/cloudflared/dell/tunnel-token.sops.yaml (CI/CD) |
— |
— (rig-gitops credential-ops environment secrets DELL_TENANT_MAP_CLIENT_ID, DELL_TENANT_MAP_CLIENT_SECRET; none at repo level) |
The Cloudflare Access service token the Dell's conductor uses to read the tenant map. Written by OpenTofu (infra#658, from cloudflare/dashecorp.com state). Set 2026-10-15. The secret may be 64 hex or, for tokens made on or after 2026-08-26, cfast_[40 alphanumeric][8-character checksum] |
Only C5 (c4-c5-cloudflare-secrets.yml), after the operator approves each run |
1 year by default (Access service-token default, infra#660) |
— (rig-gitops credential-ops environment secrets GCP_TENANT_MAP_CLIENT_ID, GCP_TENANT_MAP_CLIENT_SECRET; none at repo level) |
GCP's own Cloudflare Access service token for its conductor's tenant-map reads. Written by OpenTofu (infra#668, second apply 18:14:43Z). Set 2026-10-15 | Only the c5-gcp step (c4-c5-cloudflare-secrets.yml, rig-gitops#1025; first run 38784866311), which fenced GCP's conductor (rig-gitops#1027) |
1 year by default (infra#660) |
— (rig-gitops credential-ops environment secret GCP_TUNNEL_TOKEN; none at repo level) |
The connector token of GCP's rotated tunnel dashecorp-gcp-v2, whose secret OpenTofu never prints (random_bytes). It replaced dashecorp-gcp, whose secret old CI logs printed (infra#654), and which was deleted on 2026-10-16 (infra#684). Written by OpenTofu (infra#678; created by the second run, 2026-10-15 23:27:55Z) |
Only the c4-gcp step (rig-gitops#1034), after the operator approves each run. It encrypts the token into apps/cloudflared/gcp/tunnel-token-v2.sops.yaml; first run #16, 2026-10-16. Since rig-gitops#1039 the step refuses the deleted tunnel's fixed ID and requires main's apps/cloudflared/gcp/tunnel-id |
Rotated by replacing random_bytes.tunnel_secret_gcp_v2 (infra docs/2026-10-15-gcp-tunnel-rotation.md) |
— (rig-cockpit-worker secret TENANT_MAP_READERS) |
The worker's tenant-map reader set: [dell, gcp] since infra#668 (2026-10-15). Under infra#658 it was [] when var.dell_tenant_map_reader_enabled is false. Written by OpenTofu (infra#658, from cloudflare/dashecorp.com state). Set 2026-10-15 |
rig-cockpit-worker deploy.yml, which puts it on the Worker (first: run 38718557371; [dell, gcp] live 18:17:52Z) |
Each reader's Access token: 1 year by default (infra#660) |
— (rig-cockpit-worker secret ROUTER_WEBHOOK_SECRET) |
The webhook router's HMAC secret: the same shared value as infra's WEBHOOK_SECRET and the conductor's GITHUB_WEBHOOK_SECRET. Written by OpenTofu (infra#664, from var.webhook_secret; GitHub refuses Actions secret names that start with GITHUB_). Set 2026-10-15 |
rig-cockpit-worker deploy.yml, which puts it on the Worker as GITHUB_WEBHOOK_SECRET on every deploy (rig-cockpit-worker#302) |
Rotates with WEBHOOK_SECRET: after the apply, deploy the worker, or the router (once live) refuses deliveries with 401 |
— (rig-cockpit-worker repo secret INBOX_COMMENT_TOKEN) |
Fine-grained token "Inbox comments": dashecorp/operator-inbox only, Issues read and write. Lets the Worker post a Done as an issue comment (Operator inbox). Set 2026-10-06 |
rig-cockpit-worker | 2027-10-07: the dashecorp enterprise caps fine-grained tokens at 366 days. Renewal is tracked in the deadline tracker (#45) |
— (rig-cockpit-worker repo secret INFRA_DISPATCH_TOKEN) |
Fine-grained token "rig-cockpit INFRA_DISPATCH_TOKEN": dashecorp/infra only, Actions read and write (plus the mandatory Metadata read). Lets the Worker dispatch infra's rig-location.yml with gcp=running, the GCP automatic start (rig-gitops#1021 step 7). It has no Deployments permission, so it cannot approve rig-standby: it can never stop GCP or point the stable host at the Dell. Made by the operator, 2026-10-16 08:47:37Z |
rig-cockpit-worker. Unused until rig-cockpit-worker#315 adds it to deploy.yml's synced secrets |
2027-10-17 (the 366-day enterprise cap). Renewal is tracked in the deadline tracker (#47) |
codi-e-bot App PEM (ORCHESTRATOR_BOT_PEM) |
Read-only GitHub App (ID 4127706, @dashecorp) for orchestrator cross-org reads; replaced the expired GH_PAT |
claude-3 monitor-infra.yml + build-unified-docs.yml (mint per-org tokens via actions/create-github-app-token) |
App key (no expiry) — must be PKCS#8; re-set as PKCS#8 2026-09-30 (#765) |
GitHub App PEMs: Stored locally on each agent host, not in Bitwarden. See .claude/skills/github-apps/SKILL.md for paths. Exception: the codi-e-bot App PEM is also held as the ORCHESTRATOR_BOT_PEM repo secret on Stig-Johnny/claude-3 (PKCS#8), and its source copy is in the password field of the Bitwarden Login item codi-e-bot (PKCS#1; the item has no notes or attachments). actions/create-github-app-token requires PKCS#8, so convert before upload — runbook docs/runbooks/ops-e-reset-orchestrator-bot-pem.md.
The GHCR read PAT is single-purpose. Reuse an existing PAT only if its scope is exactly read:packages. The legacy agent PATs are never used for it.
Credential automation¶
The direction the operator approved in claude-3#1027: credentials stop passing through agents and chat.
- From state to SOPS: secrets flow from OpenTofu state to SOPS through CI. #897 P2 (GitHub Actions secrets from remote state) is the next step.
- Phone-approved workflows: pre-reviewed workflows behind the credential-ops environment, approved by the operator in GitHub Mobile (CI/CD).
- GitHub Mobile shows no Approve for runs triggered under the operator's own login (no notification is sent), so the operator approves those in Safari (run 38784866311).
- The approve-rule (claude-3#1191, merged 2026-10-15, CLAUDE.md): the orchestrator may approve a waiting rig-gitops credential-ops run (C2, C4/C5) only if it dispatched the run itself from main, and the operator replied with a literal yes naming that step after being told the run number, step and target branch. It approves through POST /repos/dashecorp/rig-gitops/actions/runs/<id>/pending_deployments with a comment quoting the yes and its timestamp. Never a rejection, never another environment.
- Later: approvals as Rig Cockpit push notifications, and phase 2, a Cockpit credential-intake page (operator decision pending).
- Asking the operator: credential steps are filed in dashecorp/operator-inbox, never asking for the secret itself (Operator inbox).
Rig Cockpit approvals and push¶
- The approvals webhook secret signs both Cockpit GitHub webhooks (
/v1/github/waitingand/v1/github/inbox, see Networking). It flows OpenTofurandom_password→ rig-cockpit-worker Actions secretAPPROVALS_WEBHOOK_SECRET→deploy.yml→ the Worker, with no manual step (dashecorp/infra#582, #585). - APNs, not set yet: the Worker needs four rig-cockpit-worker repo secrets, requested in operator-inbox #5:
APNS_KEY_ID,APNS_TEAM_ID,APNS_AUTH_KEY(the full.p8) andAPNS_BUNDLE_ID(no.invotek.rig-cockpit). Once they exist, check the names withgh secret list --repo dashecorp/rig-cockpit-worker, rundeploy.yml, and have the operator tap Send Test Push in More → Settings. Until then the app says "Push is not configured", and pushes fall back to Discord #admin.
Apple / App Store Connect¶
| Bitwarden Entry | Purpose | Used By |
|---|---|---|
| App Store Connect API Key | ASC API (issuer ID, key ID, private key) | appstoreconnect-mcp |
| Apple ID (u7232055051@gmail.com) | ASC portal login, Xcode signing | Browser automation |
Cloud Services¶
| Bitwarden Entry | Purpose | Used By |
|---|---|---|
| Cloudflare API Token | Cloudflare API access | cloudflare MCP, GitHub Actions |
| OpenAI API Key (Claude) | DALL-E image generation, Nutri-E workers | Blog images, Nutri-E |
| Google Service Account Key | Play Store deployment | dashe-reward-e Android |
Cloudflare account facts (non-secret): account ID 59710bf016d417f860051f1f00b00258 (dashboard identifier). The local wrangler OAuth token lives at ~/.config/.wrangler/config/default.toml on the operator machine (not in Bitwarden); the Cloudflare API Token above is what the cloudflare MCP + CI use.
Messaging / Notifications¶
| Bitwarden Entry | Purpose | Used By |
|---|---|---|
| Discord Bot Token (Codi-E) | Codi-E Discord bot | discord MCP |
| Discord Bot Token (Pi-E) | Pi-E Discord bot | Pi-E agent-runner |
| Discord Bot Token (Volt-E) | Volt-E Discord bot | Volt-E agent-runner |
| Discord Bot Token (Review-E) | Review-E Discord bot | Review-E agent-runner |
| ~~Discord Bot Token (ATL-E)~~ | ~~ATL-E Agent bot~~ — retired 2026-05-20 (ATL-E replaced by rig-conductor + rig-planner) | — |
| Telegram Bot Token | Telegram notifications (legacy) | telegram-notifications-mcp |
| Pushbullet API Key | SMS forwarding | pushbullet-sms-mcp |
Revenue / Subscriptions¶
| Bitwarden Entry | Purpose | Used By |
|---|---|---|
| RevenueCat API Key | Subscription management | revenuecat MCP |
GCP¶
| Resource | Value |
|---|---|
| Infra Project | invotek-github-infra |
| Service Account | github-actions@invotek-github-infra.iam.gserviceaccount.com |
| Auth | Workload Identity Federation (keyless, no secret needed) |
| OpenTofu state encryption | Since 2026-09-30, dashecorp/infra state in gs://tablez-terraform-state is encrypted client-side (AES-GCM) with the KMS key tofu-state (europe-north1), so a bucket read alone reveals no secrets. The CI SA has cryptoKeyEncrypterDecrypter on the key and project roles/cloudkms.admin (infra BOOTSTRAP step 22). All ten root modules are encrypted since 2026-10-01 (dashecorp/infra#492 closed). Nine reject plaintext state. k8s/dell/k3s was encrypted from its first apply by GitHub Actions (#503, no fallback), and the two never-applied mac-mini/* modules were retired (#512). The bucket's old plaintext versions (1,069 from before encryption) and four orphaned plaintext prefixes were purged on 2026-10-02 by OpenTofu lifecycle rules (infra#537, #521). The bucket is now google_storage_bucket.tofu_state in gcp/dashecorp, with prevent_destroy, and it keeps 30 days of stale (encrypted) versions. Check a module by printing only the state object's top-level keys: encrypted state shows encrypted_data, encryption_version, lineage, meta and serial |
| Dell CI SSH key | ED25519 key generated by OpenTofu (tls_private_key.hosts_ci, github/dashecorp). The private half lives in KMS-encrypted state and the dashecorp/infra hosts environment secret HOSTS_SSH_PRIVATE_KEY, usable from main only. The public half is the repo variable HOSTS_SSH_PUBLIC_KEY. It is not in Bitwarden, and no person handles it. Only claude on the Dell trusts it, from tailnet addresses (infra BOOTSTRAP §23). To rotate: rename the resource in a PR, then swap the Dell's line |
GCP default Compute account (1090472687120-compute@, the rig VMs) |
No project Editor since 2026-10-02. Its project roles are exactly roles/compute.storageAdmin plus the custom role gcePdCsiAttach, and it can act as itself (BOOTSTRAP §24). gcp/dashecorp/node-sa.tf fails the plan if those roles drift or anyone gets Editor (infra#539). Agent namespaces cannot reach the metadata server (rig-gitops#887) |
| Billing Account | 015BBC-422A59-EB7AF4, which also pays for cuti-e, claude-memory-mcp, reward-e-app and dev-entropy-477313-v5 |
| Human owner | stig@dashecorp.com since 2026-09-29, also of claude-memory-mcp, cuti-e, nutri-e-app, reward-e-play-store, reward-e-app-dev and dev-entropy-477313-v5. post@nutrieapp.com owned them until then; its domain was to lapse on 2026-10-05 (claude-3#975), but Domeneshop auto-renewed it on 2026-10-06 until 2027-10-05; the operator is asking for the renewal to be cancelled and refunded (deadline #4) |
| Billing administrators | codiedev42@gmail.com and stig@dashecorp.com. post@nutrieapp.com stays until it is retired by 2026-11-04 (deadline #36) |
| Dashecorp billing | Dashecorp Billing 0151A6-EDAFD0-29E42A, managed by stig@dashecorp.com. No project is on it yet |
| Other accounts | codiedev42@gmail.com owns reward-e-app. invotekas@gmail.com has no access to the rig's project or its billing account (claude-3#901) |
Rig (k3s) — SOPS-managed secrets (dashecorp/rig-gitops)¶
Names, keys and pairings only — the values live encrypted in git and decrypted only inside the cluster.
| Secret (namespace/name) | Keys | Consumer / notes |
|---|---|---|
rig-conductor/rig-conductor-secrets |
10 pre-existing keys + runpod-api-key (added rig-gitops#727, re-provisioned with the real rpa_ key in rig-gitops#731, both 2026-09-08) |
rig-conductor. runpod-api-key → RUNPOD_API_KEY (optional secretKeyRef: the conductor boots without it and the RunPod idle monitor simply exits). RunPod key = Restricted: api.runpod.io/graphql Read/Write, api.runpod.ai None; drives the conductor's volume-first wake / stop / idle auto-stop of the operator's vLLM pod (rig-conductor#2008). |
dashecorp/rig-e-secrets |
chat-api-token, openai-api-key, anthropic-api-key, database-url |
Rig-E. chat-api-token must be byte-identical to the Cloudflare Worker secret below — the pair IS the auth gate; a mismatch is a 401 that the cockpit SPA renders as a logout. openai-api-key = the vLLM key of the RunPod pod (rig-gitops#723). anthropic-api-key is misnamed: it holds the Claude subscription OAuth token (CLAUDE_CODE_OAUTH_TOKEN), not an API key — rename tracked in rig-agent-runtime#664. |
dashecorp/{review-e,dev-e,planner}-secrets, dashecorp/ar-pull-secret |
tenant copies of the base fleet secrets + Artifact Registry pull cred | dashecorp tenant fleet (rig-gitops apps/dashecorp/kustomization.yaml header documents each). |
CF Worker rig-cockpit-worker → AGENT_CHAT_TOKEN_RIG_E |
(Worker secret) | Set with wrangler secret put AGENT_CHAT_TOKEN_RIG_E --name rig-cockpit-worker in the CF account 59710bf0…. Not in deploy.yml's sync list, so it survives deploys but is not reproducible from git. AGENT_CHAT_ROUTES next to it is a plain [vars] entry in wrangler.toml, not a secret. |
Finding (2026-10-03): the conductor's GitHub token is the operator's PAT. rig-conductor-secrets/github-token holds the operator's PAT, so MergeGate merges as the operator. It should be narrowed. That is operator work, and it isn't filed yet: it needs prepared steps first.
Encryption facts (non-secret): age recipient (public) age1n06gnyjdll2hpl0t2ufupww2v0denz73cz02vpg0k3qcgxhhfflsyu8dlq; the cluster decrypts with flux-system/sops-age; Flux Kustomization dashecorp-bootstrap (10 min interval). The rig age private key is not on the operator Mac and is not needed: to add a key to an existing secret, rebuild it — read the current values from the live cluster secret (kubectl get secret … -o json over gcloud compute ssh invotek-k3s --account stig@dashecorp.com; .data is base64), add the new key from a hidden prompt, re-encrypt with the public recipient. Gate before staging: ENC[ count = 5 structural + N values + 1 MAC (17 for the 11-key conductor secret), key count inside the stringData: block only, no plaintext. main is linear with no force-push, so a plaintext commit there is unscrubbable — the gate is the last line.
Operator-held, not in Bitwarden: the vLLM key and the RunPod API key were minted by the operator and are kept in his personal manager (Enpass), not Bitwarden — the rig docs/CLAUDE.md still say Bitwarden; reconcile when the RunPod account gets MFA (it has none today).
Agent Authentication Methods¶
All agents share ONE Claude Max subscription: invotekas@gmail.com (max tier).
| Agent | Claude Auth | GitHub Auth | How Token Stays Alive |
|---|---|---|---|
| Codi-E / Claude-4/5/6 | Local Claude Max | User's gh CLI | Manual claude auth login |
| Pi-E | OAuth (invotekas@gmail.com) | pie-agent-bot GitHub App | Claude cron job (3-day keepalive) |
| Volt-E | OAuth (invotekas@gmail.com) | volt-e-agent-bot GitHub App | Claude cron job (3-day keepalive) |
| Review-E | OAuth (invotekas@gmail.com) | review-e-bot GitHub App | Cluster-managed (GCP k3s rig-agent-runtime) |
| iBuild-E | Local Claude Max | ibuild-e-bot GitHub App | Local LaunchAgent |
Token Lifecycle¶
- Primary: Each Pi/VPS remote agent (Pi-E, Volt-E) runs a Claude cron job that keeps the OAuth token alive for 3 days by refreshing it automatically. (Review-E runs in GCP k3s and uses a cluster-managed credential, not this cron path.)
- Bootstrap/Fallback: M4 MacBook pushes tokens via
push-claude-creds.sh(LaunchAgent, every 5 min). Only pushes when local token is newer than remote. Used to bootstrap new agents or recover from expiry. - Emergency: VNC into agent host, run
claude loginto get a fresh token directly.
Token Sync Infrastructure¶
LaunchAgents on MacBook Air M4:
- com.invotek.push-claude-creds — pushes OAuth to Pi-E only on credential change + 5-min interval (Volt-E disabled — it uses interactive claude login + VNC; Review-E migrated to GCP k3s and is no longer a push target)
- com.invotek.refresh-github-tokens — generates fresh ghs_ GitHub App tokens every 30 min
- ~~ai.invotek.claude3.token-sync — additional token sync~~ (DISABLED 2026-03-26)
- ai.invotek.claude3.heartbeat — heartbeat check
Push targets (from script):
- Pi-E: claude@100.107.48.17 (key: rpi-claude), path: /home/claude/.openclaw/agents/main/agent/auth-profiles.json
- Volt-E: root@100.111.142.118 (key: vps-srv1099021), path: ~/.claude/credentials.json (agent-runner)
- Review-E: migrated to GCP k3s (rig-agent-runtime) — credential is cluster-managed, no longer pushed to Pi4-02
Note: iBuild-E manages its own token via environment variable in ~/.zshrc (disabled com.invotek.ibuild-e.token-sync), not through the push script. The ai.invotek.mac-executor.plist LaunchAgent that also carried it was retired on 2026-09-25 with the rest of claude's LaunchAgents (see hosts.md).
Pi-E Token Watchdog: Cron every 5 min, Discord alert 1hr before OAuth expiry.
Rotation Schedule¶
| Credential | Rotation | Next Due |
|---|---|---|
| RELEASE_PAT | Manual (Review-E PAT) | 2026-05-30 (EXPIRED — rotation pending, #23) |
| GitHub App tokens | Auto (1hr lifetime, refreshed every 30min) | — |
| OAuth tokens | Auto (refreshed by push script / VNC login) | — |
| Cloudflare API Token | Manual | Check Bitwarden |
| ASC API Key | Manual | Check Bitwarden |
Expired / Deprecated¶
| Entry | Status | Notes |
|---|---|---|
GitHub PAT (Claude) (ghp_*) |
EXPIRED | Replaced by OAuth + GitHub Apps |
| Telegram Bot Token | Deprecated | Discord preferred, Telegram still configured |