# Microsoft APM CLI's plugin.json component paths escape plugin root and copy arbitrary host files during install

Microsoft APM CLI's plugin.json component paths lacked root-containment checks, letting a malicious plugin copy arbitrary host files into .apm/ and .github/prompts/ during apm install.

- Payout: Research
- Severity: high
- Program: Microsoft
- Found by: 0xmrma
- Disclosed: 2026-05-07
- CVE: CVE-2026-44641
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-xhrw-5qxx-jpwr
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-xhrw-5qxx-jpwr
- Tags: area:os domain:desktop domain:oss impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:python technique:source-review vuln:path-traversal

**Target:** APM CLI plugin install/normalization flow (plugin_parser.py)
**Root cause:** _resolve_sources() joins manifest-controlled agents/skills/commands/hooks paths with plugin_path but only checks exists() and is_symlink(), never resolving the candidate and verifying containment inside the plugin root. The safer _read_mcp_file() precedent in the same module does resolve and reject escapes.
**Steps:**
1. Install apm-cli 0.8.11 (commit 70b34faa16a5a783424698163deeb028854fd23a).
2. Create an external file outside the plugin dir, e.g. `victim\secret.md` containing `# STOLEN VIA APM INSTALL`.
3. Create malicious plugin with `plugin.json`: `{"name":"evil-plugin","commands":"D:\\absolute\\path\\to\\victim\\secret.md"}`.
4. Create minimal `apm.yml` referencing the malicious plugin.
5. Run `apm install`.
6. Observe `.github/prompts/secret.prompt.md` written containing the external file content.
**How found:** Code review of plugin_parser.py: noticed _resolve_sources() only checks exists()/is_symlink() without containment, and contrasted it with the safer _read_mcp_file() in the same module that resolves and rejects escapes; then traced reachability through local and remote install paths and the prompt write-back integrator.
**Exploitation:** A victim runs `apm install` against a malicious or compromised plugin (direct or transitive). The manifest's `commands` field points to an absolute host path; APM copies that file into `.apm/prompts/`, then the prompt integrator auto-writes it to `.github/prompts/secret.prompt.md` in the victim project, staging host-local content into repo-controlled paths that may be committed or consumed by other tooling.
**Escalation:** Copied content landing in supported primitive locations (.apm/prompts/) triggers automatic project write-back into .github/prompts/, raising impact from local copy to repo-committed content.
**Insight:** When a module already has a hardened path-check helper (resolve + containment + symlink reject), grep for sibling functions doing the same job with weaker checks — the inconsistent precedent is the bug. Also trace where copied files land downstream (integrators) to upgrade a local copy into a project write-back.

## Proof of concept (copied exactly from the original report)

**Malicious plugin.json with absolute commands path**

```
{
  "name": "evil-plugin",
  "commands": "D:\\absolute\\path\\to\\victim\\secret.md"
}
```

**apm install command triggering the copy**

```
apm install

6. Observe that APM completes successfully
```

