system: OPERATIONAL
← back to all hacks
AGENTS MEDIUM NEW

When an MCP tool argument becomes an Android intent: mobile-mcp's injection sinks

CVE-2026-35394 lets a model-controlled URL fire arbitrary Android intents through mobile-mcp's mobile_open_url tool. Paired with a sibling path-traversal CVE, it shows a pattern: MCP tool arguments flowing unvalidated into platform sinks.

2026-06-05 // 6 min affects: mobile-mcp, mobile-mcp-lt-0.0.50, model-agnostic-mcp-clients

In brief mobile-mcp is an MCP server for mobile development and automation. CVE-2026-35394, published in April 2026 (CWE-939, GitHub CNA vector AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H, High), lets a URL handed to the mobile_open_url tool fire arbitrary Android intentstel:, sms:, USSD codes, content: — because the tool never validates the URI scheme. It is fixed in 0.0.50. A sibling bug, CVE-2026-33989 (path traversal, fixed in 0.0.49), is the same class through a different sink. The lesson isn’t one package: it’s that MCP tool arguments are attacker-reachable, and any sink they reach must validate them.

What is this?

Mobile Next’s @mobilenext/mobile-mcp exposes a phone or emulator to an LLM agent as a set of MCP tools — tap, type, screenshot, open a URL. Per the GitHub Security Advisory GHSA-5qhv-x9j4-c3vm, versions prior to 0.0.50 pass the string given to the mobile_open_url tool straight to Android’s intent system with no scheme allowlist. Android resolves far more than http(s): tel: places a call, sms: opens a message, content: reaches a content provider, and dialer codes can trigger USSD actions. So a value the model was free to choose becomes a device action.

The same project shipped CVE-2026-33989 a few days earlier (NVD published 2026-03-27, fixed in 0.0.49): the saveTo and output parameters of the screenshot and screen-recording tools were written to disk without validation, allowing path traversal outside the workspace. Two CVEs, two tools, one root cause — unvalidated tool arguments reaching a powerful sink.

How it works

An MCP tool argument is not “trusted input.” It is whatever the model emitted, and the model’s context routinely contains untrusted text: a web page it browsed, an email it summarised, an issue it triaged. That is the indirect-prompt-injection surface — attacker text upstream, tool call downstream. When the tool forwards that argument into a sink without checking it, the injection lands as a real action.

For mobile_open_url, the dangerous step is the missing scheme check before dispatch:

// Vulnerable shape (< 0.0.50): scheme is never constrained
async function mobile_open_url(url) {
  // url may be tel:, sms:, content:, or a dialer/USSD string —
  // it is resolved by Android's intent system as-is.
  await device.openUrl(url);   // sink: arbitrary intent
}

// Fixed shape (0.0.50): allowlist the scheme first
function assertWebScheme(url) {
  const ok = ["http:", "https:"];
  if (!ok.includes(new URL(url).protocol)) throw new Error("scheme not allowed");
}

The CVSS vector (UI:R) reflects that a human still has to run the agent on the connected device, but no attacker privileges are required (PR:N) and the request is network-reachable (AV:N). The integrity and availability impact is High: the action executes on a real phone. We are not publishing a working chain; the point is the shape of the flaw, not a payload.

Why it matters

MCP’s value is that one agent can drive many tools. Its risk is that each tool independently decides whether to trust its arguments — there is no central mediator that says “this string came from model output, treat it as untrusted.” mobile-mcp happens to sit in front of an unusually powerful sink (a phone’s intent resolver), but the pattern is everywhere: a database MCP that interpolates an argument into SQL, a shell MCP that forwards one into a command, a filesystem MCP that joins one into a path. Each new tool is a fresh chance to reintroduce the class, which is exactly why this project produced two CVEs in two weeks.

The practical danger is a false boundary. Teams reason about “the agent” as the thing to secure and overlook that the MCP server is a confused deputy: it holds device or host privileges and will use them on instructions that ultimately trace back to untrusted content.

Defenses

  1. Upgrade. mobile-mcp ≥ 0.0.50 fixes the intent sink; ≥ 0.0.49 fixes the path-traversal sink. Pin the version and check with npm list @mobilenext/mobile-mcp.
  2. Allowlist at every sink, not just this one. Validate scheme for URL sinks (http/https only), canonicalise and confine paths for filesystem sinks, parameterise for query sinks. Treat each tool argument as untrusted model output.
  3. Run MCP servers with least privilege. Drive a throwaway emulator rather than a personal device; deny telephony/SMS permissions the workflow doesn’t need; isolate filesystem and network so a bad argument can’t reach sensitive data.
  4. Audit your tool surface for the pattern. Grep your own and third-party MCP servers for arguments that flow into openUrl, exec, file writes, or query builders without validation, and gate that check in CI so a future tool can’t reintroduce it.
  5. Log and alert on out-of-band schemes. A tel:, sms:, content:, or non-http(s) value reaching a URL tool — or a write outside the workspace — is a useful hunting signal that an argument was attacker-influenced.

Status

ItemReferenceDateNotes
CVE-2026-35394GHSA-5qhv-x9j4-c3vm2026-04mobile_open_url intent injection, CWE-939, vector C:L/I:H/A:H
Patched versionmobile-mcp 0.0.50Scheme validation added
CVE-2026-33989GHSA-3p2m-h2v6-g9mx2026-03-27 (NVD)Path traversal in screenshot/recording tools, CWE-22/73
Patched versionmobile-mcp 0.0.49Path validation added

The right framing isn’t “patch mobile-mcp.” It’s that every MCP tool argument is reachable by whatever sits in the model’s context, and a tool that forwards one into a privileged sink without validation turns an upstream prompt injection into a downstream action — on a phone, a filesystem, or a database.

Sources