mcp-android-emulator 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SECURITY.md ADDED
@@ -0,0 +1,43 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you discover a security issue in `mcp-android-emulator`, please report it
6
+ privately to the maintainer via GitHub Security Advisories:
7
+
8
+ <https://github.com/Anjos2/mcp-android-emulator/security/advisories/new>
9
+
10
+ Please include:
11
+
12
+ - A description of the issue and its impact.
13
+ - Steps to reproduce, ideally without a weaponized payload.
14
+ - Affected tool(s), file(s), and line number(s) if known.
15
+ - Your preferred contact for follow-up.
16
+
17
+ We aim to acknowledge reports within **72 hours** and publish a fix within
18
+ **14 days** for critical issues when a remediation path is clear.
19
+
20
+ ## Scope
21
+
22
+ The MCP server runs **with the privileges of the user that launched it**
23
+ (typically your Claude Code / Claude Desktop process). A vulnerability in
24
+ this package may allow code execution on the user's machine through any
25
+ LLM that is authorised to call its tools. Please treat that blast radius
26
+ when assessing severity.
27
+
28
+ ## Supported Versions
29
+
30
+ | Version | Supported |
31
+ |---------|-----------|
32
+ | 2.x | ✅ |
33
+ | 1.x | ❌ (superseded by 2.0.0; please upgrade) |
34
+
35
+ ## Disclosure Timeline
36
+
37
+ We follow a coordinated disclosure model:
38
+
39
+ 1. Reporter submits advisory privately.
40
+ 2. Maintainer acknowledges, triages, and proposes a timeline.
41
+ 3. Fix is developed and tested on a private branch.
42
+ 4. A release is published, followed by the advisory going public.
43
+ 5. Reporter is credited unless they request otherwise.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Runner seguro para comandos adb.
3
+ *
4
+ * Finalidad:
5
+ * Centraliza toda interacción con el binario adb usando execFile (NO exec),
6
+ * lo que elimina la interpretación de metacaracteres shell en el HOST.
7
+ * El host queda protegido aunque se pasen argumentos con ';', '|', '`', etc.
8
+ *
9
+ * Nota sobre adb shell:
10
+ * Cuando se usa 'adb shell', adb concatena los argv con espacios y los
11
+ * entrega al /system/bin/sh del device. El sh del device SÍ reinterpreta
12
+ * metacaracteres. Por lo tanto, la defensa en profundidad exige que los
13
+ * argumentos vengan validados (allowlist) por src/adb/validators.ts antes
14
+ * de llegar aquí.
15
+ *
16
+ * Interrelación:
17
+ * - Usado por src/index.ts (todas las tools migradas).
18
+ * - Complementado por src/adb/validators.ts (allowlists zod).
19
+ * - Config externa: variable de entorno ADB_PATH.
20
+ */
21
+ export interface RunOptions {
22
+ /** Timeout en ms; default 30s. 0 o negativo = sin timeout. */
23
+ timeoutMs?: number;
24
+ /** Buffer máximo de stdout/stderr en bytes; default 10 MB. */
25
+ maxBufferBytes?: number;
26
+ }
27
+ /**
28
+ * Ejecuta `adb <args...>` sin pasar por shell del host.
29
+ * Cada elemento de args llega como argumento separado al binario adb.
30
+ */
31
+ export declare function runAdb(args: string[], opts?: RunOptions): Promise<string>;
32
+ /**
33
+ * Ejecuta `adb shell <argv...>`. Los tokens serán re-ensamblados por adb y
34
+ * pasados al sh del device. Los argumentos DEBEN estar validados contra
35
+ * shell metacharacters antes de invocar esta función (ver validators.ts).
36
+ */
37
+ export declare function runAdbShell(argv: string[], opts?: RunOptions): Promise<string>;
38
+ /**
39
+ * Ejecuta `adb exec-out <argv...>`. Útil para obtener bytes binarios sin
40
+ * transformación (screencap, pull de archivos). Los argumentos DEBEN estar
41
+ * validados. Devuelve un Buffer.
42
+ */
43
+ export declare function runAdbExecOutBinary(argv: string[], opts?: RunOptions): Promise<Buffer>;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Runner seguro para comandos adb.
3
+ *
4
+ * Finalidad:
5
+ * Centraliza toda interacción con el binario adb usando execFile (NO exec),
6
+ * lo que elimina la interpretación de metacaracteres shell en el HOST.
7
+ * El host queda protegido aunque se pasen argumentos con ';', '|', '`', etc.
8
+ *
9
+ * Nota sobre adb shell:
10
+ * Cuando se usa 'adb shell', adb concatena los argv con espacios y los
11
+ * entrega al /system/bin/sh del device. El sh del device SÍ reinterpreta
12
+ * metacaracteres. Por lo tanto, la defensa en profundidad exige que los
13
+ * argumentos vengan validados (allowlist) por src/adb/validators.ts antes
14
+ * de llegar aquí.
15
+ *
16
+ * Interrelación:
17
+ * - Usado por src/index.ts (todas las tools migradas).
18
+ * - Complementado por src/adb/validators.ts (allowlists zod).
19
+ * - Config externa: variable de entorno ADB_PATH.
20
+ */
21
+ import { execFile } from "node:child_process";
22
+ import { promisify } from "node:util";
23
+ const execFileAsync = promisify(execFile);
24
+ const ADB_PATH = process.env.ADB_PATH || "adb";
25
+ const DEFAULT_TIMEOUT_MS = 30_000;
26
+ const DEFAULT_MAX_BUFFER = 10 * 1024 * 1024;
27
+ function normalizeOpts(opts) {
28
+ const t = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
29
+ return {
30
+ timeout: t > 0 ? t : 0,
31
+ maxBuffer: opts.maxBufferBytes ?? DEFAULT_MAX_BUFFER,
32
+ };
33
+ }
34
+ /**
35
+ * Ejecuta `adb <args...>` sin pasar por shell del host.
36
+ * Cada elemento de args llega como argumento separado al binario adb.
37
+ */
38
+ export async function runAdb(args, opts = {}) {
39
+ const { timeout, maxBuffer } = normalizeOpts(opts);
40
+ try {
41
+ const { stdout } = await execFileAsync(ADB_PATH, args, { timeout, maxBuffer });
42
+ return stdout.trim();
43
+ }
44
+ catch (error) {
45
+ throw wrapAdbError(error);
46
+ }
47
+ }
48
+ /**
49
+ * Ejecuta `adb shell <argv...>`. Los tokens serán re-ensamblados por adb y
50
+ * pasados al sh del device. Los argumentos DEBEN estar validados contra
51
+ * shell metacharacters antes de invocar esta función (ver validators.ts).
52
+ */
53
+ export async function runAdbShell(argv, opts = {}) {
54
+ if (argv.length === 0) {
55
+ throw new Error("runAdbShell requires at least one argument");
56
+ }
57
+ return runAdb(["shell", ...argv], opts);
58
+ }
59
+ /**
60
+ * Ejecuta `adb exec-out <argv...>`. Útil para obtener bytes binarios sin
61
+ * transformación (screencap, pull de archivos). Los argumentos DEBEN estar
62
+ * validados. Devuelve un Buffer.
63
+ */
64
+ export async function runAdbExecOutBinary(argv, opts = {}) {
65
+ if (argv.length === 0) {
66
+ throw new Error("runAdbExecOutBinary requires at least one argument");
67
+ }
68
+ const { timeout, maxBuffer } = normalizeOpts(opts);
69
+ return new Promise((resolve, reject) => {
70
+ execFile(ADB_PATH, ["exec-out", ...argv], { timeout, maxBuffer, encoding: "buffer" }, (error, stdout) => {
71
+ if (error) {
72
+ reject(wrapAdbError(error));
73
+ return;
74
+ }
75
+ resolve(stdout);
76
+ });
77
+ });
78
+ }
79
+ function wrapAdbError(error) {
80
+ if (error instanceof Error) {
81
+ // @ts-expect-error — stderr no es estándar pero lo añade child_process
82
+ const stderr = error.stderr;
83
+ const msg = stderr?.toString?.().trim() || error.message;
84
+ return new Error(`adb error: ${msg}`);
85
+ }
86
+ return new Error(`adb error: ${String(error)}`);
87
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Validators zod — allowlists estrictas para todo input que eventualmente
3
+ * llegará a `adb shell` (donde el sh del device reinterpreta metacaracteres).
4
+ *
5
+ * Finalidad:
6
+ * Actuar como segunda línea de defensa sobre src/adb/runner.ts. El runner
7
+ * protege el host (no invoca sh local); estos validators protegen el device
8
+ * (bloquean metacaracteres antes de llegar al sh del device).
9
+ *
10
+ * Interrelación:
11
+ * - Usado por los schemas de tool en src/index.ts.
12
+ * - Combinable con z.object() normal de la librería zod.
13
+ */
14
+ import { z } from "zod";
15
+ /**
16
+ * Regex para metacaracteres shell peligrosos.
17
+ * Usado para rechazo en allowlists que admiten ciertos caracteres
18
+ * pero quieren excluir los más peligrosos explícitamente.
19
+ */
20
+ export declare const SHELL_METACHARS: RegExp;
21
+ export declare const packageNameSchema: z.ZodString;
22
+ export declare const apkPathSchema: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
23
+ export declare const resourceIdSchema: z.ZodString;
24
+ export declare const freeTextSchema: z.ZodEffects<z.ZodString, string, string>;
25
+ export declare const typeableTextSchema: z.ZodEffects<z.ZodString, string, string>;
26
+ export declare const searchFilterSchema: z.ZodEffects<z.ZodString, string, string>;
27
+ export declare const positiveCountSchema: z.ZodNumber;
28
+ export declare const coordinateSchema: z.ZodNumber;
29
+ export declare const durationMsSchema: z.ZodNumber;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Validators zod — allowlists estrictas para todo input que eventualmente
3
+ * llegará a `adb shell` (donde el sh del device reinterpreta metacaracteres).
4
+ *
5
+ * Finalidad:
6
+ * Actuar como segunda línea de defensa sobre src/adb/runner.ts. El runner
7
+ * protege el host (no invoca sh local); estos validators protegen el device
8
+ * (bloquean metacaracteres antes de llegar al sh del device).
9
+ *
10
+ * Interrelación:
11
+ * - Usado por los schemas de tool en src/index.ts.
12
+ * - Combinable con z.object() normal de la librería zod.
13
+ */
14
+ import { z } from "zod";
15
+ /**
16
+ * Regex para metacaracteres shell peligrosos.
17
+ * Usado para rechazo en allowlists que admiten ciertos caracteres
18
+ * pero quieren excluir los más peligrosos explícitamente.
19
+ */
20
+ export const SHELL_METACHARS = /[;&|`$()<>\\"'\n\r\t\x00-\x1f]/;
21
+ // ---------------------------------------------------------------------------
22
+ // Android package name
23
+ // Formato Java-compatible: segmentos separados por puntos, cada segmento
24
+ // empieza con letra o underscore, continúa con alfanumérico/underscore.
25
+ // Mínimo dos segmentos (no hay package top-level sin punto en Android real).
26
+ // ---------------------------------------------------------------------------
27
+ const PACKAGE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)+$/;
28
+ export const packageNameSchema = z
29
+ .string()
30
+ .min(3)
31
+ .max(255)
32
+ .regex(PACKAGE_NAME_REGEX, "Invalid Android package name");
33
+ // ---------------------------------------------------------------------------
34
+ // APK path
35
+ // Rechaza metacaracteres shell y exige extensión .apk.
36
+ // La existencia del archivo se valida en el handler (no aquí, para que
37
+ // los tests puedan usar paths simulados).
38
+ // ---------------------------------------------------------------------------
39
+ export const apkPathSchema = z
40
+ .string()
41
+ .min(5)
42
+ .max(4096)
43
+ .refine((p) => p.toLowerCase().endsWith(".apk"), {
44
+ message: "Must be a .apk file",
45
+ })
46
+ .refine((p) => !SHELL_METACHARS.test(p), {
47
+ message: "Path contains disallowed characters",
48
+ });
49
+ // ---------------------------------------------------------------------------
50
+ // Android resource-id (e.g. com.app:id/button_login)
51
+ // ---------------------------------------------------------------------------
52
+ const RESOURCE_ID_REGEX = /^[a-zA-Z_][a-zA-Z0-9_.]*:[a-zA-Z]+\/[a-zA-Z_][a-zA-Z0-9_]*$/;
53
+ export const resourceIdSchema = z
54
+ .string()
55
+ .min(3)
56
+ .max(512)
57
+ .regex(RESOURCE_ID_REGEX, "Invalid Android resource-id");
58
+ // ---------------------------------------------------------------------------
59
+ // Texto libre del usuario (filter, search, assert)
60
+ // Este texto NO llega al shell: se usa en JavaScript (String.includes,
61
+ // RegExp.source con escape). Aceptamos caracteres amplios pero limitamos
62
+ // longitud y prohibimos control characters para evitar sorpresas.
63
+ // ---------------------------------------------------------------------------
64
+ const CONTROL_CHARS = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
65
+ export const freeTextSchema = z
66
+ .string()
67
+ .max(1024)
68
+ .refine((s) => !CONTROL_CHARS.test(s), {
69
+ message: "Text contains control characters",
70
+ });
71
+ // ---------------------------------------------------------------------------
72
+ // Texto que se va a tipear en un input Android (type_text, set_text).
73
+ // Este texto PASA por el shell del device (`input text ...`). Usamos DENY-list
74
+ // (rechazar solo metacaracteres shell y caracteres de control), NO allow-list,
75
+ // para no romper i18n: acentos, ñ, CJK y emoji son válidos en inputs reales
76
+ // de aplicaciones. Mismo nivel de seguridad que una allow-list ASCII porque
77
+ // los shell metachars son el único vector de inyección.
78
+ // ---------------------------------------------------------------------------
79
+ export const typeableTextSchema = z
80
+ .string()
81
+ .min(0)
82
+ .max(2048)
83
+ .refine((s) => !SHELL_METACHARS.test(s), {
84
+ message: "Text contains shell metacharacters (; & | ` $ ( ) < > \\ \" ' or control chars). Use key events for these.",
85
+ });
86
+ // ---------------------------------------------------------------------------
87
+ // Filter para list_packages / get_logs — usado en JS, no en shell.
88
+ // ---------------------------------------------------------------------------
89
+ export const searchFilterSchema = z
90
+ .string()
91
+ .max(256)
92
+ .refine((s) => !CONTROL_CHARS.test(s), {
93
+ message: "Filter contains control characters",
94
+ });
95
+ // ---------------------------------------------------------------------------
96
+ // Conteo numérico sanitizado (lines en get_logs, etc).
97
+ // ---------------------------------------------------------------------------
98
+ export const positiveCountSchema = z
99
+ .number()
100
+ .int()
101
+ .positive()
102
+ .max(100_000);
103
+ // ---------------------------------------------------------------------------
104
+ // Coordenadas (seguras por tipo, pero clamp a límites razonables).
105
+ // ---------------------------------------------------------------------------
106
+ export const coordinateSchema = z.number().int().min(0).max(100_000);
107
+ // ---------------------------------------------------------------------------
108
+ // Duración en ms.
109
+ // ---------------------------------------------------------------------------
110
+ export const durationMsSchema = z.number().int().nonnegative().max(600_000);
package/dist/index.d.ts CHANGED
@@ -1,7 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * MCP Server for Android Emulator
4
- * Enables AI assistants to interact with Android devices/emulators via ADB
3
+ * MCP Server for Android Emulator.
4
+ *
5
+ * Finalidad:
6
+ * Expone 43 tools MCP que permiten a un asistente LLM controlar un device
7
+ * Android vía ADB (screenshot, tap, type, launch apps, logs, asserts...).
8
+ *
9
+ * Interrelación:
10
+ * - src/adb/runner.ts → ejecución segura de adb (execFile, sin shell del host).
11
+ * - src/adb/validators.ts → allowlists zod para inputs que llegan al sh del device.
12
+ * - test/ → smoke tests que validan que payloads shell-metachar son
13
+ * rechazados por los validators y que los argv construidos
14
+ * son los esperados.
15
+ *
16
+ * Seguridad:
17
+ * Fix de la issue #1 (command injection). TODOS los argumentos derivados del
18
+ * LLM pasan por zod.refine antes de llegar al runner, y el runner usa execFile
19
+ * (no exec), por lo que /bin/sh del host nunca reinterpreta la línea de comando.
5
20
  *
6
21
  * @license MIT
7
22
  */