Skip to content

Make sure that extensions are bundled with the package they claim to be. - #9981

Open
johnpryan wants to merge 8 commits into
flutter:masterfrom
johnpryan:package-centric-extensions
Open

Make sure that extensions are bundled with the package they claim to be.#9981
johnpryan wants to merge 8 commits into
flutter:masterfrom
johnpryan:package-centric-extensions

Conversation

@johnpryan

Copy link
Copy Markdown
Contributor

With this change, extensions are enabled if and only if the package name on disk matches the package name in extension/devtools/config.yaml.

This supersedes #9965

With this change, extensions are enabled if and only if the package name
on disk matches the package name in extension/devtools/config.yaml.
@johnpryan
johnpryan requested review from a team, bkonyi and kenzieschmoll as code owners August 26, 2026 21:40
@johnpryan
johnpryan requested review from srawlins and removed request for a team August 26, 2026 21:40

@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 improves DevTools extension isolation by tracking the providing package name for enablement, deduplication, and asset loading, and adds validation checks for extension names. The review feedback highlights a compilation error in _extensions_api.dart due to invalid map literal syntax, a potential runtime TypeError in _validate.dart when casting the configuration name, and a suggestion to normalize packageRoot in extension_manager.dart for more robust path comparisons.

Comment thread packages/devtools_app/lib/src/shared/server/_extensions_api.dart
Comment thread packages/devtools_extensions/bin/_validate.dart Outdated
Comment thread packages/devtools_shared/lib/src/extensions/extension_manager.dart Outdated
Comment thread packages/devtools_app/lib/src/extensions/extension_service_helpers.dart Outdated
return DevToolsExtensionConfig._(
// These values are required fields in the extension's config.yaml file.
name: name,
packageName: packageName,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the comment above name is intended for all keys until the next comment on line 82, so package should not go here unless we are expecting users to manually enter this in the extension config.yaml file.

Comment thread packages/devtools_shared/CHANGELOG.md

// Spoofed package does not inherit provider's enablement
expect(
options.lookupExtensionEnabledState(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this going to be backwards compatible for existing devtools_options.yaml entries from before this change?

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.

Yes, for most packages (like patrol or provider), the packageName field (from .dart_tool/package_config.json') matches the name` field (the package name the extension is declared to before in config.yaml).

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.

If they don't match, then users will need to re-enable the extension after this change lands, because DevToolsExtensionConfig.identifier will be put in devtools_options.yaml file instead.

throw FileSystemException('${packageDirectory.path} directory not found');
}

final pubspecFile = File(path.join(packageDirectory.path, 'pubspec.yaml'));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the additional validations added to this file seem useful, but they also seem out of scope of this PR. Are these somehow related to resolving the security bug?

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.

I think AGY added this because extensions need to be included in a Dart package, right?

codePoint = codePointFromJson as int;
}

final packageName = json[packageNameKey] as String? ?? name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

where is the packageName getting set in the json? Maybe I'm missing it in this PR.

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.

In extension_manager.dart L191, the ExtensionsManager class puts this key into DevToolsExtensionConfig manually when detecting the available extensions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Similar comment to above, but when would we expect this to be null. Is this for backwards compatibility?

Comment on lines +157 to +159
/// This value is parsed from the package name in
/// `.dart_tool/package_config.json`.
final String packageName;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

related to my last comment. Where is this parsing code?

@johnpryan johnpryan Sep 1, 2026

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.

My understanding is that this is parsed from the package_config.json file in the extension_discovery package, and then ExtensionsManager puts this value into DevToolsExtensionConfig.

@johnpryan

Copy link
Copy Markdown
Contributor Author

@kenzieschmoll this is ready for another look

ExtensionsApi.extensionNamePropertyName: extensionName,
if (enable != null)
ExtensionsApi.enabledStatePropertyName: enable.toString(),
ExtensionsApi.extensionPackagePropertyName: ?extensionPackage,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

when would we ever expect this to be null?

[#8507](https://github.com/flutter/devtools/issues/8507)
* Improved DevTools extension isolation by tracking the providing package name for
enablement, deduplication, and asset loading.
[#9965](https://github.com/flutter/devtools/pull/9965)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

incorrect pr link

## 0.5.2-wip
* The minimum Dart SDK version is bumped to 3.11.0.
* The minimum Flutter SDK version is bumped to 3.41.0.
* Updates `devtools_shared` constraint to `^14.0.1`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

also include the change that was made to the validate tool

Comment on lines +64 to +65
/// Sets the enabled state for [extensionName] (and optionally [packageName])
/// in the 'devtools_options.yaml' file at [devtoolsOptionsUri].

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this should actually be "Sets the enabled state of [packageName] (falls back to [extensionName]) in the ..."

Comment on lines +33 to +34
/// Returns the current enabled state for [extensionName] (and optionally
/// [packageName]) in the 'devtools_options.yaml' file at [devtoolsOptionsUri].

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same comment here

final missingKeys = <String>[];
for (final key in _serverGeneratedKeys) {
if (key == packageNameKey) {
continue; // Optional for backwards compatibility.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When do we need this? Since this is a generated key, a user who is using this version of the DevTools server with the changes in this PR should always have this key. I believe we only would need backwards compatibility for the keys we expect an extension author to provide in the extension's config.yaml file


final configWithDistinctPackage = DevToolsExtensionConfig.parse({
'name': 'provider',
'packageName': 'provider_devtools_extension',

Copy link
Copy Markdown
Member

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 this is reflective of the package provider extension. it is shipped with the provider package, not provider_devtools_extension where it is developed

class TestPackageWithExtension {
TestPackageWithExtension({
required this.name,
String? packageName,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

let's make this non-nullable since we expect all extensions to have a package name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants