Status: Implemented (PR #468) Created: 2026-05-09 UTC Related issue: #468 — env-aware token resolution Related docs: console-env-switcher.md, console-dashboard.md
The console vault service (console/app/services/vault.py) previously resolved
all credentials to a flat vendor path regardless of which environment the
operator was targeting. When the console was targeting staging, a rotation
handler would still read (and write) the prod Heroku API key because both envs
shared the same Infisical path.
This created operational risk: an operator believing they were rotating a staging credential was silently operating on the production secret.
Selected: Option B — separate sub-folders per environment.
/MooseQuest/heroku/prod/HEROKU_API_KEY
/MooseQuest/heroku/staging/HEROKU_API_KEY
/MooseQuest/heroku/prod/HEROKU_PLATFORM_API_TOKEN
/MooseQuest/heroku/staging/HEROKU_PLATFORM_API_TOKEN
Why Option B over Option A (separate key names):
Option A (HEROKU_API_KEY_PROD, HEROKU_API_KEY_STAGING) pollutes the
credential namespace and requires the operator to remember naming conventions.
Option B (separate folders) maps cleanly onto Infisical's folder model,
keeps each environment's secrets co-located, and requires no credential rename
for callers — the same HEROKU_API_KEY name is used in both envs; only the
path differs.
These credentials are different per environment because they correspond to distinct accounts, apps, or modes:
| Credential | Reason |
|---|---|
HEROKU_API_KEY |
Prod and staging are separate Heroku OAuth authorizations. |
HEROKU_PLATFORM_API_TOKEN |
Same — separate platform tokens per Heroku account context. |
ALPACA_PAPER_API_KEY_ID |
Staging should always target paper; different paper account from prod. |
ALPACA_PAPER_API_SECRET_KEY |
As above. |
ALPACA_LIVE_API_KEY_ID |
Live account keys. Staging must never hold live keys. |
ALPACA_LIVE_API_SECRET_KEY |
As above. |
STRIPE_RESTRICTED_KEY |
Staging uses a test-mode key; prod uses live-mode. |
POSTMARK_SERVER_TOKEN |
Staging uses a dedicated test server to avoid real email delivery. |
These credentials have a single account or value that applies equally to both prod and staging operations:
| Credential | Reason |
|---|---|
CF_PAGES_DEPLOY, CF_ACCESS_MGMT, CF_PAGES_READ |
Single Cloudflare account; tokens are scoped by zone/resource, not by deployment env. |
CF_ACCESS_SVC_CONSOLE, CF_ACCESS_SVC_VAULT |
Same CF Access application across deployments. |
CLOUDFLARE_RAXX_AUTOMATION_API_TOKEN (legacy) |
As above. |
ANTHROPIC_API_KEY |
Single Anthropic account; usage metered at org level, not per env. |
GITHUB_API_READONLY_TOKEN |
Read-only GitHub PAT — same repo access from either env. |
GOOGLE_WORKSPACE_SA_KEY_JSON |
Single Google Workspace tenant; SA key is org-scoped. |
DREAMHOST_API_KEY |
Single DreamHost account. |
SLACK_BOT_TOKEN, BOT_USER_SLACK_TRADING_MASTER_API |
Single Slack workspace. |
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY |
Single IAM user (per project_aws_iam_state.md). |
INFISICAL_SERVICE_TOKEN |
Vault itself is the credential store — single service token. |
vault._secret_path_for(name, env=None) determines the Infisical secretPath.
When FLAG_CONSOLE_VAULT_ENV_AWARE is on:
env-specific cred → /MooseQuest/<vendor>/<active_env>/
shared cred → /MooseQuest/<vendor>/
When the flag is off (default):
all creds → /MooseQuest/<vendor>/ (legacy flat path)
active_env resolution order:
1. Caller-supplied env argument (explicit override).
2. flask.g.selected_env if inside a Flask request context.
3. "prod" fallback (background threads, CLI, tests without a request).
rotation_mode_a._propagate_to_heroku() pushes a newly rotated credential to
the Heroku app(s) that consume it. When FLAG_CONSOLE_VAULT_ENV_AWARE is on,
propagation is scoped to the active environment:
active_env="prod" — only raxx-api-prod and raxx-console-prod are updated.active_env="staging" — only raxx-api-staging and raxx-console-staging are updated.When the flag is off the original behavior (all affected sites) is preserved.
Before enabling FLAG_CONSOLE_VAULT_ENV_AWARE:
POST /api/v1/folders
{
"workspaceId": "<INFISICAL_PROJECT_ID>",
"environment": "prod",
"path": "/MooseQuest/heroku/prod"
}
Repeat for each: /MooseQuest/heroku/staging, /MooseQuest/alpaca/prod,
/MooseQuest/alpaca/staging, /MooseQuest/stripe/prod,
/MooseQuest/stripe/staging, /MooseQuest/postmark/prod,
/MooseQuest/postmark/staging.
Vault folder must exist before writing secrets (feedback_vault_folder_must_exist.md).
Copy or re-provision each env-specific credential into the new sub-folders.
The existing flat-path secrets (/MooseQuest/heroku/HEROKU_API_KEY) remain
in place until the flag is on and all env-specific paths are confirmed working.
Verify with a test read:
GET /api/v3/secrets/raw/HEROKU_API_KEY
?workspaceId=...&environment=prod&secretPath=/MooseQuest/heroku/prod/
Enable the flag on staging first (FLAG_CONSOLE_VAULT_ENV_AWARE=true on
raxx-console-staging). Verify staging rotations read from
/MooseQuest/heroku/staging/.
Promote to prod after 48h soak.
console_vault_env_aware — default off. B1 migration row in
console/migrations/versions/0018_promote_console_vault_env_aware.py.
Flip via Heroku:
heroku config:set FLAG_CONSOLE_VAULT_ENV_AWARE=true --app raxx-console-staging >/dev/null 2>&1