Security findings 4
Privilege escalation
mediumOverreachConfirmed on reviewmedium confidenceThe auto-install flow silently installs the bundled companion APK with all runtime permissions granted (-g) and enables its AccessibilityService on the user's device without asking, which takes device authority without the user's consent.
Enables the AccessibilityService via `adb shell settings put`
vector:code
Reviewer's reasoning
The documented auto-install enables the companion's AccessibilityService via adb settings put without any user confirmation, which takes device authority the user did not grant. The companion is the artifact's own app, but enabling accessibility bypasses the user's consent step, so test 6 applies.
Privilege escalation
mediumOverreachConfirmed on reviewmedium confidenceThe companion client enables its own Android accessibility service through ADB and installs the APK with permission grants, so the user is not asked to approve the accessibility grant in Settings; this is undeclared standing authority on the user's device.
This avoids the user having to navigate to Settings manually.
vector:code
Reviewer's reasoning
enableService writes the accessibility setting through adb so the user is not asked to approve it in Settings. This bypasses the consent prompt for a significant device permission, which is test 6 overreach.
Privilege escalation
mediumOverreachConfirmed on reviewmedium confidenceThe companion install runs adb install with -g, which grants all runtime permissions to the companion app at install time without the user's review.
["install", "-r", "-g", apkPath]
vector:code
Reviewer's reasoning
The install command passes -g, which silently grants all runtime permissions to the companion app at install time with no user review. This takes device authority without consent, so test 6 applies.
Privilege escalation
mediumOverreachConfirmed on reviewmedium confidenceOn first connect failure, the driver silently installs a companion APK, enables its accessibility service, and forces accessibility_enabled=1 on the device without asking the user, granting the app screen-reading control beyond the declared UI-inspection task.
Auto-installing companion app on
vector:code
Reviewer's reasoning
On first connect failure the driver silently installs the companion APK, enables its accessibility service, and forces accessibility_enabled=1 with no prompt to the user. This grants screen-reading control beyond the user's direct consent, so test 6 applies.
Notes 8
Capabilities, vulnerabilities and disclosed behavior worth knowing. Nothing here works against the person who installs it, so notes never change the verdict.
Code execution
mediumNoteConfirmed on reviewmedium confidenceThe iOS driver's raw shell method runs a caller-supplied command inside the simulator via simctl spawn, split on spaces, with no shell quoting; it is a caller-invoked capability, not a hidden one.
const result = await this.simctl.execute(["spawn", deviceId, ...command.split(" ")]);vector:instruction
Reviewer's reasoning
The shell method runs a caller-supplied command inside the simulator as its declared raw-shell capability, and it needs a caller to supply input; this is a declared core capability, not a hidden one.
Credential access
mediumNoteConfirmed on reviewmedium confidenceThe server reads GEMINI_API_KEY and MCP_AI_API_KEY from the environment, which server.json does not declare, and uses them only as the key for the AI model service the server itself wraps, so this is undeclared operator configuration.
process.env.GEMINI_API_KEY
vector:code
Reviewer's reasoning
The server reads AI provider keys from the operator's environment and uses them only for the AI service it wraps, which is operator-controlled configuration and an API key for the wrapped service.
Credential access
mediumNoteConfirmed on reviewmedium confidenceThe license check reads the MOBILE_MCP_LICENSE_KEY environment variable to set the tier; the read is undeclared but only gates the publisher's own pro tools and sends nothing off-host.
const key = process.env.MOBILE_MCP_LICENSE_KEY;
vector:code
Reviewer's reasoning
The license key environment variable is only read to set the tier for the publisher's own pro tools and nothing is sent off-host, so this is operator-controlled configuration.
Code execution
mediumNoteConfirmed on reviewlow confidenceRecorded tool parameters are embedded inside single-quoted Python source in the generated test script without escaping, so a parameter containing a quote breaks or alters the script the user later runs; a hygiene bug.
arguments=json.loads('${JSON.stringify(action.params)}'),vector:code
Reviewer's reasoning
Unescaped parameters in the generated Python script are a hygiene bug in the user's own recorded test output, which the user chooses to run; no outside input is shown to drive it.
Supply chain
lowNoteConfirmed on reviewmedium confidenceThe smithery launch config runs npx with -y on the unpinned publisher package mobile-device-mcp, so the latest published version is fetched and executed at each start; this is a note because it is the publisher's own package.
args: ["-y", "mobile-device-mcp"],
vector:instruction
Reviewer's reasoning
The launch config runs npx -y on the publisher's own unpinned package, which is the carve-out for the publisher's own package fetched when the user follows its instructions. npm is the official registry, so this is a note.
Privilege escalation
lowNoteConfirmed on reviewmedium confidenceThe handlePopup tool, in its default auto mode, taps the first clickable button of any detected system permission dialog without user confirmation, so the agent grants permissions on the device the user controls.
// Fallback: if auto mode found nothing specific, tap the first clickable button
vector:code
Reviewer's reasoning
The handlePopup tool's auto mode is its declared capability (the doc comment says it can dismiss, accept, or auto-handle), and it runs only when the user's agent invokes it on the device. The blind fallback tap is a reliability flaw in doing the declared job, not a concealed or unconsented action, so it is a note.
Manipulation
lowNoteConfirmed on reviewmedium confidenceThe free-tier upgrade message tells the user to pay for a license and add the key to .mcp.json; this is a publisher upsell shown to the user, who decides.
Add it to your .mcp.json:
vector:instruction
Reviewer's reasoning
The free-tier message is a publisher upsell shown to the user, who decides whether to pay and add the key; it does not steer the agent or act without the user.
Reconnaissance
infoNoteNot verified by reviewlow confidenceThe Flutter driver probes localhost TCP ports in the DDS range 50000-60000 and reads HTTP status codes to locate a live Dart VM service, a discovery step beyond the declared single-app connection, which is a note-level behavior.
// DDS typically runs on ports 50000-60000
vector:code
Third-party flags 1
Behavior aimed at other sites, platforms or people, such as evading rate limits or offensive-security tooling. It does not affect the person who installs it, so it never changes the verdict.
Privilege escalation
lowNoteConfirmed on reviewmedium confidenceThe license signing salt is hardcoded and a key generator ships in the artifact, so anyone can mint pro-tier keys that bypass the publisher's paywall; this affects only the publisher's licensing, not the installer.
const SALT = "m0b1l3-d3v1c3-mcp-2026-s4r4nsh";
vector:code
Reviewer's reasoning
The hardcoded salt and key generator let anyone mint pro keys, but the harm falls only on the publisher's paywall and does not act against the installer.