security(plugins): govern ALL entry-point groups by the unified allow/block (total trust boundary)#297
Merged
Merged
Conversation
…/block (no bypass) The final re-assessment found the one structural blocker to a uniform trust boundary: four CLI-internal entry-point groups — fluid_build.commands, fluid_build.apply_hooks, fluid_build.extension_schemas, fluid_build.extension_validators — walked importlib.metadata OUTSIDE the unified manager, never called is_allowed, and were invisible to `fluid plugins`. Two of them (apply_hooks, extension_validators) ep.load() and EXECUTE plugin code with no operator policy, using best-effort redact_secret_text(str(e)) instead of the type-only guarantee. So FLUID_PLUGINS_BLOCKLIST could not stop a malicious apply_hook / extension-validator from loading and running. - plugin_manager gains EXTRA_GROUPS (the four CLI-internal groups) + governed_groups() = ROLE_GROUPS ∪ EXTRA_GROUPS — the single source of truth for "every group the operator allow/block policy governs". - All four walk sites (bootstrap.py commands, apply.py apply_hooks, extension_schemas.py schemas + validators) now gate each entry-point through is_allowed(ep.name) BEFORE ep.load(), and log load/discovery failures by exception TYPE only (was redact_secret_text(str(e))). Dropped the now-unused redact_secret_text import + stale comment in bootstrap. - installed_plugins() (and thus `fluid plugins`) now surfaces all governed groups, so commands/apply_hooks/extension_* are no longer invisible. The "one allow/block policy for every plugin surface" invariant is now total. 107 governance/validate/apply/provider/help-sync tests pass; 7 new pins (governed-group coverage, inspection surface, extension schema/validator blocked before load).
…+ source_adapters The PR-I security review found two MORE code-executing entry-point walks that still bypassed the gate (pre-existing): fluid_build.modeling_techniques (_discover_entrypoints ep.load()) and fluid_build.source_adapters (resolve_catalog_adapter_class lazy target.load()). To make the "one allow/block policy for every plugin surface" invariant genuinely TOTAL: - EXTRA_GROUPS gains modeling_technique + source_adapter (governed_groups + `fluid plugins` now cover all 11 groups: 5 roles + 6 CLI-internal). - modeling_techniques._discover_entrypoints gates each ep through is_allowed BEFORE load; type-only failure logging. - source_registry.resolve_catalog_adapter_class gates the lazy target.load() through is_allowed (raises a clear RuntimeError when blocked); type-only discovery logging. 1657 copilot/modeling/source tests pass; 2 new gate pins (source adapter + modeling technique blocked before load). Now NO entry-point group that runs plugin code escapes the operator allow/block.
📄 Documentation ReminderThis PR appears to be missing a documentation reference. Our docs live in a separate repo. Please update the PR description with one of:
See the Contributing Guide for details. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Closes the single structural blocker the final re-assessment (8.6/10) named: a
uniform plugin trust boundary. After the round-2 work, six entry-point groups
still walked
importlib.metadataandep.load()'d plugin code outside theunified manager — never calling
is_allowed, invisible tofluid plugins, andlogging raw
redact_secret_text(str(e)). Several execute plugin code, soFLUID_PLUGINS_BLOCKLISTcould not stop a maliciousapply_hook,extension_validator,modeling_technique, orsource_adapterfrom running.Changes
plugin_managergainsEXTRA_GROUPS+governed_groups()=ROLE_GROUPS ∪the six CLI-internal groups (
commands,apply_hooks,extension_schemas,extension_validators,modeling_techniques,source_adapters) — the singlesource of truth for every group the allow/block policy governs.
is_allowed(ep.name)before
ep.load(), and log load/discovery failures by exception type only.installed_plugins()/fluid pluginsnow surface all governed groups — none areinvisible.
The "one allow/block policy for every plugin surface" invariant is now total:
no entry-point group that runs plugin code escapes the operator gate.
Verification
(each of the 6 groups + governed-group coverage + inspection surface).
ruff+black==24.10.0clean.load()at every site, type-only logging strictly more conservative, no importcycle; the reviewer explicitly identified
modeling_techniques+source_adaptersas the remaining ungated walks, which the second commit closes with the identical pattern.
fluid plugins+ the CLI: the extension groups are now visible;FLUID_PLUGINS_BLOCKLIST=demoextblocks an extension-validator from loading andrunning (previously impossible).