`--dart-define` Configures Flutter Builds but Does Not Hide Secrets
Compile-time values are useful for endpoints and feature flags, but values embedded in a client app can be extracted. Keep credentials on a trusted server.
A client binary cannot keep a long-lived secret
Flutter's --dart-define makes environment-specific values available at compile time. It is a practical way to set an API base URL, a non-sensitive feature flag, or a release label. It is not a vault. A value compiled into an iOS or Android application can be recovered through binary inspection, runtime instrumentation, or logs.
Use compile-time values for configuration that is safe for every user to learn:
flutter build apk --release --dart-define=APP_ENV=staging \
--dart-define=API_BASE_URL=https://staging.example.comDo not place private API keys, signing passwords, database credentials, or privileged service tokens in these flags.
Keep authority server-side
If a provider requires a secret key, make the app call your backend and have the backend enforce user authorization, rate limits, and provider access. A public client key may identify an application, but it cannot authorize a sensitive operation by itself.
Do not assume obfuscation or encrypted string constants solve this problem; the app must eventually use the value, which gives an attacker an observable path. Store signing credentials in the CI secret manager and inject them only into the signing step, never into Dart defines.
For non-secret config, log a safe environment label and add a release assertion that rejects staging endpoints in production builds. Review the compiled artifact or build metadata when changing environments. This catches accidental configuration drift while respecting the fact that client-side configuration is visible by design.
Classify configuration by authority
Create an inventory of values that enter each build and label them as public configuration, user-specific data, or privileged secret. Public configuration might include an API hostname, a non-sensitive analytics project identifier, or a feature toggle. A secret is a credential that grants authority: signing keys, service-account tokens, private API keys, or shared passwords. The fact that a value is difficult to find in a binary does not change its security category.
Ask what an attacker could do if the value were copied from an installed application. If it grants access to other users' data, incurs billable usage, or changes server state without user authorization, it does not belong in a mobile bundle. Move that capability behind a backend that authenticates the user and enforces authorization and rate limits. When the client needs a token, issue one with narrow scope and a short lifetime, and design revocation and abuse monitoring.
Prevent leakage through build plumbing
Build arguments and CI variables can be recorded in process listings, verbose logs, crash reports, or artifact metadata. Do not pass secrets through --dart-define and assume the compiler will hide them. Keep logs limited to a safe environment label and non-sensitive endpoint details. Audit generated configuration, source maps, and bundled assets for values that should not ship. A repository scanner can catch committed credentials, but it cannot make a credential embedded in an app safe.
Separate staging and production credentials and ensure staging accounts cannot access production records. Rotate a credential if it was ever shipped or committed, even if the app was distributed only internally. Deleting it from a later commit does not remove it from repository history or downloaded packages. Treat an exposed credential as compromised and use the provider's revocation process.
Add a release check that verifies the production artifact points to the intended service and scans for known forbidden secret patterns. A clean scan is useful but cannot prove that an unknown credential is absent. The architecture should remain secure even if every string in the client application becomes public.
Treat an internal or beta distribution channel as real distribution. Employees and contractors can inspect the binary, and a test token can remain valid after the test ends unless it is revoked. Use short-lived accounts and credentials, monitor their use, and rotate them at the end of an exercise. “Not publicly released” is not a security control; only the capability and lifetime of the credential determine its impact.
Review build logs, crash reports, source maps, and generated assets for accidental values as part of release readiness. A scanner can find known patterns, but architecture matters more than scanning. If a value would be damaging when copied from an installed app, redesign the flow so the app never receives that authority in a reusable form.