Vault token taxonomy — function mapping, tag system, and provisioning template
Status: Phase 2 in progress — Cloudflare rename complete (#754); other vendors pending Owner: operator / sre-agent Last updated: 2026-05-01 UTC Tracking: #754 (CF token rename), refs #81 (Epic: SDLC, Tooling, and Security Hardening)
Related issues: #402 (secrets-store org review), #596 (env coverage audit), #747 (WAF token gap), #253 (rotation pipelines)
Background
As of 2026-04-30 the vault at vault.raxx.app has 20+ credentials across 8 vendors. Several pain points have emerged:
- Token names do not communicate scope —
CLOUDFLARE_RAXX_AUTOMATION_API_TOKENwas tested against multiple CF endpoints before an SRE agent confirmed it was missing WAF Edit scope (see #747). - Multiple tokens per vendor with overlapping-sounding names force reliance on operator memory as the source of truth.
- No canonical template for provisioning a new token — what scopes to grant, where to store it, what to call it.
- Environment coverage (prod vs. staging vs. dev) and rotation cadence are inconsistently captured.
This document locks in a taxonomy that solves all four.
Section 1 — Naming convention
Chosen pattern: <VENDOR>_<FUNCTION>_<SCOPE>
<VENDOR> two-to-four-letter vendor code (CF, HK, PM, AWS, ANT, GH, STR, ALP, INF, SL)
<FUNCTION> verb-noun describing what the token does (DNS_EDIT, WAF_EDIT, PAGES_DEPLOY, etc.)
<SCOPE> optional — only present when a vendor issues the same function-type for multiple targets
Examples:
| Proposed name | Vendor | Function | Scope |
|---|---|---|---|
CF_DNS_EDIT_RAXX_APP |
Cloudflare | DNS record write | raxx.app zone |
CF_DNS_EDIT_GETRAXX_COM |
Cloudflare | DNS record write | getraxx.com zone |
CF_WAF_EDIT_RAXX_APP |
Cloudflare | WAF rule write | raxx.app zone |
CF_PAGES_DEPLOY |
Cloudflare | Pages project deploy | account-wide |
CF_ACCESS_MGMT |
Cloudflare | Access app configuration | account-wide |
CF_ACCESS_SVC_VAULT |
Cloudflare | CF Access service token — vault bypass | vault.raxx.app |
CF_ACCESS_SVC_CONSOLE |
Cloudflare | CF Access service token — console bypass | console.raxx.app |
HK_PLATFORM_FULL |
Heroku | Platform API full-account | account-wide |
PM_SERVER_MAIL |
Postmark | Transactional email send (server scope) | raxx.app server |
PM_ACCOUNT_MGMT |
Postmark | Account admin (create servers, view stats) | account-wide |
AWS_INFISICAL_BOOTSTRAP |
AWS IAM | Lightsail + Cloudflare bootstrap only | infisical-bootstrap user |
AWS_BILLING_READ |
AWS IAM | Cost Explorer read | account-wide |
ANT_CLAUDE_SDK |
Anthropic | Claude API SDK calls | account-wide |
GH_READONLY |
GitHub | Read-only repo + org API | MooseQuest org |
GH_APP_DEV_BOT |
GitHub App | dev-bot installation token source | raxx-dev-bot App |
GH_APP_OPS_BOT |
GitHub App | ops-bot installation token source | raxx-ops-bot App |
GH_APP_PM_BOT |
GitHub App | pm-bot installation token source | raxx-pm-bot App |
STR_BILLING_WRITE |
Stripe | Restricted key — billing/subscription write | account-wide |
ALP_PAPER_TRADING |
Alpaca | Paper trading API | paper account |
ALP_LIVE_TRADING |
Alpaca | Live trading API | live account |
INF_SVC_DISPATCHER |
Infisical | Service token — agent dispatcher read | /MooseQuest/raxx--bot/ |
SL_BOT_NOTIFY |
Slack | Bot token — Slack notification DMs | MooseQuest workspace |
Why this pattern over deeper path nesting
Two alternatives were evaluated:
Option A — deeper path, shorter names:
/MooseQuest/cloudflare/dns-edit/RAXX_APP_TOKEN
/MooseQuest/cloudflare/waf-edit/RAXX_APP_TOKEN
Advantage: path carries the metadata, secret name is just TOKEN.
Problem: Infisical's feedback_vault_folder_must_exist.md constraint means every leaf folder must be pre-created. Four Cloudflare function-types = four folders to manage. More surface area for folder-creation 404s. Path segments do not appear in audit log snippets — you need the full path context to understand the audit trail.
Option B — vendor flat with fully-qualified name (chosen):
/MooseQuest/cloudflare/CF_DNS_EDIT_RAXX_APP
/MooseQuest/cloudflare/CF_WAF_EDIT_RAXX_APP
Advantage: the secret name is self-describing. Audit log entries, vault list output, Slack alerts, and console UI all show the key name without needing path context. One folder per vendor (already created). Consumer code reads CF_DNS_EDIT_RAXX_APP and anyone reading the code knows the function and scope without looking up a mapping.
Decision: Option B. One folder per vendor, self-describing key names. The <VENDOR>_<FUNCTION>_<SCOPE> pattern carries enough context in the name alone.
Scope qualifier rules
- Omit scope when there is exactly one token for that function (
HK_PLATFORM_FULL— Heroku has one account). - Include zone/app scope when the vendor may issue separate tokens per zone or deployment (
CF_DNS_EDIT_RAXX_APPvs.CF_DNS_EDIT_GETRAXX_COM). - Use
_FULLsuffix when the token has unrestricted (admin) access to its function domain — signals to provisioners that least-privilege is not achieved and a scoped replacement should be prioritized.
Companion secrets (no change from #402 decision)
Companion metadata fields use double-underscore suffix convention:
CF_DNS_EDIT_RAXX_APP__EXPIRES_AT— ISO-8601 UTC expiryCF_DNS_EDIT_RAXX_APP__SCOPES— space-separated vendor scope names (for quick audit without hitting vendor API)CF_ACCESS_SVC_VAULT__CLIENT_ID— paired ID for CF Access service tokens
Section 2 — Tagging strategy
Tags are applied in Infisical on each secret. Four dimensions — no more.
Tag set
function: — What the token does
| Tag | Meaning |
|---|---|
function:dns-edit |
Write DNS records at vendor |
function:waf-edit |
Create/modify WAF / firewall rules |
function:pages-deploy |
Deploy to Cloudflare Pages or equivalent CDN |
function:access-mgmt |
Configure CF Access apps, policies, service tokens |
function:access-bypass |
CF Access service token that bypasses Access gate |
function:platform-api |
Full platform admin key (Heroku, AWS account-level) |
function:mail-send |
Send transactional email |
function:mail-admin |
Manage mail servers / accounts |
function:billing-read |
Read billing + cost data |
function:billing-write |
Write billing / subscription (Stripe) |
function:trading-paper |
Paper trading API calls |
function:trading-live |
Live trading API calls (production money) |
function:ai-sdk |
Call LLM API (Anthropic Claude) |
function:ci-deploy |
Used by CI/CD pipelines for deploy |
function:agent-identity |
GitHub App identity source for bot agents |
function:repo-read |
Read-only access to version control / repo API |
function:vault-read |
Read secrets from Infisical |
function:notify |
Send notifications (Slack, webhook) |
sensitivity: — Production risk if leaked
| Tag | Criteria |
|---|---|
sensitivity:critical |
Leak enables live-money trades, production data deletion, or account takeover. E.g., live trading key, Heroku full platform key. |
sensitivity:high |
Leak enables production write on infra (DNS, WAF, Access, deploy). E.g., CF_DNS_EDIT_RAXX_APP, CF_WAF_EDIT_RAXX_APP. |
sensitivity:medium |
Leak enables staging/paper operations, cost exposure, or operational disruption without production data risk. E.g., paper trading keys, billing-read. |
sensitivity:low |
Read-only or dev-only tokens. Leak is embarrassing but not operationally damaging. E.g., GH_READONLY. |
consumer: — Which system uses the token
| Tag | Meaning |
|---|---|
consumer:ci |
GitHub Actions workflows |
consumer:console |
raxx-console backend (vault.py reads at boot) |
consumer:agent |
Agent dispatch (mint_github_token.py, sre-agent, pm-agent, etc.) |
consumer:operator |
Direct operator CLI use (terraform, ad-hoc scripts) |
consumer:raptor |
backend_v2 (Raptor) runtime |
consumer:terraform |
Terraform provider auth |
Multiple consumer tags are allowed on one secret (e.g., a DNS token used by both Terraform and CI).
rotation: — Cadence
| Tag | Meaning |
|---|---|
rotation:30d |
Monthly — high-privilege programmatic keys (AWS IAM access keys) |
rotation:60d |
Every two months — live trading keys |
rotation:90d |
Quarterly — standard rotation for most tokens |
rotation:180d |
Semi-annual — lower-risk tokens, operator-heavy rotation |
rotation:annual |
Once a year — GitHub App private keys, long-lived identities |
rotation:non-rotating |
Does not rotate by design (e.g., AWS Account ID — a config value, not a secret) |
Tag discipline rules
- Every secret gets exactly one tag from each dimension.
- If a token is used by multiple consumers, pick the primary one — the one that would be paged if the token expired.
- Tags are not a substitute for the companion
__SCOPESfield. Tags say what the token broadly does;__SCOPESdocuments the exact vendor permission strings. sensitivity:criticalrequires a paired ops review in the provisioning PR — no solo provisioning.
Section 3 — Environment usage
Infisical environments: prod, staging, dev.
Guiding rules
Rule 1 — Env-specific credentials live in both prod and staging with different values. If the vendor isolates accounts or projects by environment (separate Heroku apps, separate Alpaca paper accounts), the token must have env-specific values. Both envs are populated, values differ.
Rule 2 — Account-wide tokens that serve all environments live in prod only, consumed by both.
A Cloudflare account token (DNS, WAF, CF Access) controls the whole account regardless of whether a staging or prod deploy calls it. There is one Cloudflare account. Store in prod; consumers in staging read from prod path explicitly. Do NOT duplicate with an identical value into staging — duplicates drift and cause "which env's value is real?" confusion.
Rule 3 — Dev environment is populated only for tokens that have a dev-mode analog.
Most secrets do not have a dev analog (there is no "dev CF account"). Dev env in Infisical stays sparse. Local dev uses .env files (gitignored). Infisical dev is used only for tokens where the vendor offers sandbox/test mode keys (e.g., Stripe test-mode key = dev env).
Classification table
| Token | Prod | Staging | Dev | Notes |
|---|---|---|---|---|
CF_DNS_EDIT_RAXX_APP |
yes | no — reads prod | no | One CF account; staging CI uses the same token |
CF_DNS_EDIT_GETRAXX_COM |
yes | no | no | Same |
CF_WAF_EDIT_RAXX_APP |
yes | no | no | Same |
CF_PAGES_DEPLOY |
yes | no | no | CF Pages has one project; envs are CF Pages environments not Infisical envs |
CF_ACCESS_MGMT |
yes | no | no | Account-wide |
CF_ACCESS_SVC_VAULT |
yes | yes (separate token) | no | Staging may need its own service token if Access policies differ |
CF_ACCESS_SVC_CONSOLE |
yes | yes (separate token) | no | Same |
HK_PLATFORM_FULL |
yes | yes (different key) | no | Heroku has separate staging + prod apps; platform key may be same account but should be scoped per app post-hardening |
PM_SERVER_MAIL |
yes | yes (different key) | no | Separate Postmark servers for prod/staging |
PM_ACCOUNT_MGMT |
yes | no | no | Account-wide admin |
AWS_INFISICAL_BOOTSTRAP |
yes | no | no | Single IAM user; not replicated |
AWS_BILLING_READ |
yes | no | no | Account-wide |
ANT_CLAUDE_SDK |
yes | no | no | One Anthropic account; staging + dev share prod key |
GH_READONLY |
yes | no | no | One GitHub org |
GH_APP_DEV_BOT |
yes | no | no | One GitHub App installation |
GH_APP_OPS_BOT |
yes | no | no | Same |
GH_APP_PM_BOT |
yes | no | no | Same |
STR_BILLING_WRITE |
yes | no | dev (test key) | Stripe test-mode key goes in dev env |
ALP_PAPER_TRADING |
yes | yes (different key) | no | Paper account can have separate keys per env |
ALP_LIVE_TRADING |
yes | no | no | Never in staging |
INF_SVC_DISPATCHER |
yes | no | no | Dispatcher reads only prod secrets |
SL_BOT_NOTIFY |
yes | no | no | One Slack workspace |
Section 4 — Token-to-function index
All tokens currently known in vault or referenced in rotation matrix / issues, grouped by vendor.
Cloudflare — User API Tokens (/MooseQuest/cloudflare/)
| Current name | Proposed name | Function | Required vendor scopes | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|---|
CLOUDFLARE_EDIT_DNS |
CF_DNS_EDIT_RAXX_APP |
Write DNS records for raxx.app zone | Zone > DNS > Edit, Zone > Zone > Read | consumer:terraform, consumer:ci |
sensitivity:high |
rotation:90d |
CLOUDFLARE_RAXX_AUTOMATION_API_TOKEN |
CF_PAGES_DEPLOY |
Deploy to CF Pages + Access app read | Account > Cloudflare Pages > Edit, Zone > Zone > Read | consumer:ci |
sensitivity:high |
rotation:90d |
CLOUDFLARE_ACCESS_MGMT_TOKEN |
CF_ACCESS_MGMT |
Manage CF Access applications and policies | Account > Access: Apps and Policies > Edit | consumer:terraform, consumer:operator |
sensitivity:high |
rotation:90d |
CLOUDFLARE_PAGES_DEPLOY_TOKEN |
CF_PAGES_DEPLOY |
Deploy to CF Pages (if distinct from automation token) | Account > Cloudflare Pages > Edit | consumer:ci |
sensitivity:high |
rotation:90d |
| (missing) | CF_WAF_EDIT_RAXX_APP |
Write WAF + rate-limit rules for raxx.app zone | Zone > WAF > Edit, Zone > Zone > Read | consumer:ci, consumer:operator |
sensitivity:high |
rotation:90d |
| (missing) | CF_DNS_EDIT_GETRAXX_COM |
Write DNS records for getraxx.com zone | Zone > DNS > Edit, Zone > Zone > Read | consumer:terraform |
sensitivity:high |
rotation:90d |
Note:
CLOUDFLARE_RAXX_AUTOMATION_API_TOKENandCLOUDFLARE_PAGES_DEPLOY_TOKENmay overlap in scope — Kristerpher should confirm whether these are the same token stored under two names or two distinct tokens. If the same, one should be deprecated after migration. See Section 7 open question.
Cloudflare — Access Service Tokens (/MooseQuest/cloudflare/)
| Current name | Proposed name | Function | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|
CF_ACCESS_SERVICE_TOKEN_CONSOLE |
CF_ACCESS_SVC_CONSOLE |
Bypass CF Access gate for console.raxx.app | consumer:ci, consumer:console |
sensitivity:medium |
rotation:90d |
CF_ACCESS_SERVICE_TOKEN_VAULT_PROBE |
CF_ACCESS_SVC_VAULT |
Bypass CF Access gate for vault.raxx.app (agent dispatcher) | consumer:agent |
sensitivity:high |
rotation:90d |
Heroku (/MooseQuest/heroku/)
| Current name | Proposed name | Function | Required vendor scopes | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|---|
HEROKU_API_KEY |
HK_PLATFORM_FULL |
Full Heroku platform admin — all apps | Full account (Heroku tokens are not scoped) | consumer:ci |
sensitivity:critical |
rotation:90d |
HEROKU_PLATFORM_API_TOKEN |
HK_PLATFORM_FULL |
Appears to duplicate the above — confirm | Full account | consumer:ci |
sensitivity:critical |
rotation:90d |
Note: Two Heroku entries in the rotation matrix (
HEROKU_API_KEYandHEROKU_PLATFORM_API_TOKEN) likely refer to the same logical credential. Confirm and deduplicate; see Section 7.
Postmark (/MooseQuest/postmark/)
| Current name | Proposed name | Function | Required vendor scopes | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|---|
POSTMARK_SERVER_API_KEY |
PM_SERVER_MAIL |
Send transactional email from raxx.app server | Server token (send only for one server) | consumer:raptor |
sensitivity:medium |
rotation:180d |
POSTMARK_ACCOUNT_API_KEY |
PM_ACCOUNT_MGMT |
Account admin — create/configure servers | Account API token | consumer:operator |
sensitivity:high |
rotation:180d |
AWS (/MooseQuest/aws/)
| Current name | Proposed name | Function | Required vendor scopes | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|---|
AWS_ACCESS_KEY_ID |
AWS_INFISICAL_BOOTSTRAP (key component) |
Lightsail VM ops + CF bootstrap | Lightsail full, limited IAM user (claude-infisical-bootstrap) |
consumer:operator |
sensitivity:high |
rotation:30d |
AWS_SECRET_ACCESS_KEY |
AWS_INFISICAL_BOOTSTRAP (secret component) |
Paired with above | Same | consumer:operator |
sensitivity:high |
rotation:30d |
AWS key pairs are stored as companion secrets:
AWS_INFISICAL_BOOTSTRAP__ACCESS_KEY_IDandAWS_INFISICAL_BOOTSTRAP__SECRET_ACCESS_KEY. The base key name is the logical credential; suffixes are the two components. This avoids a_KEY_ID/_SECRET_KEYnaming ambiguity.
Anthropic (/MooseQuest/anthropic/)
| Current name | Proposed name | Function | Required vendor scopes | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|---|
ANTHROPIC_API_KEY |
ANT_CLAUDE_SDK |
Call Claude API (all SDK-capable models) | Account-level API key | consumer:agent, consumer:raptor |
sensitivity:high |
rotation:90d |
GitHub (/MooseQuest/github/)
| Current name | Proposed name | Function | Required vendor scopes | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|---|
GITHUB_API_READONLY_TOKEN |
GH_READONLY |
Read-only repo + org metadata | Contents:read, Metadata:read | consumer:agent |
sensitivity:low |
rotation:90d |
GitHub Apps — bot identities (/MooseQuest/raxx-dev-bot/, /MooseQuest/raxx-ops-bot/, /MooseQuest/raxx-pm-bot/)
Each bot path holds three companion secrets (APP_ID, INSTALLATION_ID, PRIVATE_KEY_PEM). Under the new taxonomy these are grouped as one logical credential per bot:
| Logical credential | Path | Function | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|
GH_APP_DEV_BOT |
/MooseQuest/raxx-dev-bot/ |
Source of installation tokens for raxx-dev-bot[bot] | consumer:agent |
sensitivity:high |
rotation:annual |
GH_APP_OPS_BOT |
/MooseQuest/raxx-ops-bot/ |
Source of installation tokens for raxx-ops-bot[bot] | consumer:agent |
sensitivity:high |
rotation:annual |
GH_APP_PM_BOT |
/MooseQuest/raxx-pm-bot/ |
Source of installation tokens for raxx-pm-bot[bot] | consumer:agent |
sensitivity:high |
rotation:annual |
Bot key paths retain their existing path structure (
/MooseQuest/raxx-*-bot/) because mint_github_token.py hard-codes these paths. Rename only after updating the script.
Stripe (/MooseQuest/stripe/)
| Current name | Proposed name | Function | Required vendor scopes | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|---|
STRIPE_RESTRICTED_KEY |
STR_BILLING_WRITE |
Create/manage subscriptions, customers, invoices | Restricted key: Customers write, Subscriptions write, Invoices read | consumer:raptor |
sensitivity:critical |
rotation:90d |
Alpaca (/MooseQuest/alpaca/)
| Current name | Proposed name | Function | Required vendor scopes | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|---|
ALPACA_PAPER_API_KEY_ID |
ALP_PAPER_TRADING (key component) |
Paper trading API | Paper trading (key+secret pair) | consumer:raptor |
sensitivity:medium |
rotation:180d |
ALPACA_PAPER_API_SECRET_KEY |
ALP_PAPER_TRADING (secret component) |
Paired with above | Same | consumer:raptor |
sensitivity:medium |
rotation:180d |
ALPACA_LIVE_API_KEY_ID |
ALP_LIVE_TRADING (key component) |
Live trading API (real money) | Live trading (key+secret pair) | consumer:raptor |
sensitivity:critical |
rotation:60d |
ALPACA_LIVE_API_SECRET_KEY |
ALP_LIVE_TRADING (secret component) |
Paired with above | Same | consumer:raptor |
sensitivity:critical |
rotation:60d |
Alpaca key pairs use the
__KEY_ID/__SECRET_KEYcompanion pattern:ALP_PAPER_TRADING__KEY_IDandALP_PAPER_TRADING__SECRET_KEY.
Infisical (/MooseQuest/infisical/)
| Current name | Proposed name | Function | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|
INFISICAL_SERVICE_TOKEN |
INF_SVC_DISPATCHER |
Machine identity client secret — agent dispatcher reads bot key paths | consumer:agent |
sensitivity:high |
rotation:90d |
Slack (/MooseQuest/slack/)
| Current name | Proposed name | Function | Required vendor scopes | Consumer(s) | Sensitivity | Rotation |
|---|---|---|---|---|---|---|
BOT_USER_SLACK_TRADING_MASTER_API |
SL_BOT_NOTIFY |
Send Slack DM notifications to operator | chat:write, im:write | consumer:raptor, consumer:agent |
sensitivity:medium |
rotation:annual |
DreamHost / Oracle Dyn (legacy)
| Current name | Proposed name | Function | Consumer(s) | Sensitivity | Rotation | Notes |
|---|---|---|---|---|---|---|
DREAMHOST_API_KEY |
DH_DNS_MGMT |
DreamHost hosting/DNS management | consumer:operator |
sensitivity:medium |
rotation:90d |
Evaluate whether still needed |
DYN_PASSWORD |
DYN_DNS_UPDATE |
Oracle Dyn DNS update | consumer:operator |
sensitivity:medium |
rotation:180d |
Migration off Dyn recommended (#402 follow-up) |
Section 5 — Provisioning template
When a new token needs to be created, walk these steps in order. Steps 1-4 are one-time per token; step 5 is done whenever a new consumer is wired up.
Step 1 — Assign a name and path
- Choose the vendor code from the table in Section 1.
- Choose a function keyword that matches the permission being granted (match an existing
function:tag if possible; if new, propose to this doc as a PR first). - Add a scope suffix if the vendor issues per-zone or per-app tokens.
- Full proposed name:
<VENDOR>_<FUNCTION>[_<SCOPE>] - Path:
/MooseQuest/<vendor-lowercase>/— verify the folder exists in Infisical before writing. If not, create viaPOST /api/v1/folders(perfeedback_vault_folder_must_exist.md).
Step 2 — Provision the token at the vendor
| Vendor | Dashboard URL | Notes |
|---|---|---|
| Cloudflare | https://dash.cloudflare.com/profile/api-tokens |
Use "Create Custom Token"; set Zone Resources to the specific zone |
| Cloudflare Access | CF Zero Trust → Access → Service Auth → Service Tokens | Duration = 1 year; apply Service Auth policy to the target Access app |
| Heroku | https://dashboard.heroku.com/account → "API Key" |
No scope isolation available; treat as full account |
| Postmark | https://account.postmarkapp.com/servers → Server → API Tokens |
Server token is per-server; account token is global |
| AWS | AWS Console → IAM → Users → claude-infisical-bootstrap → Security credentials |
Rotate the existing key; do not create additional users without approval |
| Anthropic | https://console.anthropic.com/settings/keys |
Account-level; no scope isolation |
| GitHub (PAT) | https://github.com/settings/tokens |
Fine-grained PATs preferred; limit to MooseQuest org and minimum permissions |
| GitHub (App) | https://github.com/apps/<appname> → Settings → Private Keys |
Generate new key on existing App; do NOT create a new App without architectural review |
| Stripe | https://dashboard.stripe.com/apikeys → "Create restricted key" |
Specify only the write permissions needed; never use secret key directly |
| Alpaca | https://app.alpaca.markets → Account → API → Create New Key |
Separate keys for paper vs. live environments |
| Infisical | vault.raxx.app → Access Control → Identities → Create Machine Identity |
Use Universal Auth; assign read-only role scoped to the paths needed |
Step 3 — Store in vault
# 1. Verify folder exists (or create it)
curl -s -H "Authorization: Bearer $INFISICAL_TOKEN" \
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
"https://vault.raxx.app/api/v1/folders?workspaceId=$PROJECT_ID&environment=prod&path=/MooseQuest/<vendor>"
# 2. Create the secret
# (Use Infisical UI for first provisioning; use rotation pipeline for subsequent rotations)
# 3. Create companion secrets (always):
# <NAME>__EXPIRES_AT = ISO-8601 UTC expiry or "non-expiring"
# <NAME>__SCOPES = space-separated vendor scope strings
Step 4 — Apply tags
In Infisical UI → select secret → Tags → add the four required tags:
function:<tag>
sensitivity:<level>
consumer:<primary-consumer>
rotation:<cadence>
If the secret is sensitivity:critical, add a comment in the provisioning PR describing who was notified.
Step 5 — Register the consumer reference
For each consumer type, add the new key name to the canonical reference location:
| Consumer | Reference location |
|---|---|
| CI (GitHub Actions) | .github/workflows/*.yml — add to the env: block or secrets mapping. Document in PR description. |
| Terraform | terraform/<module>/variables.tf + comment header in terraform.tfvars.example naming the vault path |
| Raptor (backend_v2) | backend_v2/api/vault.py — add to the bootstrap list + os.environ.get call site |
| Console | console/vault.py or equivalent config loader |
| Agent scripts | scripts/agents/*.py — add to the env-var resolution list |
| Operator runbooks | docs/ops/runbooks/rotation/INDEX.md — add a row to the rotation matrix |
Step 5 is required. No orphan tokens — every token in vault must have at least one documented consumer reference.
Step 6 — Add to rotation matrix
Open docs/ops/runbooks/rotation/INDEX.md and add a row:
| <NEW_NAME> | <Vendor> | operator-assisted | <sop-link> | <cadence> | TBD | TBD |
If no vendor-specific SOP exists yet, create one under docs/ops/runbooks/rotation/ before filing the provisioning PR.
Section 6 — Migration plan
Phase 0 — Design complete (this document)
No token is renamed or moved. Existing consumers are unaffected. The taxonomy doc is the reference for all net-new tokens provisioned from this point forward.
Phase 1 — New tokens follow the taxonomy (immediate, no migration needed)
Starting with CF_WAF_EDIT_RAXX_APP (#747) — the first token to be provisioned after this doc exists — all new tokens use the new naming and tagging convention.
Phase 2 — Parallel rename (one PR per vendor)
For each vendor:
- Add the new-taxonomy name as a new secret in vault pointing to the same value (do not delete the old name yet).
- Update all consumer references in one PR: rename the env var in CI workflows, Terraform, vault.py, and runbooks.
- Smoke-test in staging: verify the consumer works with the new name.
- Merge the PR.
- After the PR is merged and the next deploy succeeds, delete the old-taxonomy name from vault.
Vendor order (suggested — highest impact first):
- Cloudflare — most tokens, most ambiguity, most consumers (CI + Terraform + agent). File as one PR per CF function (DNS, WAF, Pages, Access).
- Heroku — deduplicate the two platform-key entries.
- AWS — rename to companion-secret pattern.
- Postmark — two tokens, low consumer count.
- Alpaca — rename to companion-secret pattern.
- Anthropic, GitHub, Slack, Stripe — single tokens, low friction.
- Infisical, bot paths — last, because mint_github_token.py must be updated simultaneously.
Phase 3 — Rotation matrix updated
After all vendor renames complete, update docs/ops/runbooks/rotation/INDEX.md to use new names. Old entries marked [archived].
Rotation safety protocol during parallel rename
During the parallel rename window (old + new names both exist):
- Rotation pipeline rotates OLD name only until the consumer PR lands.
- After consumer PR merges, rotation pipeline switches to NEW name.
- Old name is revoked at vendor dashboard the same day it is deleted from vault.
- Never let both names be valid simultaneously for longer than one deploy cycle.
Section 7 — Open questions for Kristerpher
Q1 — Are CLOUDFLARE_RAXX_AUTOMATION_API_TOKEN and CLOUDFLARE_PAGES_DEPLOY_TOKEN the same physical token?
The rotation matrix and the memory reference (reference_cloudflare_tokens.md) name both tokens. If they are the same token stored under two names, one name should be deprecated immediately after the canonical new name is provisioned. If they are distinct (different scopes), the function mapping needs to reflect that. The taxonomy proposes CF_PAGES_DEPLOY for the Pages-deploy function — confirm whether the automation token also has Pages scope or whether it is truly distinct.
Q2 — Rotation cadence enforcement: Infisical expiration or documented only?
Infisical supports setting an expiration date on a secret (it will be flagged as expired in the UI and rotation alerts fire). The taxonomy documents cadences, but does not mandate using Infisical's expiration field. Recommendation: use the __EXPIRES_AT companion secret for auditable tracking and let the console rotation UI read it (#253, #254). Infisical's native expiration is a belt-and-suspenders option. Kristerpher to decide whether to enable native expiration (no implementation work — just a UI toggle per secret) or rely solely on the rotation pipeline's scheduling.
Q3 — DNS edit: one token for both raxx.app and getraxx.com, or two?
Option A — one token with both zones in scope: simpler rotation, wider blast radius if leaked.
Option B — two tokens, one per zone: least-privilege, higher provisioning overhead.
The taxonomy proposes Option B (CF_DNS_EDIT_RAXX_APP and CF_DNS_EDIT_GETRAXX_COM) as the default. If Kristerpher prefers Option A for operational simplicity, the scope suffix becomes CF_DNS_EDIT_ACCOUNT (or just CF_DNS_EDIT) and the __SCOPES companion documents both zones. Either is valid — but the decision should be made before any migration PR is filed.
Q4 — Are there any tokens not in this index?
This index is built from the rotation matrix (INDEX.md), the vault README, issues #402/#596/#747, and the memory notes. If any tokens exist in vault that are not listed here — particularly any remaining CLOUDFLAREROLLED, legacy bootstrap tokens, or per-agent access tokens — they should be audited against this taxonomy before the Phase 2 migration starts. Kristerpher's live read of vault contents (using the CF Access service token and Infisical list API) would surface any gaps.
Q5 — Should ALP_LIVE_TRADING ever exist in vault at all pre-launch?
The live trading keys control real money. Before Raxx has live-trading users, these keys belong to the operator's personal Alpaca account. The taxonomy includes them for completeness, but the provisioning decision — whether to store live keys in vault now or defer until first live user — is Kristerpher's call. Recommendation: store in vault under sensitivity:critical with consumer:raptor and rotation:60d even pre-launch, so the rotation pipeline can enforce cadence on the keys that matter most.
Appendix A — Vendor code reference
| Code | Vendor |
|---|---|
CF |
Cloudflare |
HK |
Heroku |
PM |
Postmark |
AWS |
Amazon Web Services |
ANT |
Anthropic |
GH |
GitHub |
STR |
Stripe |
ALP |
Alpaca |
INF |
Infisical |
SL |
Slack |
DH |
DreamHost |
DYN |
Oracle Dyn |
Appendix B — Checklist for net-new token provisioning PR
[ ] Name follows <VENDOR>_<FUNCTION>[_<SCOPE>] pattern
[ ] Path folder verified to exist (or created via POST /api/v1/folders first)
[ ] Secret created in correct Infisical environment(s) per Section 3 table
[ ] __EXPIRES_AT companion secret set
[ ] __SCOPES companion secret set (exact vendor permission strings)
[ ] All four tag dimensions applied (function, sensitivity, consumer, rotation)
[ ] If sensitivity:critical — ops review noted in PR description
[ ] Consumer reference added (CI workflow / Terraform / vault.py / runbook)
[ ] Row added to docs/ops/runbooks/rotation/INDEX.md
[ ] Vendor-specific SOP exists at docs/ops/runbooks/rotation/<vendor>.md