Shell command injection via unsanitized package_name/url in Android driver and MCP tools
- Dominant language
- Python
- Stars
- 5.9k
- Forks
- 516
- Avg merge
- 22m
- Merged PRs (30d)
- 5
Description
### Summary
`AndroidAdbDriver` builds `adb shell` command strings by directly f-string
interpolating caller-supplied values (`package_name`, `url`) with no
validation or escaping. `adb shell ""` is parsed by the **device's own
shell**, so any shell metacharacters in these values (`;`, `` ` ``, `$()`,
`&&`, ...) are executed on the connected Android device. This is reachable
directly from the MCP server tools (`launch_app`, `stop_app`) that IDE-based
AI agents (Claude Code, Cursor, Antigravity, Windsurf, per the README's MCP
setup) call with arbitrary string arguments — no sanitization happens
anywhere between the MCP tool boundary and the shell call.
### Affected code
**1. `artemis/mcp/adb_server.py` — `launch_app` / `stop_app` MCP tools**
```python
@mcp.tool()
async def launch_app(ctx: Context, package_name: str) -> str:
"""Launches an application by its Android package name with retries and smart polling."""
...
success, error_msg = await launch_app_with_retries(controller.ctx, package_name)
...
@mcp.tool()
async def stop_app(ctx: Context, package_name: str) -> str:
"""Force stops an application by its Android package name."""
...
success = await controller.terminate_app(package_name)
```
`package_name` is passed straight through — through `launch_app_with_retries`
/ `UnifiedMobileController` — to the driver below, with no validation at any
layer.
**2. `artemis/drivers/android/adb_driver.py` — `AndroidAdbDriver.launch_app` / `stop_app`**
```python
async def launch_app(self, package_name: str) -> bool:
try:
cmd = f"monkey -p {package_name} -c android.intent.category.LAUNCHER 1"
await asyncio.to_thread(self.device.shell, cmd)
return True
...
async def stop_app(self, package_name: str) -> bool:
try:
await asyncio.to_thread(self.device.shell, f"am force-stop {package_name}")
return True
...
```
**3. `artemis/controllers/unified_controller.py` — `open_url` (same root cause)**
```python
async def open_url(self, url: str) -> bool:
await self._driver.execute_shell(f"am start -a android.intent.action.VIEW -d '{url}'")
return True
```
This wraps `url` in single quotes but does not escape embedded `'`
characters, so a URL containing a single quote breaks out of the quoting.
Note that `find_package()` in `artemis/tools/mobile/launch_app.py` *does*
validate a resolved package name against the device's installed package list
before use — but that check only exists on the LangChain agent tool path.
The MCP server path (`adb_server.py`) and the driver methods themselves have
no equivalent guard, so the validation is inconsistently applied rather than
structurally guaranteed.
### Proof of concept
Calling the MCP `launch_app` tool with:
```
package_name = "com.android.settings; reboot"
```
produces the on-device shell command:
```
monkey -p com.android.settings; reboot -c android.intent.category.LAUNCHER 1
```
which reboots the device. Similarly, `open_url` with:
```
url = "http://x'; reboot; echo '"
```
produces:
```
am start -a android.intent.action.VIEW -d 'http://x'; reboot; echo ''
```
### Impact
Arbitrary command execution on any Android device/emulator connected to
Artemis, triggerable through the exact MCP surface the project advertises
for letting AI coding agents drive real devices. If an agent is processing
untrusted content (a webpage, file, or ticket text) and that content
influences a tool argument, this becomes a remote code execution path onto
physical hardware, not just a local misuse case.
### Suggested fix
- Validate `package_name` against Android's package-name grammar
(`^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$`) before it is ever
interpolated into a shell string, in the driver methods that are the
common choke point for both the MCP and LangChain call paths.
- Use `shlex.quote()` for `url` in `open_url` instead of manual single-quote
wrapping.
A patch implementing both fixes is attached (`fix-shell-injection.patch`).
### Environment
- Repo: `Abdullah-Builds/artemis` (fork of `google/artemis`)
- Files: `artemis/mcp/adb_server.py`, `artemis/drivers/android/adb_driver.py`,
`artemis/controllers/unified_controller.py`
Contributor guide
Research direction
Read artemis/drivers/android/adb_driver.py and artemis/controllers/unified_controller.py first, then trace the MCP entry points in artemis/mcp/adb_server.py. Ensure package_name is validated at the shared driver boundary and that open_url safely handles embedded quotes; done means the MCP and controller paths no longer allow shell metacharacters to execute on the device.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, python
- Domain
- api, mobile-dev, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100