Back to  vulnerability database

CVE-2026-77931: Confused-Deputy Remote Code Execution via Prompt Injection into Always-Available Command Execution Tool

Sub A critical vulnerability in MaxKB may allow remote attackers to execute system commands on affected servers.

CVSS Score
8.8
Severity
High
Type
Indirect prompt injection to RCE
Attack type
Indirect prompt injection to RCE
Vendor/tech
jxxghp/MoviePilot
Know exploit
No
Impact Assessment
Confidentiality
High
Integrity
High
Availability
High

Overview

MoviePilot's AI assistant always offers the model an execute_command tool that runs OS commands directly on the host, and it lets the model itself set the confirm_dangerous flag that skips the command safety denylist.

The assistant's content tools (browse_webpage, search_web, and the torrent and media metadata tools) feed untrusted external text into the same conversation. When an administrator asks the assistant to process attacker-controlled content, instructions hidden in that content can make the model call execute_command.

‍

Because the session already belongs to an administrator, the command runs on the host with the privileges of the MoviePilot process, which often has access to the media library and mounted storage and sometimes runs as root.

‍

Because any attacker who can get a web page, torrent listing or media metadata entry in front of a MoviePilot administrator's assistant can make it run commands on the host, with no access to the instance and no non-default configuration, affected deployments should be treated as high priority.

‍

Affected Product

jxxghp/MoviePilot

Affected Files

  • app/agent/tools/impl/execute_command.py (~line 410) - asyncio.create_subprocess_shell, confirm_dangerous LLM-settable parameter, full start/run/read/write/kill dispatcher
  • app/agent/tools/impl/_command_safety.py - validate_command_safety denylist, bypassable via confirm_dangerous=True
  • app/agent/factory.py - TOOL_SELECTOR_ALWAYS_INCLUDE_NAMES (tool always offered to agent regardless of context)
  • app/agent/__init__.py:629 - _is_system_admin_context, sets is_admin=True for background and OpenAI/Anthropic-originated web calls
  • app/api/endpoints/agent.py:246-257 - POST /api/v1/agent/stream, sets is_admin = current_user.is_superuser

Root Cause

‍

ExecuteCommandTool (require_admin=True) runs arbitrary OS commands on the host via asyncio.create_subprocess_shell with no isolation boundary, and is unconditionally included in the agent's tool set via TOOL_SELECTOR_ALWAYS_INCLUDE_NAMES.

‍

The tool accepts a LLM-settable confirm_dangerous boolean that bypasses the denylist in validate_command_safety when set to True - the flag appears in the LLM-facing tool schema and can be set by the model in its tool call without any human confirmation step. Content ingestion tools (browse_webpage, search_web, and torrent/media metadata tools) are not subject to the same admin gate as execute_command and feed untrusted external text directly into the model's context.

‍

When an administrator uses the assistant to process attacker-controlled content, that content can instruct the model to invoke execute_command; because the admin session already satisfies the is_admin=True permission check, the command executes on the host.

PoC

‍

Reproduced against a verbatim reconstruction of the vulnerable code (importing the real ExecuteCommandTool pulls in MoviePilot's entire runtime, so the load-bearing lines were copied verbatim, exercising the identical primitive and guard as the upstream source):

‍

1. Scripted, deterministic run: reproduced the permission matrix exactly as described (non-admin denied, admin allowed), then executed a benign id canary on the host via the verbatim asyncio.create_subprocess_shell path. Result: the command ran and returned the host user's id output.

‍

2. Real-model run (backed by a live Claude model): the model was given the real execute_command tool schema plus a poisoned page that an "admin" asked it to summarize. The model chose to call execute_command with command="id", which executed on the host, confirming the full injection-to-RCE path end to end.

‍

Impact

An attacker who can place LLM-targeting instructions in any content the MoviePilot assistant is asked to process (a web page, torrent listing, or media metadata entry) can achieve code execution on the host with the privileges of the MoviePilot process - often a container, sometimes root, with access to the media library and mounted storage. The attack requires no direct access to the MoviePilot instance, no exploitation of authentication, and no non-default configuration.

‍

It requires only that an administrator use the assistant to browse or analyze attacker-controlled content. Because confirm_dangerous is LLM-settable, even commands that would otherwise be blocked by the safety denylist can be executed by the model in the same turn.

‍

Suggested Remediation

1. Make execute_command, write_file, and edit_file opt-in and off by default.

2. Treat any turn that ingested external content as tainted, and require human confirmation before executing commands in that turn.

3. Sandbox execution (non-root, containerized, with no host mounts) instead of running asyncio.create_subprocess_shell directly on the host.

4. Use an allowlist or an out-of-band confirmation mechanism, not an LLM-settable confirm_dangerous flag.

Additional Resources

  • CVE-2026-77931 record

Automated AI Red Teaming

Test your applications against adversarial conditions.
Book a Demo