This is a product-neutral reference repository. It holds no production data, no Firebase credentials and no live project. Even so, the boundaries it draws are the real ones, and this file states them explicitly so that neither a human nor an automated scanner "fixes" the wrong side of the line.
The Firebase client configuration is public by design, and this repository commits it deliberately:
- API key (
FIREBASE_API_KEY) - App ID (
FIREBASE_APP_ID,FIREBASE_ADMIN_APP_ID) - Project ID (
FIREBASE_PROJECT_ID) - Messaging sender ID, auth domain, storage bucket
Every one of these values is compiled into the web bundle, the APK and the IPA that any user can download and unpack. A Firebase API key identifies a project; it is not a bearer credential and does not by itself grant read or write access to anything. Access is decided by Firebase Auth, Firestore security rules, Storage rules and App Check.
Moving these into a secret store buys no security and makes builds harder to
reproduce. Do not report committed Firebase client config as a leak. See
packages/app/lib/firebase_options.dart for the same explanation in-code.
Reference: https://firebase.google.com/docs/projects/api-keys
Everything on the trusted side of the boundary:
| Material | Why | Where it belongs |
|---|---|---|
Service-account JSON ("type": "service_account") |
Bypasses all security rules via the Admin SDK | Workload Identity Federation, or a CI secret |
Admin SDK private keys, any PEM PRIVATE KEY block |
Full project impersonation | Secret manager |
Firebase CI deploy tokens (FIREBASE_TOKEN) |
Long-lived, deploy-capable, hard to scope | Prefer OIDC/WIF; otherwise a CI secret |
| Web Push (VAPID) private keys | Lets an attacker send push as your app | Secret manager |
Android keystores (.jks, .keystore), key.properties |
Lets an attacker ship a signed impostor build | CI secret / managed signing |
Apple signing keys (.p8, .p12, .mobileprovision) |
Same | CI secret / managed signing |
.runtimeconfig.json |
Contains Functions runtime secrets | firebase functions:secrets |
.env files with real values |
Everything above, eventually | Secret manager |
These are excluded by .gitignore, and — because a .gitignore entry can be
bypassed with git add -f and is therefore not evidence — they are additionally
enforced by two committed checks that run on every CI build:
scripts/check-no-secrets.mjsscans every git-tracked file for credential-shaped content and credential-shaped filenames.scripts/check-bundle-secrets.mjsscans the built web bundles afterflutter build web, because a source-clean repo can still ship a key that a--dart-defineor a copied asset introduced at build time.
The admin console holds no key of any kind. Privilege comes from the admin
custom claim on the caller's Firebase ID token, and it is verified twice on the
server:
firestore.rulesreadsrequest.auth.token.adminfor privileged reads.- The callable Cloud Function re-checks
request.auth.token.admin === truebefore performing any Admin SDK write.
The client-side gate in packages/admin/lib/admin_app.dart only decides what to
render. Both server checks are covered by tests
(firestore-tests/rules.test.mjs, packages/functions/src/policy.test.ts),
including the case of a client presenting admin: "true" as a string.
Crucially, an admin cannot write the moderation fields directly, even with
the claim: firestore.rules forbids every client from writing reviewState,
reviewedBy and reviewedAt. The only path is the Cloud Function.
This repository never deploys. .github/workflows/deploy.yml is gated on manual
dispatch or a version tag, requires every CI gate to pass first, and its
firebase deploy step is disabled with if: ${{ false }}. No Firebase
credential is configured in this repository or in its GitHub environment.
This is a documentation and reference artifact with no users and no data. If you find a flaw in the patterns it teaches, open an issue on https://github.com/vibecodeqa/vibecodeqa/issues.