CVE-2026-45539: Verified Repro With Script Download
CVE-2026-45539: Microsoft APM: arbitrary file disclosure via symlink-following on apm install
CVE-2026-45539 is verified against apm · pip. Affected versions: 0.5.4 through 0.12.4 inclusive. Fixed in 0.13.0. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00136.
What Is CVE-2026-45539?
CVE-2026-45539 is a high-severity arbitrary file disclosure vulnerability in apm-cli (the apm PyPI package), caused by transparent symlink-following when it installs APM packages. Pruva reproduced it (reproduction REPRO-2026-00136).
CVE-2026-45539 Severity & CVSS Score
CVE-2026-45539 is rated high severity, with a CVSS base score of 7.4 out of 10.
High — serious impact or readily exploitable. Prioritize remediation.
Affected apm Versions
apm · pip versions 0.5.4 through 0.12.4 inclusive are affected.
How to Reproduce CVE-2026-45539
pruva-verify REPRO-2026-00136 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00136/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-45539
Reproduced by Pruva's autonomous agents — 212 tool calls over 29 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-45539
Summary
apm-cli (the apm-cli PyPI package, versions 0.5.4 through 0.12.4) transparently follows symbolic links when enumerating and reading APM package primitive files (.prompt.md, .agent.md, .chatmode.md). A malicious APM dependency can include a symlink under .apm/prompts/ or .apm/agents/ that points to an arbitrary absolute host path (e.g. /etc/passwd). During apm install, the CLI dereferences the symlink, reads the target file's contents with Path.read_text(), and copies those contents into the consuming project's installed-package tree (e.g. .github/prompts/). This results in arbitrary file disclosure into the victim's project tree.
Impact
- Package/component affected:
apm-cli(PyPI package) - Affected versions:
0.5.4→0.12.4(inclusive) - Fixed versions:
0.13.0 - Risk level: High (CVSS 3.1 base 7.4)
- Consequences: A malicious dependency author can exfiltrate any file readable by the user running
apm installinto files inside the victim project, where they may be committed, uploaded, or otherwise exposed.
Root Cause
The vulnerable code paths are in PromptIntegrator.find_prompt_files(), AgentIntegrator.find_agent_files(), and the various copy_*() methods in src/apm_cli/integration/. Specifically:
File enumeration used bare
Path.glob()andPath.rglob()without checking whether entries were symlinks. On Python 3.10+,Path.glob()transparently follows symlinks, so a symlink inside.apm/prompts/matching*.prompt.mdwould be returned as a valid file path.File reading used
Path.read_text()which dereferences symlinks. Whencopy_prompt()or agent copy methods read the source file, they inadvertently read the symlink target's contents.No containment check verified whether a resolved file path stayed within the package directory.
The fix in v0.13.0 (commit range from v0.12.4..v0.13.0, security merge commits 77d1dda and f85b9f5) addresses this by:
- Introducing/refactoring
BaseIntegrator.find_files_by_glob()to skip symlink entries (f.is_symlink()) and hardlinks (st_nlink > 1), plus a defense-in-depthresolve().is_relative_to()containment guard. - Updating
PromptIntegrator.find_prompt_files()andAgentIntegrator.find_agent_files()to usefind_files_by_glob()instead of bareglob()/rglob(). - Adding explicit
source.is_symlink()checks incopy_prompt(),copy_agent(), and related agent copy methods that raiseValueErrorif a symlink is somehow passed through.
Reproduction Steps
The reproduction script is located at:
repro/reproduction_steps.sh
What the script does:
- Creates a scratch workspace under
$ROOT/scratch. - Creates a malicious local APM package (
malicious_pkg) containing:- A legitimate
.apm/prompts/legit.prompt.mdand.apm/agents/legit.agent.md - A symlink
.apm/prompts/evil.prompt.md -> /path/to/sentinel.txt
- A legitimate
- Creates a consumer project with
target: copilotand an emptydependencies.apmlist. - Installs
apm-cli==0.12.4(vulnerable) in a virtualenv. - Runs
apm install ../malicious_pkgfrom the consumer project. - Verifies that
evil.prompt.mdappears in.github/prompts/with the sentinel file's contents (host file disclosure). - Installs
apm-cli==0.13.0(fixed) in the same virtualenv. - Resets the consumer project state and re-runs
apm install ../malicious_pkg. - Verifies that
evil.prompt.mddoes not appear in.github/prompts/.
Expected evidence of reproduction:
- With
apm-cli==0.12.4: The install log shows2 prompts integrated, andscratch/consumer/.github/prompts/evil.prompt.mdexists containing the sentinel string. - With
apm-cli==0.13.0: The install log shows1 prompts integrated, andevil.prompt.mdis absent from.github/prompts/.
Evidence
Log files generated by the reproduction script:
logs/vulnerable_install.log— Output ofapm installusing the vulnerable build.logs/fixed_install.log— Output ofapm installusing the fixed build.
Key excerpts from a successful run:
Vulnerable build (0.12.4):
[+] ../malicious_pkg (local)
|-- 2 prompts integrated -> .github/prompts/
|-- 1 agents integrated -> .github/agents/
Post-install file check:
FILE: .../.github/prompts/evil.prompt.md
CVE-2026-45539-REPRO-SENTINEL-<timestamp>
Fixed build (0.13.0):
[+] ../malicious_pkg (local)
|-- 1 prompts integrated -> .github/prompts/
|-- 1 agents integrated -> .github/agents/
Post-install file check:
evil.prompt.mdis not present under.github/prompts/.
Recommendations / Next Steps
- Upgrade immediately to
apm-cli>=0.13.0. - Audit existing projects for any
apm_modules/or installed dependencies that may have already leaked host file contents. - Review dependency sources — only install APM packages from trusted registries or verified local bundles.
- Add regression tests that exercise symlink and hardlink rejection during integration (the project already added
tests/unit/integration/test_symlink_rejection.pyin the fix release). - Consider broader symlink policy — evaluate whether symlinks should be permitted anywhere in the install pipeline (e.g. during download/unpack) or should be stripped at package ingestion time.
Additional Notes
- Idempotency confirmed: The reproduction script passes two consecutive runs without modification, producing the same observable results each time.
- Edge cases: The fix also rejects hardlinks (
st_nlink > 1) becausePath.resolve()returns the hardlink's own path, sois_relative_to()cannot catch a hardlink to a file outside the package tree. - Limitations: The reproduction uses a local path dependency (
../malicious_pkg). The same vulnerability would be exploitable via remote dependencies if an attacker could publish a malicious APM package containing symlinks in its.apm/subtree.
CVE-2026-45539 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-45539
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix CVE-2026-45539
Upgrade apm · pip to 0.13.0 or later.
FAQ: CVE-2026-45539
What happens when a victim installs a dependency exploiting CVE-2026-45539?
Which apm-cli versions are affected by CVE-2026-45539, and where is it fixed?
How severe is CVE-2026-45539?
How can I reproduce CVE-2026-45539?
References for CVE-2026-45539
Authoritative sources for CVE-2026-45539 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.