Raxx · internal docs

internal · gated ↑ index

Console Env Isolation — Vault Path Layout

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


1. Problem

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.


2. Design Choice: Option B (Separate Vault Folders)

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.


3. Credential Classification

3.1 Env-specific credentials (Option B path applies)

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.

3.2 Shared credentials (flat vendor path — no env sub-folder)

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.

4. Vault Path Resolution

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).


5. Heroku Propagation Scoping

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:

When the flag is off the original behavior (all affected sites) is preserved.


6. Operator Action — Provisioning Env Sub-Folders

Before enabling FLAG_CONSOLE_VAULT_ENV_AWARE:

  1. Create the env sub-folders in Infisical for each vendor that has env-specific credentials. Using the Infisical API (requires an authenticated token):

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).

  1. 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.

  2. Verify with a test read:

GET /api/v3/secrets/raw/HEROKU_API_KEY ?workspaceId=...&environment=prod&secretPath=/MooseQuest/heroku/prod/

  1. Enable the flag on staging first (FLAG_CONSOLE_VAULT_ENV_AWARE=true on raxx-console-staging). Verify staging rotations read from /MooseQuest/heroku/staging/.

  2. Promote to prod after 48h soak.


7. Flag

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