fix(plugin): validate entry point module paths against allowlist before loading #1292

Open
opened 2026-04-02 09:07:42 +00:00 by freemo · 0 comments
Owner

Metadata

  • Branch: fix/plugin-loader-entry-point-validation
  • Commit Message: fix(plugin): validate entry point module paths against allowlist before loading
  • Milestone: v3.3.0
  • Parent Epic: #939

Bug Report: [security] — PluginLoader does not validate entry point module paths

Severity Assessment

  • Impact: A malicious or compromised dependency could use an entry point to execute arbitrary code within the application, bypassing the _allowed_prefixes module prefix allowlist security control. This could lead to a full system compromise.
  • Likelihood: Low to Medium. It requires a malicious package to be installed in the environment.
  • Priority: High

Location

  • File: src/cleveragents/infrastructure/plugins/loader.py
  • Function/Class: PluginLoader.load_from_entry_points
  • Lines: 184–211

Description

The PluginLoader.load_from_entry_points method discovers and loads plugins from importlib.metadata entry points. However, it does not validate the module path of the entry point against the _allowed_prefixes allowlist before calling ep.load(). This allows a malicious package to register an entry point that points to an arbitrary module, which will then be loaded and executed by the application.

# src/cleveragents/infrastructure/plugins/loader.py:184
        for ep in eps:
            try:
                ep.load()
                module_path = ep.value.rsplit(":", 1)[0] if ":" in ep.value else ""
                # ...

The ep.load() call happens before any validation of the module path.

Expected Behavior

The load_from_entry_points method should parse the module path from the entry point's value, validate it against the _allowed_prefixes allowlist via _validate_module_prefix, and only then call ep.load().

Actual Behavior

The entry point is loaded without validation, creating a security vulnerability that bypasses the allowlist security control.

Suggested Fix

Move the module path extraction and allowlist validation to occur before ep.load():

        for ep in eps:
            try:
                module_path = ep.value.rsplit(":", 1)[0] if ":" in ep.value else ""
                if not module_path:
                    raise PluginLoadError(f"Entry point '{ep.name}' has an invalid value: {ep.value}")

                self._validate_module_prefix(module_path)

                ep.load()
                class_name = ep.value.rsplit(":", 1)[1] if ":" in ep.value else ep.value
                # ...

Subtasks

  • Move module_path extraction to before ep.load() in PluginLoader.load_from_entry_points
  • Add guard: raise PluginLoadError if module_path is empty or malformed
  • Call self._validate_module_prefix(module_path) before ep.load()
  • Write Behave scenario: entry point with disallowed module prefix is rejected before loading
  • Write Behave scenario: entry point with allowed module prefix is loaded successfully
  • Write Behave scenario: entry point with empty/malformed value raises PluginLoadError
  • Verify nox -e typecheck passes (0 Pyright errors)
  • Verify nox -e unit_tests passes
  • Verify nox -e coverage_report >= 97%
  • Run full nox suite and fix any errors

Definition of Done

  • PluginLoader.load_from_entry_points validates the module path against _allowed_prefixes before calling ep.load()
  • A PluginLoadError is raised for any entry point whose module path is empty, malformed, or not in the allowlist
  • All new Behave scenarios pass
  • All nox stages pass
  • Coverage >= 97%
## Metadata - **Branch**: `fix/plugin-loader-entry-point-validation` - **Commit Message**: `fix(plugin): validate entry point module paths against allowlist before loading` - **Milestone**: v3.3.0 - **Parent Epic**: #939 ## Bug Report: [security] — PluginLoader does not validate entry point module paths ### Severity Assessment - **Impact**: A malicious or compromised dependency could use an entry point to execute arbitrary code within the application, bypassing the `_allowed_prefixes` module prefix allowlist security control. This could lead to a full system compromise. - **Likelihood**: Low to Medium. It requires a malicious package to be installed in the environment. - **Priority**: High ### Location - **File**: `src/cleveragents/infrastructure/plugins/loader.py` - **Function/Class**: `PluginLoader.load_from_entry_points` - **Lines**: 184–211 ### Description The `PluginLoader.load_from_entry_points` method discovers and loads plugins from `importlib.metadata` entry points. However, it does not validate the module path of the entry point against the `_allowed_prefixes` allowlist **before** calling `ep.load()`. This allows a malicious package to register an entry point that points to an arbitrary module, which will then be loaded and executed by the application. ```python # src/cleveragents/infrastructure/plugins/loader.py:184 for ep in eps: try: ep.load() module_path = ep.value.rsplit(":", 1)[0] if ":" in ep.value else "" # ... ``` The `ep.load()` call happens before any validation of the module path. ### Expected Behavior The `load_from_entry_points` method should parse the module path from the entry point's `value`, validate it against the `_allowed_prefixes` allowlist via `_validate_module_prefix`, and only then call `ep.load()`. ### Actual Behavior The entry point is loaded without validation, creating a security vulnerability that bypasses the allowlist security control. ### Suggested Fix Move the module path extraction and allowlist validation to occur **before** `ep.load()`: ```python for ep in eps: try: module_path = ep.value.rsplit(":", 1)[0] if ":" in ep.value else "" if not module_path: raise PluginLoadError(f"Entry point '{ep.name}' has an invalid value: {ep.value}") self._validate_module_prefix(module_path) ep.load() class_name = ep.value.rsplit(":", 1)[1] if ":" in ep.value else ep.value # ... ``` ## Subtasks - [ ] Move `module_path` extraction to before `ep.load()` in `PluginLoader.load_from_entry_points` - [ ] Add guard: raise `PluginLoadError` if `module_path` is empty or malformed - [ ] Call `self._validate_module_prefix(module_path)` before `ep.load()` - [ ] Write Behave scenario: entry point with disallowed module prefix is rejected before loading - [ ] Write Behave scenario: entry point with allowed module prefix is loaded successfully - [ ] Write Behave scenario: entry point with empty/malformed value raises `PluginLoadError` - [ ] Verify `nox -e typecheck` passes (0 Pyright errors) - [ ] Verify `nox -e unit_tests` passes - [ ] Verify `nox -e coverage_report` >= 97% - [ ] Run full `nox` suite and fix any errors ## Definition of Done - [ ] `PluginLoader.load_from_entry_points` validates the module path against `_allowed_prefixes` before calling `ep.load()` - [ ] A `PluginLoadError` is raised for any entry point whose module path is empty, malformed, or not in the allowlist - [ ] All new Behave scenarios pass - [ ] All nox stages pass - [ ] Coverage >= 97%
freemo added this to the v3.3.0 milestone 2026-04-02 09:08:13 +00:00
freemo self-assigned this 2026-04-02 18:45:25 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#1292
No description provided.