BUG-HUNT: [security] PluginLoader.validate_protocol() instantiates arbitrary plugin classes with no-arg constructor — can execute arbitrary code during validation #7418

Closed
opened 2026-04-10 19:08:39 +00:00 by HAL9000 · 3 comments
Owner

Bug Report: Security — Arbitrary Code Execution During Plugin Protocol Validation

Severity Assessment

  • Impact: The validate_protocol() method instantiates plugin classes by calling klass() with no arguments as part of protocol validation. A malicious or buggy plugin constructor can execute arbitrary code (file I/O, network calls, subprocess execution) during what should be a safe structural check. This is especially dangerous because validate_protocol() is called before the plugin is confirmed to be safe.
  • Likelihood: Medium — requires a malicious plugin to be installed in the cleveragents.plugins entry-point group, but entry-point discovery loads ALL registered plugins
  • Priority: High (security)

Location

  • File: src/cleveragents/infrastructure/plugins/loader.py
  • Function: PluginLoader.validate_protocol()
  • Lines: 171–206

Description

The validate_protocol() static method attempts to create an instance of the plugin class to validate it against a protocol:

@staticmethod
def validate_protocol(klass: type[Any], protocol: type[Any]) -> bool:
    try:
        instance = klass()          # ← DANGEROUS: instantiates unknown class!
        if isinstance(instance, protocol):
            return True
    except Exception:
        # If instantiation fails, try subclass check
        try:
            if issubclass(klass, protocol):
                return True
        except TypeError:
            pass

This means:

  1. Any class discovered via entry points will be instantiated during validation
  2. The constructor of that class executes in the calling process with full permissions
  3. If the plugin constructor makes network connections, spawns processes, reads files, or makes destructive changes — all of this happens before the plugin is confirmed safe
  4. The except Exception: pass silently swallows ALL constructor exceptions, which could hide serious problems (e.g., resource exhaustion, persistent side effects)

The fallback issubclass check should always be tried first (it's safe and doesn't instantiate), with instantiation only as a secondary check if absolutely needed.

Evidence

# src/cleveragents/infrastructure/plugins/loader.py, lines 171–206

@staticmethod
def validate_protocol(klass: type[Any], protocol: type[Any]) -> bool:
    # Try instance check first (most reliable for runtime_checkable)
    try:
        instance = klass()      # ← instantiates the class — can execute arbitrary code
        if isinstance(instance, protocol):
            return True
    except Exception:           # ← swallows ALL exceptions silently
        # If instantiation fails, try subclass check
        try:
            if issubclass(klass, protocol):  # ← this should be tried FIRST
                return True
        except TypeError:
            pass
    # ...

Expected Behavior

Protocol validation should use structural type checking (issubclass) that does NOT instantiate the class, or should only instantiate classes that have already passed a security review/allowlist check.

Actual Behavior

validate_protocol() instantiates plugin classes as the primary validation strategy, executing constructor code of potentially untrusted plugins.

Suggested Fix

Reverse the order — try issubclass first, only instantiate if the protocol requires runtime checking:

@staticmethod
def validate_protocol(klass, protocol):
    # Try structural check first (no instantiation)
    try:
        if issubclass(klass, protocol):
            return True
    except TypeError:
        pass
    # Only if structural check is insufficient, try runtime isinstance
    # with a timeout/sandbox if possible
    raise ProtocolMismatchError(f"Class '{klass.__name__}' does not satisfy protocol")

Category

security

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD with tags: @tdd_issue, @tdd_issue_, @tdd_expected_fail.


Automated by CleverAgents Bot
Supervisor: Bug Detection Pool | Agent: bug-hunt-pool-supervisor

## Bug Report: Security — Arbitrary Code Execution During Plugin Protocol Validation ### Severity Assessment - **Impact**: The `validate_protocol()` method instantiates plugin classes by calling `klass()` with no arguments as part of protocol validation. A malicious or buggy plugin constructor can execute arbitrary code (file I/O, network calls, subprocess execution) during what should be a safe structural check. This is especially dangerous because `validate_protocol()` is called before the plugin is confirmed to be safe. - **Likelihood**: Medium — requires a malicious plugin to be installed in the `cleveragents.plugins` entry-point group, but entry-point discovery loads ALL registered plugins - **Priority**: High (security) ### Location - **File**: `src/cleveragents/infrastructure/plugins/loader.py` - **Function**: `PluginLoader.validate_protocol()` - **Lines**: 171–206 ### Description The `validate_protocol()` static method attempts to create an instance of the plugin class to validate it against a protocol: ```python @staticmethod def validate_protocol(klass: type[Any], protocol: type[Any]) -> bool: try: instance = klass() # ← DANGEROUS: instantiates unknown class! if isinstance(instance, protocol): return True except Exception: # If instantiation fails, try subclass check try: if issubclass(klass, protocol): return True except TypeError: pass ``` This means: 1. Any class discovered via entry points will be instantiated during validation 2. The constructor of that class executes in the calling process with full permissions 3. If the plugin constructor makes network connections, spawns processes, reads files, or makes destructive changes — all of this happens before the plugin is confirmed safe 4. The `except Exception: pass` silently swallows ALL constructor exceptions, which could hide serious problems (e.g., resource exhaustion, persistent side effects) The fallback `issubclass` check should always be tried first (it's safe and doesn't instantiate), with instantiation only as a secondary check if absolutely needed. ### Evidence ```python # src/cleveragents/infrastructure/plugins/loader.py, lines 171–206 @staticmethod def validate_protocol(klass: type[Any], protocol: type[Any]) -> bool: # Try instance check first (most reliable for runtime_checkable) try: instance = klass() # ← instantiates the class — can execute arbitrary code if isinstance(instance, protocol): return True except Exception: # ← swallows ALL exceptions silently # If instantiation fails, try subclass check try: if issubclass(klass, protocol): # ← this should be tried FIRST return True except TypeError: pass # ... ``` ### Expected Behavior Protocol validation should use structural type checking (`issubclass`) that does NOT instantiate the class, or should only instantiate classes that have already passed a security review/allowlist check. ### Actual Behavior `validate_protocol()` instantiates plugin classes as the primary validation strategy, executing constructor code of potentially untrusted plugins. ### Suggested Fix Reverse the order — try `issubclass` first, only instantiate if the protocol requires runtime checking: ```python @staticmethod def validate_protocol(klass, protocol): # Try structural check first (no instantiation) try: if issubclass(klass, protocol): return True except TypeError: pass # Only if structural check is insufficient, try runtime isinstance # with a timeout/sandbox if possible raise ProtocolMismatchError(f"Class '{klass.__name__}' does not satisfy protocol") ``` ### Category security ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD with tags: @tdd_issue, @tdd_issue_<this-issue-number>, @tdd_expected_fail. --- **Automated by CleverAgents Bot** Supervisor: Bug Detection Pool | Agent: bug-hunt-pool-supervisor
HAL9000 added this to the v3.6.0 milestone 2026-04-10 19:36:15 +00:00
HAL9000 self-assigned this 2026-04-11 03:21:08 +00:00
Author
Owner

Verified — Critical security bug: PluginLoader can execute arbitrary code during validation. MoSCoW: Must-have. Priority: Critical — remote code execution risk.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Critical security bug: PluginLoader can execute arbitrary code during validation. MoSCoW: Must-have. Priority: Critical — remote code execution risk. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Critical security bug: PluginLoader can execute arbitrary code during validation. MoSCoW: Must-have. Priority: Critical — remote code execution risk.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Critical security bug: PluginLoader can execute arbitrary code during validation. MoSCoW: Must-have. Priority: Critical — remote code execution risk. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Critical security bug: PluginLoader can execute arbitrary code during validation. MoSCoW: Must-have. Priority: Critical — remote code execution risk.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Critical security bug: PluginLoader can execute arbitrary code during validation. MoSCoW: Must-have. Priority: Critical — remote code execution risk. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
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#7418
No description provided.