Repository navigation
fix(plugins): stop passing grant_capabilities= to loader that rejects it (#17420) - #17427
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: mrveiss/AutoBot-AI/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe manual-load endpoint no longer passes an unsupported argument to the plugin loader. After a successful load, it grants manifest capabilities through ChangesManual plugin loading
Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: 🔵 Low · up to A manually loaded community plugin can appear to have approved capabilities by declaring itself official. Those grants are not currently enforced in this repository, so the present risk is bounded to incorrect approval state; the PR is mergeable with that limitation understood. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to Manual loading remains restricted to administrators, but the newly working grant can rely on a plugin’s own trust declaration and can outlive an unload. Both warrant review before the capability policy is relied on. Retained concerns
Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ SSOT Configuration Compliance: Passing🎉 No new hardcoded values of either class — Known backlog in |
06e9d91 to
1fafe06
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: mrveiss/AutoBot-AI/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Advanced
Run ID: 60a8702a-99e3-4b04-b350-0e6e69dd95f0
📒 Files selected for processing (2)
autobot-backend/plugin_manager.pyautobot-backend/tests/api/test_plugin_manager.py
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.
… it (#17420) autobot-backend/plugin_manager.py:271 called loader.load_plugin(manifest, plugin_config, grant_capabilities=auto_grant), but autobot_shared/plugin_sdk/loader.py:320 defines load_plugin(self, manifest, config) — no grant_capabilities kwarg. Every manual plugin-load raised TypeError: load_plugin() got an unexpected keyword argument 'grant_capabilities' at runtime. Fix: drop the kwarg, then invoke the already-implemented CapabilityChecker.grant_capabilities() API (autobot_shared/plugin_sdk/capabilities.py:136) for official plugins. This preserves the #9049 contract (non-official plugins remain ungranted until operator approval via /approve-capabilities) while touching only the caller — the same-file rule keeps loader.py free for #17421. Why option B (caller-side fix) over option A (loader accepts+enforces): - Grant API already exists as a singleton (capabilities.py:136); adding a grant_capabilities kwarg to the loader would duplicate bool→List policy into a module whose job is loading, not policy. - Narrower blast radius: plugin_sdk/loader.py stays untouched — critical because #17421 (blocked_by this issue) also targets loader.py. - Uses PluginManifest.capabilities (base.py:134) directly — no invented gate. Line-count discipline (#14236 ratchet): plugin_manager.py is grandfathered at ceiling 702 (scripts/python_file_size_known_large.py:300). The bug fix requires an if/grant block (+2 lines). Compensated by removing the 3-line comment block above the load call and the auto_grant intermediate variable (4 lines), then adding back a 1-line #9049 policy comment. Net: -4 + 2 + 1 line changed = -1 line of code adjusted through a single-line comment, so the file lands at exactly 702. Reasoning that used to live in the comment block now lives in this commit body; git log -S finds it there. Tests (autobot-backend/tests/api/test_plugin_manager.py): - mutation guard asserts the kwarg is not passed (catches the regression) - positive: OFFICIAL plugin -> CapabilityChecker.grant_capabilities invoked with the manifest's declared capabilities - positive control: COMMUNITY plugin -> grant_capabilities NOT invoked
1fafe06 to
4743707
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: mrveiss/AutoBot-AI/.coderabbit.yaml
Review profile: ASSERTIVE
Plan: Advanced
Run ID: db0cbb04-b797-4891-95ae-6965ebdbbc48
📒 Files selected for processing (2)
autobot-backend/plugin_manager.pyautobot-backend/tests/api/test_plugin_manager.py
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.
|
Picked this up as unattended work and unblocked it — last agent push was 3h+ ago, only the branch-update bot since, no worktree and no ledger claim. Claimed Red root cause: Fixed in Pushed as a fast-forward on top of Both review threads resolved with a verdict, not a click. Neither blocks this PR, and the reason is the same for both:
Both are carried to #17459 as acceptance criteria with a mutation-proof requirement, because they are one fix and not three: wiring |
Closes #17420
Single-issue rationale: the natural batch-mate is #17421 (every discovered plugin imported and enabled at boot, no opt-in gate). #17421 also touches
autobot_shared/plugin_sdk/loader.pyand isblocked_by#17420 — its fix depends on the capability-grant delivery mechanism this PR establishes. Per the same-file rule (one agent, one PR) and the blocked_by edge, #17421 must land in its own PR after this one merges. Folding it here would violate the sequential-blockedby ordering and expand the loader-side change into a batch #17420 alone cannot honestly carry.Thinking Path
The defect.
autobot-backend/plugin_manager.py:271calledloader.load_plugin(manifest, plugin_config, grant_capabilities=auto_grant).autobot_shared/plugin_sdk/loader.py:320defines the signature asload_plugin(self, manifest: PluginManifest, config: Dict | None = None)— nogrant_capabilitiesparameter. Every manual plugin-load raisedTypeError: load_plugin() got an unexpected keyword argument 'grant_capabilities'at runtime.Two shapes were possible (per the issue): (A) make the loader accept and enforce the grant, or (B) stop passing the kwarg at the caller and grant through the API that already exists.
Evidence I read (bounded to the sites the issue names):
autobot_shared/plugin_sdk/capabilities.py:136—CapabilityChecker.grant_capabilities(plugin_name, capabilities: List[Capability])already exists as a singleton API and is what the enforcement path (capabilities.py:197,check()) reads from. Grants are in-memory only, but the mechanism is complete: there is no missing gate to invent.autobot_shared/plugin_sdk/base.py:134—PluginManifest.capabilities: List[Capability]— the manifest already declares exactly what would be granted, so the caller has everything needed to callgrant_capabilities(name, manifest.capabilities)itself.autobot-backend/plugin_manager.py:26—CapabilityCheckeris already imported in the caller module; the fix adds no new import.CapabilityCheckeras the grant surface; the caller just wasn't wired to it.Chose (B): stop passing the kwarg, grant at the caller.
Rationale:
grant_capabilities: boolparameter to the loader would duplicate policy (bool →manifest.capabilitiesconversion) into a module whose job is loading, not policy. SRP wins.plugin_sdk/loader.pyat all — critical because security(plugins): every discovered plugin is imported and enabled at boot — no opt-in gate, no persisted enable state, no integrity check #17421 (blocked_by this issue) also touchesloader.pyand the same-file rule keeps that PR clean.What (A) would have implied — a signature change on
load_plugin(public API of the SDK), adding capability-policy branching inside the loader, and tests for the loader's new gate. That's a wider change to solve the same runtime symptom, and would put the loader and the caller in the same PR alongside #17421's concerns. Not chosen.#9049 contract preserved: the
manifest.trust_tier == TrustTier.OFFICIALgate is unchanged; only the delivery mechanism moved from a nonexistent kwarg toCapabilityChecker().grant_capabilities(...). Non-official plugins still require operator approval via/approve-capabilities.What Changed
autobot-backend/plugin_manager.py— replaced the invalidgrant_capabilities=kwarg with a post-load call toCapabilityChecker().grant_capabilities(manifest.name, manifest.capabilities)guarded byif manifest.trust_tier == TrustTier.OFFICIAL:(theauto_grantintermediate variable was removed to conserve lines — see below). Existingif not plugin:HTTPException short-circuits before the grant. No loader signature change;plugin_sdk/loader.pyuntouched.Line-count discipline (tech-debt(lint): the file-size hook's grandfather list has no ceiling — the three exempted files grew to 1.4x-6.8x the limit #14236 ratchet).
plugin_manager.pyis grandfathered at ceiling 702 (scripts/python_file_size_known_large.py:300). The functional fix wants 2 net lines (if …:+ grant call). To keep the file at its recorded ceiling instead of raising it, three obsolete lines above the load call (the multi-line comment about "auto-granting" and theauto_grant = …intermediate variable) were removed and replaced with a single-line#9049policy comment. The removed reasoning now lives in the commit message body, wheregit log -S "grant_capabilities"finds it. Final size: exactly 702 lines — matches the recorded ceiling, does not raise it.autobot-backend/tests/api/test_plugin_manager.py— added three tests covering the manual-load endpoint:test_load_plugin_endpoint_does_not_pass_grant_capabilities_kwarg— mutation guard, inspectsloader.load_plugin.call_args.kwargsto fail loudly if the broken kwarg ever comes back. This is the direct regression sentinel.test_load_plugin_endpoint_auto_grants_official_plugin_capabilities— assertsCapabilityChecker.grant_capabilitiesis invoked exactly once with(plugin_name, manifest.capabilities)whentrust_tier=OFFICIAL.test_load_plugin_endpoint_does_not_auto_grant_community_plugin— positive control: proves the auto-grant is gated on trust tier, not unconditional. Without this a "grants everything" regression would satisfy test Docs improvements #2.Verification
Run 1 — fix in place, targeted tests only:
Run 2 — mutation (broken call restored,
grant_capabilities=auto_grantkwarg back in):The mutation guard fails for the intended reason — it observes
{'grant_capabilities': False}in the kwargs the caller sent to the loader, which is exactly the shape the runtime would have raisedTypeErroron. The other two tests pass on mutation becauseMagicMocksilently accepts any kwarg; a realPluginLoaderwould raiseTypeErrorbefore returning, so the kwarg-inspection assertion is the honest sentinel — it catches the defect at its source (the call site) regardless of what the loader mock swallows.Run 3 — fix restored, targeted tests:
Ratchet check.
plugin_manager.pyis at 702 lines (wc -lafter the slim edit); recorded ceiling inscripts/python_file_size_known_large.py:300is702.test_recorded_ceilings_match_the_files_todaywill now clear (it failed the earlier revision at 709; the ratchet-mandated fix was to shrink obsolete boilerplate rather than raise the ceiling — the ratchet exists to prevent exactly the latter, per #14236).Full suite not run (per issue-batching economy). CI will exercise the wider surface.
Environment note: local Python is 3.11 with a partial dep set; the pytest header notes "environment is BELOW the declared dependency floors" for 6 of 209 declared versions (pyyaml, pyjwt, starlette, requests). None of these are transitive to
plugin_manager.load_pluginorCapabilityChecker— the tests only touch pydantic models and unittest.mock. CI runs the declared floors and is the authoritative gate.Scope boundary:
autobot_shared/plugin_sdk/loader.pyis deliberately untouched. #17421 (every discovered plugin imported and enabled at boot, no opt-in gate, no signature check) isblocked_bythis issue and needs the loader-side work; the same-file rule keeps that as its own PR, not folded here.Model Used
Claude Opus 4.7 (1M context) — model ID
claude-opus-4-7[1m].Summary by CodeRabbit