Configuration weakness / insecure default (feature flag)

HIGH
flutter/flutter
Commit: 05e0ae02600d
Affected: <= v1.16.3
2026-04-08 17:02 UTC

Description

The Android engine loader (FlutterLoader) reads boolean shell flag metadata from the Android manifest using applicationMetaData.getBoolean(metadataKey, true). If the metadata key is missing or the value is unparsable, the flag defaults to true, enabling potentially risky engine features by default. The commit changes the default to false (applicationMetaData.getBoolean(metadataKey, false)), ensuring flags are only enabled when explicitly specified. This hardens configuration by preventing accidental activation of sensitive or debug features (e.g., Vulkan validation, debugging-related flags) due to malformed or absent manifest metadata. The change is accompanied by tests validating behavior for missing or malformed values.

Proof of Concept

Proof-of-concept (pre-fix behavior vs. post-fix behavior): 1) Manifest without the flag (no metadata entry): AndroidManifest.xml -------------------------------- <manifest package="com.example.app" ...> <application android:name=".MyApp"> </application> </manifest> Pre-fix (engine before patch): The Flutter engine defaults missing/unparsable boolean metadata to true. The shell will be started with the flag enabled by default. Expected observable effect: Vulkan validation or other feature flags are effectively enabled even though the manifest did not request them. You may observe this in logs or by inspecting the started shell arguments (e.g., presence of --enable-vulkan-validation). Post-fix (engine after patch): With the new default, a missing metadata entry results in false (disabled). The flag will not be added to the shell arguments unless explicitly enabled in the manifest. Observe that the shell arguments no longer contain --enable-vulkan-validation when no metadata entry is present. 2) Malformed value scenario (pre-fix): AndroidManifest.xml -------------------------------- <meta-data android:name="io.flutter.embedding.android.EnableVulkanValidation" android:value="Fasle" /> Pre-fix behavior: getBoolean(metadataKey, true) would treat the unparsable value as enabling the flag (defaulting to true in the face of parsing issues). The flag would be effectively enabled despite the invalid value. Post-fix behavior: getBoolean(metadataKey, false) treats unparsable values as false, so the flag remains disabled unless explicitly set to a valid true value. 3) Malformed value scenario (post-fix verification): - Build with the patched engine (commit 05e0ae0260...). - Use the same malformed manifest entry as above. - Verify that the flag does not get enabled and is not present in the startup shell arguments. Reproduction notes: - Affected flags include those controlled via manifest metadata (e.g., EnableVulkanValidation, EnableSoftwareRendering, etc.). The precise behavior may vary by flag, but the root cause is the insecure default true on missing/malformed values. - The fix is visible in the code change from getBoolean(metadataKey, true) to getBoolean(metadataKey, false) and corresponding test updates.

Commit Details

Author: Meylis

Date: 2026-04-08 15:53 UTC

Message:

Fix Android engine flags defaulting to true for malformed values (#184631) ## Summary - `FlutterLoader` parses Android manifest metadata for engine shell flags, but `getBoolean(metadataKey, true)` defaults to `true` when the value is missing or unparseable — accidentally enabling flags that should be off - Change the default from `true` to `false`, matching the pre-#182522 per-flag behavior where flags were disabled unless explicitly enabled ## Changes - `engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java`: Changed `applicationMetaData.getBoolean(metadataKey, true)` to `getBoolean(metadataKey, false)` Fixes flutter/flutter#184581 --------- Co-authored-by: Camille Simon <43054281+camsim99@users.noreply.github.com> Co-authored-by: Camille Simon <camillesimon@google.com>

Triage Assessment

Vulnerability Type: Configuration weakness (insecure default / preference)

Confidence: HIGH

Reasoning:

The commit changes default behavior for Android engine flags from true to false when metadata is missing or unparseable. This prevents accidentally enabling potentially sensitive or risky features (e.g., Vulkan validation, debugging/diagnostic flags) by default, reducing the attack surface. The change, along with tests for malformed/missing values, demonstrates a hardening against unsafe defaults and unintended information leakage or behavior.

Verification Assessment

Vulnerability Type: Configuration weakness / insecure default (feature flag)

Confidence: HIGH

Affected Versions: <= v1.16.3

Code Diff

diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index e35cb527d1900..4d68370f0cdc1 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -396,8 +396,9 @@ void ensureInitializationComplete( } // Check if a boolean value is specified and if so, use it to determine if the - // flags should be added. If not, assume the flag is meant to be added. - if (applicationMetaData.getBoolean(metadataKey, true)) { + // flags should be added. If the value is missing or unparseable, default to + // false (disabled) to ensure flags are only enabled when explicitly requested. + if (applicationMetaData.getBoolean(metadataKey, false)) { shellArgs.add(arg); } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java index db8f1349ac5e2..b274efc941f43 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java @@ -757,7 +757,7 @@ public void itSetsAotSharedLibraryNameAsExpectedIfSymlinkIsNotSafe() throws IOEx public void itSetsEnableSoftwareRenderingFromMetadata() { testFlagFromMetadataPresent( "io.flutter.embedding.android.EnableSoftwareRendering", - null, + true, "--enable-software-rendering"); } @@ -784,7 +784,7 @@ public void getSofwareRenderingEnabledViaManifest_returnsExpectedValueWhenSetVia public void itSetsSkiaDeterministicRenderingFromMetadata() { testFlagFromMetadataPresent( "io.flutter.embedding.android.SkiaDeterministicRendering", - null, + true, "--skia-deterministic-rendering"); } @@ -914,7 +914,7 @@ public void itSetsIsolateSnapshotDataFromMetadata() { @Test public void itSetsUseTestFontsFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.UseTestFonts", null, "--use-test-fonts"); + "io.flutter.embedding.android.UseTestFonts", true, "--use-test-fonts"); } @Test @@ -929,7 +929,7 @@ public void itSetsVmServicePortFromMetadata() { @Test public void itSetsEnableVulkanValidationFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableVulkanValidation", null, "--enable-vulkan-validation"); + "io.flutter.embedding.android.EnableVulkanValidation", true, "--enable-vulkan-validation"); } @Test @@ -944,64 +944,64 @@ public void itSetsLeakVMFromMetadata() { @Test public void itSetsTraceStartupFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.TraceStartup", null, "--trace-startup"); + "io.flutter.embedding.android.TraceStartup", true, "--trace-startup"); } @Test public void itSetsStartPausedFromMetadata() { - testFlagFromMetadataPresent("io.flutter.embedding.android.StartPaused", null, "--start-paused"); + testFlagFromMetadataPresent("io.flutter.embedding.android.StartPaused", true, "--start-paused"); } @Test public void itSetsDisableServiceAuthCodesFromMetadata() { testFlagFromMetadataPresent( "io.flutter.embedding.android.DisableServiceAuthCodes", - null, + true, "--disable-service-auth-codes"); } @Test public void itSetsEndlessTraceBufferFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.EndlessTraceBuffer", null, "--endless-trace-buffer"); + "io.flutter.embedding.android.EndlessTraceBuffer", true, "--endless-trace-buffer"); } @Test public void itSetsEnableDartProfilingFromMetadata() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableDartProfiling", null, "--enable-dart-profiling"); + "io.flutter.embedding.android.EnableDartProfiling", true, "--enable-dart-profiling"); // Test release mode. testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableDartProfiling", null, "--enable-dart-profiling"); + "io.flutter.embedding.android.EnableDartProfiling", true, "--enable-dart-profiling"); } @Test public void itSetsProfileStartupFromMetadata() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.ProfileStartup", null, "--profile-startup"); + "io.flutter.embedding.android.ProfileStartup", true, "--profile-startup"); // Test release mode. testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.ProfileStartup", null, "--profile-startup"); + "io.flutter.embedding.android.ProfileStartup", true, "--profile-startup"); } @Test public void itSetsMergedPlatformUiThread() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.MergedPlatformUIThread", null, "--merged-platform-ui-thread"); + "io.flutter.embedding.android.MergedPlatformUIThread", true, "--merged-platform-ui-thread"); // Test release mode. testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.MergedPlatformUIThread", null, "--merged-platform-ui-thread"); + "io.flutter.embedding.android.MergedPlatformUIThread", true, "--merged-platform-ui-thread"); } @Test public void itSetsTraceSkiaFromMetadata() { - testFlagFromMetadataPresent("io.flutter.embedding.android.TraceSkia", null, "--trace-skia"); + testFlagFromMetadataPresent("io.flutter.embedding.android.TraceSkia", true, "--trace-skia"); } @Test @@ -1016,7 +1016,7 @@ public void itSetsTraceSkiaAllowlistFromMetadata() { @Test public void itSetsTraceSystraceFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.TraceSystrace", null, "--trace-systrace"); + "io.flutter.embedding.android.TraceSystrace", true, "--trace-systrace"); } @Test @@ -1031,27 +1031,27 @@ public void itSetsTraceToFileFromMetadata() { @Test public void itSetsProfileMicrotasksFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.ProfileMicrotasks", null, "--profile-microtasks"); + "io.flutter.embedding.android.ProfileMicrotasks", true, "--profile-microtasks"); } @Test public void itSetsDumpSkpOnShaderCompilationFromMetadata() { testFlagFromMetadataPresent( "io.flutter.embedding.android.DumpSkpOnShaderCompilation", - null, + true, "--dump-skp-on-shader-compilation"); } @Test public void itSetsPurgePersistentCacheFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.PurgePersistentCache", null, "--purge-persistent-cache"); + "io.flutter.embedding.android.PurgePersistentCache", true, "--purge-persistent-cache"); } @Test public void itSetsVerboseLoggingFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.VerboseLogging", null, "--verbose-logging"); + "io.flutter.embedding.android.VerboseLogging", true, "--verbose-logging"); } @Test @@ -1112,6 +1112,19 @@ public void itSetsEnableVulkanGPUTracingFromMetadata() { "io.flutter.embedding.android.EnableVulkanGPUTracing", true, "--enable-vulkan-gpu-tracing"); } + @Test + public void itDoesNotSetBooleanFlagWithMalformedOrMissingValue() { + // A key present with a non-boolean string value should NOT enable the flag. + testFlagFromMetadataNotPresent( + "io.flutter.embedding.android.EnableVulkanValidation", + "Fasle", + "--enable-vulkan-validation"); + + // A key present with a null value should NOT enable the flag. + testFlagFromMetadataNotPresent( + "io.flutter.embedding.android.EnableVulkanValidation", null, "--enable-vulkan-validation"); + } + @Test public void itDoesNotSetTestFlagFromMetadata() { testFlagFromMetadataNotPresent("io.flutter.embedding.android.TestFlag", null, "--test-flag");
← Back to Alerts View on GitHub →