MoviePilot let the model decide whether a command was dangerous
Agent frameworks keep rediscovering the same boundary: the component that decides whether an action is permitted must not be the component that was persuaded to take it. CVE-2026-77931 in MoviePilot is a compact example, because the flag that was supposed to gate command execution could be set by the content the model was reading.
The affected component
The CVE record describes MoviePilot through version 3.1.5. MoviePilot is a self-hosted media automation tool: it watches torrent feeds, parses metadata, searches the web for releases, and runs a language model assistant on top of those capabilities. It is, in practice, an agent with a database, network access and a shell.
The advisory's technical summary names the tools involved: browse_webpage, search_web, and the parsers that read torrent metadata. Untrusted content reaching any of those tools can carry embedded instructions. The resulting text ends up in ExecuteCommandTool, which calls asyncio.create_subprocess_shell without restrictions.
The published severity is 8.7 under CVSS 4.0 with the vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. User interaction is rated passive, which matches the shape of the attack: nobody has to approve anything, they only have to let the tool ingest a piece of text.
The flag that was not a control
Two mechanisms appear in the advisory, and both are worth separating.
The first is validate_command_safety, a denylist that inspects a command before it runs. A denylist is a blocklist by another name: it recognises the patterns someone thought to enumerate, and it passes everything else.
The second is confirm_dangerous, a parameter exposed to the tool call. The presence of a "confirm dangerous" argument in a tool schema tells the caller, which is the language model, that some commands are dangerous. It does not make the model's judgement of danger independent of the content that shaped its output.
When content injection instructs the model to call execute_command with confirm_dangerous set to true, and the model does so, the confirmation step has been completed by the party that the step was meant to constrain. The advisory states that the denylist is bypassed and that the call executes within the administrator session's is_admin context.
That context detail matters. The subprocess runs as whatever the MoviePilot service runs as, with the privileges the assistant was given. Media servers are frequently deployed in containers with mounted media libraries, download clients and credentials for indexers; those mounts and secrets are inside the blast radius.
Why ingestion tools are the interesting part
The three named tools share a property: they bring text from outside the trust boundary into the model's context. A web page, a search result and a torrent description are all attacker-influenced by design. Torrent metadata in particular is written by whoever published the torrent, which for a public tracker means anyone.
An agent that both reads untrusted text and holds a shell must treat that text as data. The failure here is architectural rather than exotic: the same context window held the instructions and the capability.
This pattern is well understood in LLM application security and needs no novel technique. A torrent description with a paragraph instructing the assistant to run a command is enough, provided the assistant is allowed to act on it.
Deployment realities
MoviePilot installations are typically personal or small-team infrastructure. That reduces the number of perimeters involved and increases the value of each one, because a single host often holds the media library, the download client credentials, and a VPN or proxy configuration.
Remote access is common and often intentional: users want to reach the interface from outside the home network. Every such deployment exposes the assistant's tool surface to the same reachability as the interface it runs behind.
What to do
- Upgrade beyond 3.1.5. The advisory's affected range stops there; check the project's release notes for the fixed build rather than assuming a version number.
- Remove shell access from the assistant if the workflow does not require it. The most effective mitigation for prompt-to-command chains is the absence of the command tool.
- Separate the identity. If command execution must remain, run it in a container that mounts no credentials, has no network route to the media library and cannot reach the download client API. The vulnerability then becomes an inconvenience rather than an incident.
- Move authorisation out of the prompt. A confirmation that the model can supply on its own behalf is not a control. If a human approval step is intended, it has to be enforced by code that receives the tool call and refuses it, not by a parameter in the schema.
- Denylists are not a boundary. Keeping
validate_command_safetyis fine as hygiene; treating it as the gate for a system that ingests arbitrary text is not.
Detection ideas
Process lineage is the most reliable signal. The MoviePilot process should not normally spawn shells, package managers or interpreters. Alerting on child processes of the service, with command lines recorded, surfaces the execution step regardless of how the instruction arrived.
Network egress from the media host is the second signal. A media server has predictable destinations: trackers, indexers, the download client. A connection from that host to an unfamiliar address shortly after a torrent was added is worth an explanation.
Outbound content is the third. Environment variables, configuration files and indexer API keys are all readable by the process. If those credentials are valid elsewhere, the incident is not confined to the media host.
The transferable part
The specific product matters less than the arrangement: text from an untrusted source, a model that can call tools, and an execution capability in the same context. Every self-hosted assistant with a plugin directory has to answer whether a remote page can reach a shell. MoviePilot's answer, before the fix, was yes.
References
- CVE.org record for CVE-2026-77931: https://www.cve.org/CVERecord?id=CVE-2026-77931
- VulnCheck advisory, "MoviePilot Command Injection via ExecuteCommandTool": https://www.vulncheck.com/advisories/moviepilot-command-injection-via-executecommandtool
- Lasso Security vulnerability database entry for CVE-2026-77931: https://www.lasso.security/vulnerability-database/cve-2026-77931
Top comments (0)