Configuration weakness / insecure default (feature flag)
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");