Skip to content

flutter_tools: validate plugin identifiers before generating registrant code - #190462

Merged
auto-submit[bot] merged 3 commits into
flutter:masterfrom
herdiyana256:fix-plugin-registrant-codegen-injection
Aug 12, 2026
Merged

auto-submit[bot] merged 3 commits into
flutter:masterfrom
herdiyana256:fix-plugin-registrant-codegen-injection

Conversation

@herdiyana256

Copy link
Copy Markdown
Contributor

AndroidPlugin/IOSPlugin/MacOSPlugin/WindowsPlugin/LinuxPlugin.fromYaml (packages/flutter_tools/lib/src/platform_plugins.dart) read package, pluginClass, and dartPluginClass straight out of a plugin's own pubspec.yaml, validated only as "must be a string" (see plugins.dart's validate() methods). flutter_plugins.dart then mustache-renders the GeneratedPluginRegistrant source for each platform with these values substituted directly, e.g. flutterEngine.getPlugins().add(new {{package}}.{{class}}());, with no escaping at all (MustacheTemplateRenderer defaults to htmlEscapeValues: false).

A pluginClass such as Evil(); some.malicious.Payload.run(flutterEngine); // therefore produces a raw, independently-executing statement in the generated file, which gets compiled into every app that depends on the plugin the next time it runs flutter build/flutter run. Confirmed by rendering the actual _androidPluginRegistryTemplateNewEmbedding template with the real mustache_template package:

flutterEngine.getPlugins().add(new com.example.evil.Evil(); com.example.evil.Payload.run(flutterEngine); //());

The injected Payload.run(flutterEngine); lands as its own statement, not as part of the class name. Same shape applies to iOS/macOS Swift, Windows/Linux C++, and the Dart-only plugin registrant (dartPluginClass/dartFileName, the latter interpolated raw into a Dart import '...' string, so a value containing ' breaks out of that too).

Adds validatePluginIdentifier (rejects anything that isn't a valid, optionally dot-separated identifier) and validateDartFileName (rejects anything that isn't a safe relative *.dart path), called from every platform's fromYaml.

Existing plugin fixtures across android_plugin_test.dart, plugins_test.dart, dart_plugin_test.dart, plugin_parsing_test.dart, and the macOS/Swift-package-manager suites (315 tests total) all continue to pass unchanged. Adds platform_plugins_test.dart covering the injection payload for all five platforms plus the dartFileName import-string-breakout case (10 new tests).

@github-actions github-actions Bot added the tool Affects the "flutter" command-line tool. See also t: labels. label Aug 3, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces validation for plugin identifiers and Dart file names across Android, iOS, macOS, Windows, and Linux platforms to prevent code injection vulnerabilities during registrant source code generation, and adds corresponding unit tests. The review feedback suggests refactoring validateDartFileName to accept a fieldName parameter to make it reusable for other plugin types, along with updating all corresponding call sites.

Comment thread packages/flutter_tools/lib/src/platform_plugins.dart Outdated
Comment thread packages/flutter_tools/lib/src/platform_plugins.dart Outdated
Comment thread packages/flutter_tools/lib/src/platform_plugins.dart Outdated
Comment thread packages/flutter_tools/lib/src/platform_plugins.dart Outdated
Comment thread packages/flutter_tools/lib/src/platform_plugins.dart Outdated
Comment thread packages/flutter_tools/lib/src/platform_plugins.dart Outdated
@bkonyi

bkonyi commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Hi @herdiyana256! Thanks for the contribution. Is there an issue associated with the problem this PR is solving?

@herdiyana256

herdiyana256 commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi bkonyi,

No public issue for this one on purpose -- it's a supply-chain code-injection bug (a malicious pub.dev package's pluginClass/dartFileName gets compiled and executed inside every app that depends on it), so I didn't want to file a public issue spelling out the exploit before there's a fix in place.

Happy to route it through Flutter's security reporting channel if you'd prefer that over discussing further in the open PR -- let me know.

@herdiyana256

Copy link
Copy Markdown
Contributor Author

Update: filed the formal report through Google's official vulnerability disclosure channel.

Google IssueTracker: https://issuetracker.google.com/issues/542989102

@bkonyi
bkonyi self-requested a review August 5, 2026 15:50

@bkonyi bkonyi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for filing the issue! Overall, this LGTM, but I think there's another case we need to handle.

We do still support a "legacy" plugin format in the pubspec that looks like this:

flutter:
  plugin:
    androidPackage: "com.evil.Payload.run(); //"
    pluginClass: "EvilPlugin"

I don't think this case is covered by this PR. Either we'll need to update Plugin._fromLegacyYaml to add validation steps, or update the various Plugin constructors to perform validation there.

Comment thread packages/flutter_tools/lib/src/platform_plugins.dart Outdated
@herdiyana256

Copy link
Copy Markdown
Contributor Author

Good catch, thanks. You're right, _fromLegacyYaml builds AndroidPlugin/IOSPlugin through their plain constructors, not .fromYaml, so none of the validation in this PR was reached for the legacy format.

Fixed in d98ace8 by calling validatePluginIdentifier directly in _fromLegacyYaml for pluginClass, androidPackage, and iosPrefix (that last one wasn't in your example but it also gets interpolated raw as {{prefix}}{{class}} in the iOS template, so same issue). Added three tests in plugins_test.dart covering all three fields through the actual findPlugins pipeline with a legacy-format pubspec, plus the hyphen fix from your other comment.

@bkonyi
bkonyi requested a review from chingjun August 7, 2026 15:30
@bkonyi bkonyi added the CICD Run CI/CD label Aug 7, 2026
bkonyi
bkonyi previously approved these changes Aug 7, 2026

@bkonyi bkonyi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@chingjun chingjun left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we are validating every field we read from yaml. Would it be better if we wrap that in a new function to establish a new pattern to read fields from yaml? That makes the intention clearer, and makes it less likely to forget performing the check when new fields are added.

Something like:

final package = readPluginIdentifier(from: yaml, key: 'package');
final dartFileName = readDartFileName(from: yaml, key: kDartFileName);
...

This makes it more obvious that this is a pattern, than having the validatePluginIdentifier() call several lines below.

@herdiyana256
herdiyana256 force-pushed the fix-plugin-registrant-codegen-injection branch from 7b35b07 to bdefea7 Compare August 8, 2026 06:49
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Aug 8, 2026
@herdiyana256

herdiyana256 commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Heads up: I've rebased and significantly narrowed this PR.

The core fix here (restricting pluginClass/dartPluginClass/package, plus the WebPlugin and dartFileName cases, in every platform's fromYaml) has since landed on master via #189156, so I've dropped all of it to avoid duplicating merged validation.

What's left is the one vector that fix doesn't reach: the legacy plugin format @bkonyi flagged. Plugin._fromLegacyYaml builds AndroidPlugin/IOSPlugin through their plain constructors, bypassing the fromYaml validation, so a legacy-format pubspec with an injected androidPackage / pluginClass / iosPrefix still lands verbatim in the generated registrant on current master. This PR closes that gap: a small validatePluginIdentifier helper reused for all three fields in _fromLegacyYaml, plus three regression tests through the full findPlugins pipeline.

@chingjun the read-fields-via-helper suggestion targets the fromYaml reads, which are now merged and out of scope here; this PR only touches the legacy path and mirrors the existing check. Happy to follow up on that ergonomics separately.

@chingjun

Copy link
Copy Markdown
Contributor

Thanks for pointing out #189156, I thought this looks familiar :)

In that case, can you follow the pattern established in #189156? i.e. check that in the validate() function. Having two different ways to achieve the same thing is confusing to future maintainers.

The existing mechanism with Plugin.fromYaml() will actually show multiple errors at once if the validate returns an invalid result (even though the existing validate() function does not take advantage of that, that should be improved), but throwing a tool exit when that is used will only surface one error at a time.

flutter#189156 restricts pluginClass/dartPluginClass/package to identifiers in each
platform's validate(), but the legacy plugin format is parsed by
Plugin._fromLegacyYaml, which builds AndroidPlugin/IOSPlugin through their plain
constructors and never reaches that validation. A legacy-format pubspec whose
androidPackage, pluginClass or iosPrefix contains injection characters therefore
still lands verbatim in the generated GeneratedPluginRegistrant and is compiled
into any app that (transitively) depends on the plugin.

Follow the pattern flutter#189156 established: check the identifiers in
_validateLegacyYaml alongside the existing type checks, so every invalid field
is reported at once through Plugin.fromYaml's error list rather than exiting on
the first one. This only requires making the existing identifier predicate
visible outside platform_plugins.dart, matching isValidPluginDartFileName.

Adds five tests covering each field's injection payload, the combined
multiple-error output and an unchanged valid legacy declaration.
@herdiyana256
herdiyana256 force-pushed the fix-plugin-registrant-codegen-injection branch from bdefea7 to 419defa Compare August 11, 2026 13:07
@herdiyana256

Copy link
Copy Markdown
Contributor Author

Done, moved the check into _validateLegacyYaml in 419defa so it follows the pattern from #189156: the legacy identifiers are validated alongside the existing type checks and every invalid field is reported at once through Plugin.fromYaml's error list, instead of exiting on the first one. Nothing throws from _fromLegacyYaml anymore, so both plugin formats now fail the same way.

The only other production change is making the existing predicate non-private (_isValidPluginIdentifier -> isValidPluginIdentifier), so plugins.dart can call it the same way it already calls isValidPluginDartFileName. iosPrefix is skipped when empty, since an empty prefix is the default for a legacy plugin that does not set one.

Tests moved next to the existing injection tests in the plugin-parsing group and now go through Plugin.fromYaml directly, matching #189156: one per field (pluginClass, androidPackage, iosPrefix), one asserting all three errors are surfaced together, and one confirming a valid legacy declaration still parses. plugins_test.dart (84), plugin_parsing_test.dart (20), dart_plugin_test.dart (25), android_plugin_test.dart (7), flutter_manifest_test.dart (70) and project_test.dart (116) all pass, and analyzer/formatter are clean.

@bkonyi sorry for dismissing your approval again with the force-push, PTAL when you get a chance.

@bkonyi bkonyi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small comment, then this LGTM!

@@ -215,6 +215,7 @@ class Plugin {
bool isDirectDependency, {
required bool isDevDependency,
}) {
// SAFETY: This constructor is only invoked from .fromYaml, which validates.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I don't think we need this comment here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it is a leftover from the earlier revision where the validation lived in the constructor path, so it no longer describes anything real.

I have left it in for the moment only because a one line commit would dismiss both approvals again. If the branch needs another master sync before it lands I will drop it in the same push, and if you would rather have it gone now just say so and I will push it and re-request review.

@bkonyi bkonyi added autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD labels Aug 11, 2026
@auto-submit

auto-submit Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/190462, because This PR has not met approval requirements for merging. The PR author is not a member of flutter-hackers and needs 1 more review(s) in order to merge this PR.

  • Merge guidelines: A PR needs at least one approved review if the author is already part of flutter-hackers or two member reviews if the author is not a member of flutter-hackers before re-applying the autosubmit label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 11, 2026
@auto-submit

auto-submit Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/190462, because - The status or check suite ci.yaml validation has failed. Please fix the issues identified (or deflake) before re-applying this label.

@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Aug 11, 2026
@bkonyi bkonyi added autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD labels Aug 11, 2026
@auto-submit

auto-submit Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/190462, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 11, 2026
@herdiyana256

Copy link
Copy Markdown
Contributor Author

Both remaining red checks look unrelated to this change, so I don't think there is anything to fix here.

Dashboard Checks is red on two Mac tool_integration_tests subshards, neither of which touches plugin parsing:

  • Mac tool_integration_tests_7 (build 8673724692222606833): debug_adapter/test_adapter_test.dart: widget tests can run in debug mode hit the 15 minute test timeout. The exact same test timed out the same way on an unrelated commit in build 8673722276765844945, and the same shard failed on several other unrelated shas the same day on the neighbouring integration tests can run ... cases (builds 8673734273458043745, 8673742889578939073).
  • Mac tool_integration_tests_4 (build 8673724692440436817): gen_l10n_test.dart: generated l10n classes produce expected localized strings failed with Exception: Received app.stop event while waiting for app.started event, i.e. flutter-tester never came up. The same shard failed on unrelated shas the same day on swift_package_manager_create_plugin_test.dart and lldb_init_test.dart, and once on this same gen_l10n_test case with a missing dartaotruntime in the search path.

Across the last 60 try builds of each, those two shards are at 8/60 and 10/60 failures on a wide spread of commits.

Google testing ("Internal smoke tests failed") went red on at least six other PRs in the same window: #190935, #190667, #190929, #190014, #189500 and #185430, all of which have since merged.

For what it's worth, the diff only adds string checks inside Plugin._validateLegacyYaml, so it has no path to the l10n generator or the DAP adapter. Every tool_tests_general shard (Linux, Mac, Windows), which is where plugins_test.dart runs, is green on a57d2e0, as are the other 22 tool integration shards.

@bkonyi could you re-apply autosubmit when you get a chance? I can't set the label myself, and I'd rather not push a no-op commit since that dismisses both approvals again.

@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Aug 12, 2026
@herdiyana256

Copy link
Copy Markdown
Contributor Author

Correction to my previous comment, and an update.

The argument I made about Google testing there was weak and I want to withdraw it. I said the check had gone red on six other PRs that have since merged. That is true only in the sense that it went red at some point in each of them; every one of those six ended up green on its final head before it landed. So it showed nothing, and I should have checked that before posting it.

The actual evidence is better than what I offered, and it points at the master sync rather than at this PR:

head contents Google testing
419defa this PR's diff, on the older base success, 19:39 UTC
a57d2e0 same diff plus 87 master commits failure, 23:05 UTC

a57d2e0 is purely the merge commit, so the diff under test is byte for byte identical in both runs. The same content passed at 19:39 and failed at 23:05. The only variable is the 87 commits that came in with the sync, which include four Dart SDK rolls and #190480's move of examples/api to packages/flutter/examples/api. Whatever broke has since cleared: #190951 and #190960 both got a green Google testing after that window.

I have synced master again, so this is now on 9c298d7 and everything is re-running. Both approvals carried over, since a merge from base does not dismiss them the way my earlier force-pushes did.

The Dashboard Checks analysis in my previous comment still stands as written: Mac tool_integration_tests_4 and _7 failed on gen_l10n_test.dart and debug_adapter/test_adapter_test.dart, neither reachable from a change to Plugin._validateLegacyYaml, and both shards fail at roughly 8/60 and 10/60 across unrelated commits. The re-run should give those a fresh roll of the dice too.

@bkonyi the CICD label came off automatically with the new commit, so Dashboard Checks is sitting behind Awaiting CICD label. Could you re-add it, and re-apply autosubmit if things come back green?

On your nit: I deliberately kept this push a pure merge from base so the approvals would survive, which meant not touching the comment in the same commit. Happy to drop it as a follow-up, or now if you would rather have it gone before this lands and do not mind re-approving.

@herdiyana256

Copy link
Copy Markdown
Contributor Author

Google testing came back green on 9c298d7 (successful in 22m), which confirms the diagnosis above: the same diff that failed on a57d2e0 passes once master is re-synced, so nothing in this PR was ever the cause.

@bkonyi three things need a maintainer click before this can land, none of which I can do:

  1. Approve the 3 pending workflow runs (Mac Verify Binaries, Tree Analyze, No PRs on main), which are held because this is a fork PR.
  2. Add the CICD label. It came off automatically with the new commit, and Dashboard Checks has not started at all yet, it is still sitting on Awaiting CICD label.
  3. Re-apply autosubmit once the presubmit comes back green.

No rush on my end, whenever you get a moment.

@bkonyi bkonyi added CICD Run CI/CD autosubmit Merge PR when tree becomes green via auto submit App labels Aug 12, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Aug 12, 2026
Merged via the queue into flutter:master with commit e3ee762 Aug 12, 2026
29 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 12, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Aug 13, 2026
…#12453)

Manual roll Flutter from 27b098811f3b to c2437523d308 (179 revisions)

Manual roll requested by [email protected]

flutter/flutter@27b0988...c243752

2026-08-12 [email protected] Enable Gradle CI cache on all test targets that require android_sdk (flutter/flutter#190723)
2026-08-12 [email protected] [analysis] Reland "Added initial implementation of the flutter_analyzer_plugin (#175679)" (flutter/flutter#191022)
2026-08-12 [email protected] Switch testing to gradle bin distribution type instead of all (flutter/flutter#190738)
2026-08-12 [email protected] Convert Mockito instances in Kotlin to Mockk (flutter/flutter#189884)
2026-08-12 [email protected] Report individual test results to LUCI ResultDB (flutter/flutter#190254)
2026-08-12 [email protected] Toggleable reaction duration respects overrides (flutter/flutter#190857)
2026-08-12 [email protected] Roll Skia from e00dbd7448c4 to fee7272f5bc2 (1 revision) (flutter/flutter#191007)
2026-08-12 [email protected] Started caching text shadows by content. (flutter/flutter#190681)
2026-08-12 [email protected] Adds agent skill for spawning led tasks. (flutter/flutter#190937)
2026-08-12 [email protected] Roll Packages from aaaf246 to 94485f1 (8 revisions) (flutter/flutter#191008)
2026-08-12 [email protected] flutter_tools: validate plugin identifiers before generating registrant code (flutter/flutter#190462)
2026-08-12 [email protected] Roll Skia from 112f36148949 to e00dbd7448c4 (3 revisions) (flutter/flutter#190993)
2026-08-12 [email protected] Roll Skia from 7d366c802307 to 112f36148949 (3 revisions) (flutter/flutter#190983)
2026-08-12 [email protected] remove bringup for flavors test (flutter/flutter#190940)
2026-08-12 [email protected] Roll Skia from 1f10a20bdd61 to 7d366c802307 (2 revisions) (flutter/flutter#190980)
2026-08-12 [email protected] Remove `--no-sim-use-hardfp` flag (flutter/flutter#190790)
2026-08-12 [email protected] Roll pub packages (flutter/flutter#190977)
2026-08-12 [email protected] Roll Skia from 339bedab6766 to 1f10a20bdd61 (1 revision) (flutter/flutter#190975)
2026-08-12 [email protected] RawTooltip respects AnimationStyle updates and reverseCurve (flutter/flutter#190889)
2026-08-12 [email protected] ci: Support --target_arch option in prepare_package.dart (flutter/flutter#190960)
2026-08-12 [email protected] Roll Fuchsia Linux SDK from SFq4FVodIOQAS26Lr... to -uHuSGv3wt7QAlDwa... (flutter/flutter#190973)
2026-08-12 [email protected] Removes building of ci/android_debug_x86 as nobody should be consuming it. (flutter/flutter#190951)
2026-08-12 [email protected] Adds error about wimp_heavy not being implemented. (flutter/flutter#189945)
2026-08-11 [email protected] Add clang, cmake, and ninja deps to Linux windowing_test (flutter/flutter#190119)
2026-08-11 [email protected] [AGP 9.1.0 Migration #1] Add Android Gradle Plugin Public API migration documentation (flutter/flutter#190842)
2026-08-11 [email protected] [flutter_tools] Fix deadlock in debug adapters when process exits early (flutter/flutter#190931)
2026-08-11 [email protected] [tool] Define modular dependency injection containers and bootstrapper (flutter/flutter#190724)
2026-08-11 [email protected] [web] Unify MaskFilter and ColorFilter primitives across CanvasKit and Skwasm (flutter/flutter#190314)
2026-08-11 [email protected] Roll pub packages (flutter/flutter#190958)
2026-08-11 [email protected] [Impeller] Move image upload scheduling waits to GPU disable (flutter/flutter#190445)
2026-08-11 [email protected] Started generating the windows platform for macrobenchmarks (flutter/flutter#190932)
2026-08-11 [email protected] [web] Unify ui.Vertices (flutter/flutter#190563)
2026-08-11 [email protected] Fix accessibility_inspector service extensions map mutability (flutter/flutter#190888)
2026-08-11 [email protected] Add batch3 a11y_assessment for vpat (flutter/flutter#189042)
2026-08-11 [email protected] Remove Xcode environment when building swift tools in Xcode pre-action (flutter/flutter#190848)
2026-08-11 [email protected] [tool] Add missing play element in web test index.html to fix warning (flutter/flutter#190675)
2026-08-11 [email protected] Offload blocking work in ProcessTextPlugin to the background (flutter/flutter#189823)
2026-08-11 [email protected] [flutter_tools] Replace usages of package:dds/dap.dart with package:dap_adapters/dap_adapters.dart (flutter/flutter#190667)
2026-08-11 [email protected] Always update swift package dependencies (flutter/flutter#190886)
2026-08-11 [email protected] [flutter_tools] Add --preset option to flutter test (flutter/flutter#190878)
2026-08-11 [email protected] Include the examples cross imports checker in the analzyer. (flutter/flutter#190674)
2026-08-11 [email protected] [devicelab] Remove orphaned screenshot test files (flutter/flutter#190879)
2026-08-11 [email protected] Remove the bringup flag from the linux_arm_host_desktop_engine builder (flutter/flutter#190935)
2026-08-11 [email protected] Reduce web_skwasm_tests subshards from 8 to 2 (flutter/flutter#190728)
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants