Cloud Functions Secrets, Rotation, and Leak Response for Solo Flutter + Firebase Founders in 2026
Most solo Flutter + Firebase apps end up with a handful of real secrets on the backend: a payment provider key, a webhook signing key, an AI or email API token, maybe an OAuth client secret. They usually get added in a hurry, live in whatever place worked first, and never get rotated. This guide covers where each kind of configuration belongs in Cloud Functions for Firebase, how to bind secrets with least privilege, a rotation routine that doesn’t take your webhooks down, and what to do in the first hour after a leak. It pairs with our Firebase Auth and API key abuse hardening guide, which explains why the Firebase web API key in your app is not a secret and these values are.
Know what is actually a secret
Start by sorting configuration into three buckets, because each one has a different home:
- Public by design. The Firebase config in your Flutter app, including its API key, is meant to ship to clients. Firebase’s API keys documentation explains that these keys identify your project and that access is controlled by Security Rules and App Check, not by hiding the key.
- Non-sensitive backend settings. Regions, feature flags, public URLs, and batch sizes. These fit parameterized configuration or a
.envfile. - Real secrets. Anything that grants access or proves identity to another system: third-party API keys, webhook HMAC keys, service credentials. These belong in Cloud Secret Manager.
The Firebase guide Configure your environment is explicit that values in .env files should not be considered a secure way to store sensitive information, especially if those files are checked into source control. It also warns that the legacy functions.config() API is deprecated and that new deployments using it will fail after March 2027, so if an older project still reads keys from runtime config, migrating now saves you a forced fix later.
One rule for the Flutter side: no backend secret ever goes into the app bundle, --dart-define values, or Remote Config. Anything shipped to devices can be extracted. If the app needs a privileged action, call a function that holds the secret.
Bind secrets per function, not per project
Cloud Functions for Firebase integrates Secret Manager through defineSecret() (or the secrets option). Set a value with the CLI, then list the secret on each function that needs it:
// functions/src/index.ts
import { onRequest } from "firebase-functions/v2/https";
import { defineSecret } from "firebase-functions/params";
const webhookKey = defineSecret("PAYMENTS_WEBHOOK_KEY");
export const paymentsWebhook = onRequest(
{ secrets: [webhookKey] },
(req, res) => {
const key = webhookKey.value();
// verify signature with key ...
res.sendStatus(200);
}
);
The docs note that only functions that include a secret in their secrets option can read it; any other function gets an undefined value. That is useful, so lean into it:
- Bind narrowly. A scheduled cleanup job does not need your payment key. Fewer bindings means fewer places a bug or a verbose log line can leak it.
- Give sensitive functions their own identity. Google’s function identity guidance recommends a dedicated service account per function with only the roles it needs, instead of a shared default account.
- Grant access on the secret, not the project. Secret Manager’s access control page describes granting the Secret Accessor role at the level of an individual secret.
- Keep staging and production separate. Use different Firebase projects with different secret values, so a leaked staging key can’t touch real customers. Our App Check and staging guide covers the two-project setup.
Two practical traps we hit in our own pipelines. First, secrets are read when an instance cold-starts, so read them inside the handler or an onInit() callback, never at module top level during deploy. Second, the stored value is the exact bytes you provided. If you set a secret with --data-file from a file that ends in a newline, the newline is part of the key. Every system that signs or verifies with that key must use the identical bytes, or HMAC checks fail with no obvious cause.
A rotation routine that doesn’t break webhooks
Changing a secret is easy. Changing it while two systems depend on it is where outages come from. The Firebase docs list the rule that drives everything: whenever you set a new value, you must redeploy every function that references that secret for it to pick up the latest version. For a shared key such as a webhook HMAC secret, use an overlap window:
- Accept both values. Deploy verification code that checks the current key and, if that fails, a second
..._PREVIOUSsecret. Keep this logic small and well tested. - Add the new version. Run
firebase functions:secrets:set NAME(or--data-file), and copy the old value into the previous slot. - Redeploy every function that binds the secret. Check logs for verification failures.
- Switch the signer (the payment provider, your publisher script, the partner) to the new value.
- Retire the old value once traffic signed with it stops: clear the previous slot and redeploy.
Two things to know about versions. The Firebase CLI tries to clean up unused secret versions after a deploy, so don’t rely on an old version staying available; keep the overlap in your own code. And the CLI gives you functions:secrets:get to list versions and functions:secrets:prune to remove secrets no function references.
How often? For a solo team, rotate on a calendar (every 90 days is a reasonable default for webhook and API keys), whenever someone with access leaves, and immediately after any suspected exposure. Secret Manager’s rotation schedules don’t change values for you. They publish a SECRET_ROTATE message to Pub/Sub on schedule, and you decide what happens next. Even if you rotate by hand, a scheduled notification is a dependable reminder.
Leak response: the first hour
Leaks for small teams usually come from a committed .env file, a pasted log, a screenshot, or a CI job that echoes variables. Turn on GitHub secret scanning and push protection for your repositories so many of these are caught before or soon after a push. When something does leak:
- Revoke at the provider first. Rotating in Secret Manager does nothing if the old key still works at Stripe, your email service, or the AI API.
- Create the new version and redeploy the bound functions, using the overlap routine if a partner signs with the key.
- Disable the old Secret Manager version, then destroy it once you are sure nothing needs it. Google documents both steps in disable a secret version and destroy a secret version.
- Clean the exposure. Remove the value from the repo history, CI logs, and any tickets or chats. Treat it as burned regardless.
- Check the damage. Review provider usage and billing, and Secret Manager’s audit logging for unexpected access. Our cost alerts and budgets guide helps you notice abuse quickly.
Cost and housekeeping
Secret Manager is a paid service with a free tier. According to the Firebase docs, it currently allows six active secret versions and 10,000 access operations per month at no cost, and function instances read their bound secrets on every cold start. If you have many related values for one provider, defineJsonSecret() stores them as a single JSON secret, which keeps the count down. Check current numbers on Secret Manager pricing.
Quarterly secrets review (20 minutes)
- List secrets and versions; prune anything no function binds.
- Confirm each secret is bound only to the functions that use it.
- Confirm sensitive functions run under their own service account.
- Check rotation dates; rotate anything older than your policy.
- Search the repo and CI config for
functions.config(), committed.envvalues, and echoed variables. - Verify staging and production use different values.
Put the review on the same calendar as your Firestore rules review so both happen together.
Key takeaways
- The Firebase API key in your app is public; backend keys and signing secrets are not, and they belong in Secret Manager.
- Bind each secret only to the functions that need it, under a narrow service account.
- New secret values need a redeploy, so rotate shared keys with an overlap window.
- After a leak, revoke at the provider first, then rotate, disable, clean up, and review usage.
- Migrate off
functions.config()before deployments start failing in March 2027.
Sources
- Firebase: Configure your environment (secrets, params, runtime config migration)
- Google Cloud: Secret Manager best practices
- Google Cloud: Secret Manager rotation schedules
- Google Cloud: Function identity
- Firebase: Learn about using and managing API keys
- GitHub Docs: Secret scanning
More from gspteck: Remote Config kill switches and staged rollouts.