Safeguard
Application Security•

Where do secrets go in a Dart and Flutter app?

Dart's dart-define values are compiled into the app and hide nothing. Learn what belongs there, what to keep on a server, and what to rotate.

Safeguard Security Team
4 min read

In a Flutter app, real secrets do not belong in the client at all. Dart's --dart-define flag lets you inject values at build time, which keeps them out of Git, but the values are compiled into the app and can be recovered. Treat them as public configuration and keep sensitive credentials on a server.

What does dart-define actually do?

The Dart documentation on environment declarations explains that you can pass named values to the compiler and read them in code with bool.fromEnvironment, int.fromEnvironment and String.fromEnvironment. For Flutter, the tool takes --dart-define, with an example of flutter run --dart-define=DEBUG=true. The constructors are only guaranteed to work when invoked as const, because most compilers must evaluate their value at compile time.

That last point is the security point. A value read at compile time becomes part of the program. It is not fetched when the app runs, and it is not held in a protected store. Anyone who has the app binary can analyse it.

Why can't the app hide it?

Android's developer guidance says plainly that when an app is compiled with API keys in its source, an attacker can decompile it and find them. The OWASP Mobile Application Security Verification Standard (MASVS) organises mobile requirements into groups that include storage and cryptography, so use it as the checklist for what the app stores and how.

The same logic holds for an iOS build. The compile step does not change the fact that the value ships to every device.

A constructed example: a Flutter app embeds a payment processor secret key through --dart-define so that it can create charges directly. A user pulls the strings out of the app package and now holds a key that can issue refunds. The fix is architectural. The app calls your backend, the backend holds the secret and applies limits.

What can go in dart-define?

Public or low risk configuration: the base URL of your API, a feature flag, an environment name, a publishable key designed for client use, an app identifier. If the value would be fine in a public repository, it is fine here. The benefit of dart-define for those is separate builds for development, staging and production without editing code.

Where do real secrets live?

  • Third party secret keys and signing keys: on your backend, behind your own authentication.
  • User tokens issued at login: in the platform's protected storage, using a storage package that wraps the Android Keystore and the iOS Keychain. Check that package's own documentation for what it protects and what it does not.
  • Keys the app creates for local encryption: in the platform keystore so the key material is not exportable. Android documents that the Keystore keeps key material non exportable and can bind it to secure hardware.

How do you limit the damage of a leaked client key?

Even a publishable key should be restricted: by app identifier, by API and by quota where the provider supports it. Use separate keys per environment so you can revoke one without breaking another. Monitor usage and set budget alerts, and plan rotation: a release that reads a new value must reach users before you revoke the old one, so keep the old key alive for a defined overlap.

How to check or apply this

  1. List every String.fromEnvironment and --dart-define in the codebase and classify each value as public or secret.
  2. Remove secret values from the client and move the calls to a backend.
  3. Restrict remaining keys by app and API, and give each environment its own key.
  4. Store user tokens in platform protected storage, not in shared preferences.
  5. Scan the repository and CI logs for keys, because build commands with dart-define values often get echoed into logs.
  6. Write a rotation plan that accounts for app release delay.
  7. Track plugin versions, since storage and networking plugins are third party code in your app.

Quick check

Does dart-define encrypt my value?

No. It passes a value to the compiler. The value ends up in the compiled app.

Is it safe to commit a file of dart-define values?

Only for public configuration. Anything secret should not be in the client, and should not be in the repository either.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.