Hidden From the Model, Open to the Caller: MoviePilot RCE and IntentKit’s Unadvertised Wallet

Two CVE records published within a minute of each other on 15 October 2026 — CVE-2026-77931 at 02:20:16 UTC and CVE-2026-76066 at 02:20:15 UTC, both with VulnCheck as CNA and both status Received — describe the same architectural failure from opposite ends. In MoviePilot, a tool the model should never have been able to misuse is always available and carries its own safety switch, which the model itself may flip. In IntentKit, tools the model was never shown remain callable by anyone who names them, including unauthenticated callers. One gate fails because the guard hands the key to the party being guarded; the other fails because hiding something from a list was mistaken for forbidding it.

MoviePilot: the model held the safety switch

CVE-2026-77931 (CVSS 8.8: AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H) hits jxxghp/MoviePilot through 3.1.5 (11,878 stars, GPL-3.0), the media-library automation suite whose AI assistant browses the web, searches, and parses torrent metadata. Those content tools — browse_webpage, search_web, torrent and media metadata parsers — feed untrusted external text into the conversation. And the same conversation always includes execute_command, a tool that runs arbitrary OS commands on the host via asyncio.create_subprocess_shell, in the administrator’s session.

The chain, as documented in the Lasso Security analysis (file-level detail, quoted here as the record gives it): instructions hidden in content the assistant reads make the model call execute_command with confirm_dangerous set to true. That flag — the thing standing between a language model processing attacker-authored web pages and a shell on the host — appears in the LLM-facing tool schema and can be set by the model in its own tool call, with no human confirmation step. It bypasses the denylist in validate_command_safety (app/agent/tools/impl/_command_safety.py). Because the session already belongs to an administrator (_is_system_admin_context in app/agent/__init__.py sets is_admin=True for background and model-originated web calls), the command runs with the MoviePilot process’s privileges — media library, mounted storage, sometimes root.

Three design decisions compound, and each is worth stating separately because each is a reusable mistake:

  • The dangerous tool is unconditionally included via TOOL_SELECTOR_ALWAYS_INCLUDE_NAMES in app/agent/factory.py — available regardless of context, including contexts whose entire input is attacker-controlled.
  • The safety override is model-writable: confirm_dangerous is a parameter of the tool call, not a separate human-approval channel.
  • The content-ingestion tools are not subject to the same admin gate as execute_command, yet they share its conversation. The untrusted input and the shell meet inside the context window, which is exactly the meeting this publication keeps documenting.

Note the CVSS subtlety: PR:N with UI:R — no privileges required, but some user interaction. Here “interaction” means an administrator asking the assistant to process attacker-controlled content, which is the product’s normal operation, not an unusual click. Any deployment where the assistant reads the web on an admin’s behalf should be treated as exposed, with no access to the instance and no non-default configuration required of the attacker.

IntentKit: visibility is not authorisation

CVE-2026-76066 (CVSS 7.5: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N) hits crestalnetwork/intentkit through 2.6.3 (6,519 stars, MIT), the open-source framework for autonomous agents with built-in crypto-wallet, DeFi, social-posting and memory tools. Each tool can be marked public or private; private tools are meant for the agent’s owner only. That restriction is applied to the tool advertisement — the list shown to the model, enforced by ToolBindingMiddleware — but the executor validates incoming tool calls against the full tool registry. A call that names a de-advertised tool still runs.

The consequence list in the record is unusually concrete: owner-only state-changing tools — wallet transfer, posting, memory-write, call-agent — execute without any human-in-the-loop or approval gate. An unauthenticated or public caller in WEB, API, TWITTER or TELEGRAM contexts can exploit it by supplying the exact de-advertised tool name and arguments, either via indirect prompt injection or by replaying checkpoint and history data on a reused thread. Dwell on that last vector: the authorisation decision was made at advertisement time, but the recorded history of a prior authorised session can be replayed as the credential for a new one. And the sharpest detail: the always-present wallet transfer tool is in the callable set. The thing that moves money was hidden from the menu but left connected to the till.

The 7.5 vector deserves a careful read rather than a dismissal for missing confidentiality impact (VC:N). The integrity impact is High with no privileges and no interaction over the network, in four exposed contexts including two public social surfaces. For a framework whose headline tools move assets and publish speech, integrity is the asset.

The same gate, failed in opposite directions

Put the two records side by side and the symmetry is exact. MoviePilot enforced its safety property at the tool-call layer (a flag on the invocation) but let the untrusted party — the model, processing attacker content — set the flag. IntentKit enforced its safety property at the advertisement layer (which tools the model sees) but never re-checked at the execution layer. In both cases there are two layers and the check lives in exactly one of them, and the attacker’s path goes through the other.

This is the same lesson as Roo Code’s five approval bypasses (the gate and the shell parse different languages) and agno’s confirmation-gate gaps, and it sharpens the general rule: a policy that is evaluated anywhere other than the point of execution is a hint, not a control. Advertisement is UX. A model-set confirmation flag is a suggestion box. Authorisation happens where the effect happens — in the executor, against the caller’s actual authority, on every call — or it does not happen at all.

Note also what neither record claims. Neither the NVD text nor the Lasso analysis claims the underlying models misbehaved; in both cases the model did approximately what the system invited it to do. MoviePilot’s model used a documented parameter for its documented purpose. IntentKit’s executor ran a registered tool it was asked to run. The defects are in neither the model nor the tool but in the missing binding between the authority model and the execution path — which is why “better models” fixes neither of them.

Remediation and scoping

Neither NVD record names a fixed version; both describe the flaw as present through the stated version (MoviePilot 3.1.5, IntentKit 2.6.3). Until upstream releases land, treat both as open. For MoviePilot: remove or gate execute_command out of any assistant context that ingests untrusted content, run the process as an unprivileged user without mount or host access, and scope past exposure by the admin-session property — any web page, torrent listing or metadata entry processed by the assistant since the tool’s introduction is a candidate carrier. For IntentKit: enforce the narrowed tool set in the executor, not just the middleware — the fix is a one-sentence invariant (the executor may only run tools in the request’s advertised set) — and audit history for executions of tools no session should have advertised, particularly wallet and posting tools, since checkpoint replay leaves exactly those traces.

For your own agent code, the two-question review this pair suggests: can the party being constrained alter the constraint’s inputs? (MoviePilot: yes, the flag was in the tool schema) and is the constraint re-evaluated where the effect occurs? (IntentKit: no, advertisement is not execution). If either answer is wrong, you have one of these two CVEs with different names.

Reporting note: CVE IDs, publication timestamps (02:20:15/16 UTC, 15 October 2026), CVSS 3.1 and 4.0 vectors, CNA attribution (VulnCheck) and Received status were read first-hand from the NVD 2.0 API. Titles, severity assessments, affected files, root-cause analysis and impact statements were read first-hand from the linked Lasso Security vulnerability-database pages. Repository metadata (stars, forks, licences, push dates) was read first-hand from the GitHub repository API. No fixed versions are named in either record as of writing, and none is claimed here. Nothing was installed, no MoviePilot or IntentKit instance was deployed, no tool was invoked, no prompt-injection content was crafted, and no payload was executed. Brave Search returned only CVE-mirror aggregators besides the Lasso analyses; no vendor advisory or independent researcher writeup was available at the time of writing.

Sources: