Skip to main content

Secrets management

Where secrets live​

SecretDevProdShared with developers via
Backend .env (database, salt, Stripe, Deposco, ACS, Graph, Google, R2, Maps, OpenAI, tunnel token)/opt/emaw/emaw-django-app/.env on the dev VMPortainer stack environmentPassword manager (never git, chat or email)
Production username saltn/a (EMAW_SALT in .env)Key Vault emaw-app, secret emaw-app-saltnot shared; the VM's identity reads it
Cloudflare API token + account idGitHub environment develop in each frontend repoGitHub environment productionCloudflare account owner
Website VITE_* (public)GitHub repository variablesPages production variablesnot secret
Web app / CRM / mobile VITE_* and EXPO_PUBLIC_* (public)committed env filescommitted env filesnot secret
Stripe dashboard loginStripe team invite
Apple / Google developer accounts, EASStore console roles; Expo organisation
SSH keysyour own private key; public key installed by Troy or Mattsame + IP whitelist

Rule of thumb: if it is a VITE_ or EXPO_PUBLIC_ value, it ships to browsers and phones and is not a secret; if it is anything else, it belongs only in a backend .env, Portainer, Key Vault or a GitHub secret.

Getting a secret into an environment​

EnvironmentHow
Your laptop (backend)copy the .env from the password manager into the repo root; it is gitignored
Dev VMedit /opt/emaw/emaw-django-app/.env over SSH, then docker compose up -d
ProductionPortainer → stack → environment variables, then redeploy. Also add the name to docker-compose.yml x-app-env or the container will not see it
GitHub Actionsrepo → Settings → Environments → develop / production → environment secrets
Cloudflare Pages (website)project → Settings → Environment variables → Production

Rotation notes​

SecretEffect of rotatingSteps
SECRET_KEYInvalidates all JWTs (everyone signs in again) and GOOGLE_TOKEN_KEY's default, so staff must reconnect Google for MeetSet an explicit GOOGLE_TOKEN_KEY first if you want to keep Meet connections
EMAW_SALTNobody can log in: usernames are salted hashesDo not rotate; it is effectively permanent
STRIPE_WEBHOOK_SECRETWebhooks fail signature checks until updatedRoll the secret in Stripe, update .env, redeploy quickly
MS_CLIENT_SECRETExpires on a schedule in Entra; emails stop silentlyCreate a new secret before expiry, update both environments
TUNNEL_TOKENThe backend disappears from the internet until the new token runsRotate in Zero Trust, update .env on the VM and in Portainer
R2_* tokenUploads return 503Create a new token, update .env
Cloudflare API token (GitHub)Deploys failCreate a new token, update the environment secrets in every frontend repo
AZURE_BLOB_KEYMedia uploads and reads of generated audio failRegenerate in the storage account, update both environments

If a secret leaks​

  1. Rotate it immediately using the table above.
  2. Tell Troy or Matt directly; do not post the value or details in a shared channel.
  3. If it was committed, rewrite history only after rotating; rotation is what fixes the exposure.