## Ticket: CVE-2026-45539 — Microsoft APM symlink-following arbitrary file disclosure

**Advisory**: GHSA-q5pp-gvjg-h7v4 — https://github.com/microsoft/apm/security/advisories/GHSA-q5pp-gvjg-h7v4
**CVE**: CVE-2026-45539 | **CWE-59** (Link Following) / **CWE-22** (Path Traversal)
**Severity**: High — CVSS 3.1 base 7.4
**Package**: `apm` (PyPI — the `apm-cli` tool) | **Repository**: https://github.com/microsoft/apm

### Impact

`apm-cli` (the `apm` PyPI package) installs APM packages and their remote
dependencies. When it processes a package it enumerates that package's files
with bare `Path.glob()` / `Path.rglob()` and reads them with `read_text()`.
All three operations **transparently follow symbolic links**.

A malicious APM dependency can commit a symlink inside its package payload —
under `.apm/prompts/` or `.apm/agents/` — that points at an arbitrary absolute
path on the host (for example `/etc/passwd`). When a victim runs `apm install`
and that dependency is installed, apm-cli dereferences the symlink: it reads
the **target host file's contents** and copies them into the consuming
project's installed-package tree.

The result is **arbitrary file disclosure**: a dependency author can exfiltrate
any file readable by the user running `apm install` into a file inside the
victim's project (where it may then be committed, uploaded, or otherwise
exposed). No host privileges beyond running `apm install` are needed.

### Affected / fixed versions

Affected: `apm` `0.5.4` → `0.12.4` (inclusive).
Fixed: **`0.13.0`**.

Reproduce on a vulnerable build (**`0.12.4`**) and verify the fix on
**`0.13.0`**.

### Where to look

The fix ships in the `0.13.0` release. Clone the repo and inspect the diff
between the `0.12.4` and `0.13.0` tags to confirm the root cause and the
symlink guard:

```bash
git clone https://github.com/microsoft/apm.git
cd apm && git diff 0.12.4 0.13.0
```

The vulnerable code paths are the file-enumeration / file-reading routines
that use `Path.glob()`, `Path.rglob()` and `read_text()` without checking
whether an entry is a symlink (or whether its resolved target escapes the
package directory). The fix adds a check that rejects / does not follow
symlinks when installing a package's `.apm/prompts/` and `.apm/agents/`
contents.

### Reproduction approach

No service, database, or browser is needed — the bug is observable purely on
the local filesystem.

1. In a scratch virtualenv, install the **vulnerable** build:
   `pip install apm==0.12.4`
2. Build a local "dependency" APM package directory:
   - a benign normal file, e.g. `.apm/prompts/hello.prompt.md` with ordinary
     text, so the package is non-empty;
   - a malicious symlink, e.g. `.apm/prompts/evil.prompt.md` whose target is
     an absolute host path with known contents — use `/etc/passwd`, or create
     a sentinel file containing a unique marker string and point the symlink
     at that.
3. From a separate consuming project, run `apm install` so apm-cli installs
   that dependency. Inspect the installed-package directory in the consuming
   project tree.
4. Repeat the whole flow with the **fixed** build: `pip install apm==0.13.0`.

### Expected result

| Build | symlink `evil.prompt.md -> /etc/passwd` on `apm install` | host file contents in project tree? |
|-------|----------------------------------------------------------|-------------------------------------|
| `apm==0.12.4` (vulnerable) | symlink dereferenced, target read with `read_text()` | **yes** — `/etc/passwd` (or the sentinel marker) contents are written into the consuming project |
| `apm==0.13.0` (fixed) | symlink detected, not followed | **no** — the dereferenced host file contents never appear in the project tree |

- **Vulnerable indicator**: after `apm install`, a file inside the consuming
  project's installed-dependency directory contains the host file's contents
  (e.g. the `root:x:0:0:` line from `/etc/passwd`, or the unique sentinel
  marker). Host data has been disclosed into the project tree.
- **Fixed indicator**: the dereferenced host file contents are absent
  everywhere in the consuming project tree — the malicious symlink was not
  followed.

### Expected artifacts

- `reproduction_steps.sh` — builds the malicious dependency package, installs
  both `apm` versions, runs `apm install` for each, and checks whether the
  host file contents leaked into the project tree.
- `validation_verdict.json` — `verdict: confirmed`, capturing the vulnerable
  and fixed indicators.
- Logs capturing the install output and the project-tree contents for
  `apm==0.12.4` and `apm==0.13.0`.
