Restrict Android engine flags in release mode
Passing engine configuration flags to Android release binaries using Android `Intent` extras or `--use-application-binary` is no longer supported.
Summary
#
Previously, the Flutter Android embedding accepted engine configuration flags
(such as --dart-flags, --route, or tracing flags)
at launch time through Android Intent extras in all build modes.
This mechanism allowed developers and tools—including the Flutter CLI
when using --use-application-binary—to dynamically configure
running applications without modifying their source code or manifest.
To protect production applications against Intent-based spoofing
and parameter injection vulnerabilities,
Flutter Android release builds now ignore engine configuration flags
passed through Intent extras,
reading engine configuration strictly from the compiled AndroidManifest.xml
or from programmatic configuration prior to engine initialization.
This change impacts workflows in two ways:
-
Direct
Intentlaunches: Passing engine configuration flags throughIntentextras (such as throughadb shell am startor native Android code) is ignored by the embedding in release builds. -
Flutter CLI prebuilt binaries:
Because prebuilt binaries cannot have their manifests dynamically modified
after compilation, the Flutter CLI now produces a fatal error
if you pass engine configuration flags to a prebuilt release binary
with
flutter run --release --use-application-binary. This prevents flags from being silently ignored.
Standard release builds (where the CLI compiles the app with Gradle and injects flags into the manifest) and all debug and profile workflows continue to work without changes.
Context
#
The Flutter engine accepts configuration flags
(such as --dart-flags, --route, or --trace-startup)
to configure runtime behavior when running or driving an application.
Historically, the Flutter Android embedding accepted these flags at runtime
through Intent
extras.
Both developers (using adb shell am start or native code)
and the Flutter CLI (via adb) relied on Intent extras
to pass flags to running apps.
However, runtime Intent extras on Android can be spoofed or intercepted
by other applications on a user's device.
Allowing arbitrary engine flags in production builds presents
security vulnerabilities, such as dynamic parameter injection attacks
(for example, spoofing --aot-shared-library-name).
To harden production applications, Flutter establishes a build-mode-specific trust boundary:
-
Release mode:
Production application boundaries are treated as immutable.
The Android embedding ignores engine configuration flags passed
through
Intentextras and logs a warning. Release builds read configuration strictly from a signedAndroidManifest.xmlor from programmatic setup before engine startup. -
Debug and profile modes:
Dynamic
Intentflag support is intentionally maintained to preserve developer velocity, dynamic benchmarking, and local testing workflows.
For release builds of standard Gradle-based projects,
the Flutter CLI automatically injects command-line flags
into AndroidManifest.xml during compilation.
When you use a prebuilt release binary with --use-application-binary,
the CLI cannot modify the compiled manifest,
and the binary ignores runtime Intent flags.
To prevent tests or scripts from running with unnoticed configuration failures,
the CLI reports a fatal error.
Similarly, any automated scripts, test runners, or host applications
that directly construct Android Intents with configuration extras
will find those flags ignored when launching a release binary.
Description of change
#Android embedding behavior
#
The Flutter Android embedding enforces the following behavior
when receiving engine configuration flags through Android Intent extras
(such as from adb shell am start or native Intent.putExtra()
calls):
| Build mode | Intent extras behavior | Notes |
|---|---|---|
| Debug / Profile | Accepted and parsed | Flags apply dynamically at launch time. |
| Release | Ignored |
The embedding logs a warning and discards all engine configuration flags received from
Intent
extras.
|
In addition, the internal interface method
FlutterActivityAndFragmentDelegate.Host.getFlutterShellArgs is deprecated
in favor of getFlutterEngineFlags().
Flutter CLI behavior
#The Flutter CLI enforces the following behavior when running Flutter apps on Android:
| Build mode | Using --use-application-binary |
CLI behavior | Notes |
|---|---|---|---|
| Debug / Profile | Yes | Passes flags to binary through adb |
No rebuild required; flags apply at runtime. |
| Debug / Profile | No | Builds and passes flags through adb |
Standard development workflow. |
| Release | Yes | Fatal error if configuration flags are provided | Prebuilt release binaries cannot be dynamically configured. |
| Release | No | Injects flags into AndroidManifest.xml during compilation |
Standard release build workflow. |
Migration guide
#You are affected if:
-
You launch release builds using Android
Intentextras directly (such as withadb shell am startor custom launch intents in native code) to pass engine configuration flags or route arguments. -
Your CI/CD pipelines, automated scripts, or test runners pass flags
to prebuilt release binaries using
--use-application-binary. - You build Flutter Android applications using non-Gradle or hermetic build systems (such as Bazel) and configure release binaries with launch-time flags.
If your workflows are affected, use one of the following migration paths:
Switch testing and benchmarking to profile mode
#If your automated test pipelines, scripts, or benchmarks pass flags to release binaries dynamically at launch time:
-
Switch your test target to profile mode (
--profile). Profile mode mirrors release performance characteristics while retaining support for dynamic runtime flag configuration throughIntentextras and the Flutter CLI without recompilation.
Configure direct Intent launches and hermetic build systems
#
If you launch release builds using Android Intents directly
(for example, via adb shell am start or custom test runners),
or if you build Flutter Android apps with non-Gradle or hermetic build systems
(such as Bazel) that separate compilation from execution:
-
Do not rely on
Intentextras for release builds. Any engine flags passed inIntentextras to a release binary are silently ignored by the embedding at runtime. - Use profile mode for dynamic testing: For integration tests or performance benchmarks that require varying flags dynamically at launch time, compile and run a profile build instead of a release build.
-
Declare flags statically in
AndroidManifest.xml: If you must run a release binary with specific engine flags, statically declare those flags in yourAndroidManifest.xmlbefore compiling the release binary. For details, refer to Declare engine flags inAndroidManifest.xml.
Build release binaries with flags directly using the Flutter CLI
#If you must run tests against a release binary with the Flutter CLI:
-
Run
flutter buildorflutter runwith your configuration flags without--use-application-binary. The CLI automatically embeds the flags into the compiled manifest. - Alternatively, compile separate release binaries for each required configuration.
Configure engine flags programmatically in native host code
#
If you launch Flutter from native Android code
(such as in an add-to-app integration or custom native activity)
and previously passed engine flags through Intent extras:
-
Override
getFlutterEngineFlags(): If you subclassFlutterActivityorFlutterFragment, overridegetFlutterEngineFlags()instead of usingIntentextras or the deprecatedgetFlutterShellArgs():kotlinclass MyFlutterActivity : FlutterActivity() { override fun getFlutterEngineFlags(): List<String> { val flags = super.getFlutterEngineFlags().toMutableList() flags.add("--trace-startup") return flags } } -
Use
FlutterFragment.NewEngineFragmentBuilder: If you host aFlutterFragment, pass flags using the builder:kotlinval fragment = FlutterFragment.withNewEngine() .flutterEngineFlags(listOf("--trace-startup")) .build<FlutterFragment>() -
Pre-initialize and cache a
FlutterEngine: If you manage the engine lifecycle directly, supply engine arguments to theFlutterEngineconstructor:kotlinval args = arrayOf("--trace-startup", "--enable-impeller=true") val flutterEngine = FlutterEngine(context, args) FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
Declare engine flags in manifest
#
To configure engine flags statically in release builds,
add <meta-data> elements under the <application>
tag in
your android/app/src/main/AndroidManifest.xml file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="my_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<!-- Declare engine configuration flags statically -->
<meta-data
android:name="io.flutter.embedding.android.DartFlags"
android:value="--some-dart-flag" />
<meta-data
android:name="io.flutter.embedding.android.EnableDartProfiling"
android:value="false" />
<activity
...
</activity>
</application>
</manifest>
Timeline
#
Landed in version: TBD
In stable release: TBD
References
#Relevant issues:
Relevant pull requests:
Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-09-30. View source or report an issue.