Secrets Management
See also: Local Development · CI CD and Deploys · Feedback System
Where secrets live
Runtime secrets (API keys etc.) live in Firebase Secret Manager — not in .secret.local, .env, or system environment variables.
To add or update one (once per environment — dev and prod if needed):
firebase functions:secrets:set SECRET_NAME
# e.g.
firebase functions:secrets:set ORS_API_KEY --project stretched-dev
firebase functions:secrets:set ORS_API_KEY --project stretched-2c16a
Paste the value when prompted. Never write secret values into any file in the repo.
Current secrets
| Name | Used for |
|---|---|
ORS_API_KEY | OpenRouteService geocoding / routing — fallback provider for /geo/search, primary for matrix + directions |
GOOGLE_MAPS_API_KEY | Google Places API (New) Text Search — preferred /geo/search provider; when unset or failing, the function falls back to ORS |
RESEND_API_KEY | Resend email — free tier, 100/day; internal quota capped at 80/day via the email_quota/global Firestore doc (see Feedback System) |
GOOGLE_MAPS_API_KEYneeds the Places API (New) enabled on the Google Cloud project (billing required) and the key restricted to that API. Because it is bound to thegeofunction, the secret must exist in Secret Manager before the next Functions deploy.
How the emulator gets them
Nothing extra: bun run setup (tools/setup-env.ts) runs firebase login, and the authenticated Firebase CLI lets the Functions emulator pull secrets from Secret Manager automatically. See Local Development.
CI secrets
GitHub Actions uses separate deploy credentials (service-account JSON files) stored as GitHub repo secrets: FIREBASE_SERVICE_ACCOUNT_DEV and FIREBASE_SERVICE_ACCOUNT. These authorize deploys only — runtime secrets still come from Secret Manager. See CI CD and Deploys.
Hard rules (from the 2026-07-03 postmortem)
A .secret.local key was once committed and had to be rotated. The chain of events:
- An
echo "..." >> .gitignoreappended the.secret.localentry without a trailing newline, silently corrupting the last ignore rule. - The
.husky/pre-pushhook auto-formats and auto-commits everything (git add -A+chore: prettier auto-format) — which swept the now-untracked-no-longer-ignored secret file into a commit.
Therefore:
- Never keep secret values in files inside the repo. If a
.secret.localexists on disk it is legacy from before the Secret Manager migration — delete it. Its.gitignoreentry is a safety net only, and safety nets fail. - Never append to
.gitignorewith>>(or any blindecho). Open the file and add the entry on its own line. - Before pushing, check outgoing commits — remember the pre-push hook may have auto-committed files you didn't stage (see Tooling and Scripts).
If a secret does leak into git history: rotate the key immediately; scrubbing history is secondary.