Microsoft APM -- T1195.002 symlinked prompt/agent file dereferenced into deploy directories
SIGMA detection rule by WinstonRedGuard -- sigma plugin observed rules (derived from breach corpus) · Unknown
Vendor-disclosed detection for CVE-2026-45539 (GHSA-q5pp-gvjg-h7v4), a
self-reported advisory for Microsoft APM, an open-source dependency
manager for AI agents.
From 0.5.4 to 0.12.4, two file-discovery integrators
(`PromptIntegrator.find_prompt_files`, `AgentIntegrator.find_agent_files`)
enumerate package files with bare `Path.glob()`/`Path.rglob()` and read
each match with `Path.read_text()`, which transparently follows
symbolic links. A remote APM dependency can commit a symlink inside its
own package -- the advisory's own PoC:
`ln -s /tmp/poc_secret .apm/agents/notes.agent.md` and
`ln -s /tmp/poc_secret .apm/prompts/welcome.prompt.md`. The symlink is
preserved verbatim into `apm_modules/` on `apm install`/clone, then
DEREFERENCED during integration -- the resolved target's content is
written as an ordinary file into the project's deploy directories
(`.github/`, `.claude/`, `.codex/`, etc.). Three independent controls
all miss this: `content_hash` is computed BEFORE the symlink is
resolved, `SecurityGate.scan_files` walks with `followlinks=False` (so
it never inspects the symlink's actual target), and the auto-generated
`.gitignore` covers only `apm_modules/`, not the deploy directories the
dereferenced content lands in -- so the resulting files get `git add`ed
by default.
Detection logic flags the structural artifact this chain produces: a
file created under one of the deploy directories (`.github/`,
`.claude/`, `.codex/`) whose SOURCE, per the file-system event, was a
symlink under `apm_modules/.apm/agents/` or `apm_modules/.apm/prompts/`
-- an ordinary APM package install never needs a symlink there, since
legitimate prompt/agent files are plain text.
Aggregated from 1 first-party vendor security advisory (Microsoft, via
GHSA and NVD), fixed in 0.13.0 (routes finders through
`BaseIntegrator.find_files_by_glob()`, which checks
`f.is_symlink()`/`f.stat().st_nlink > 1` before reading).
title: Microsoft APM -- T1195.002 symlinked prompt/agent file dereferenced into deploy directories
id: 65cf27c9-9c1a-47dd-b4a1-49cd8dedbbcd
status: experimental
description: |
Vendor-disclosed detection for CVE-2026-45539 (GHSA-q5pp-gvjg-h7v4), a
self-reported advisory for Microsoft APM, an open-source dependency
manager for AI agents.
From 0.5.4 to 0.12.4, two file-discovery integrators
(`PromptIntegrator.find_prompt_files`, `AgentIntegrator.find_agent_files`)
enumerate package files with bare `Path.glob()`/`Path.rglob()` and read
each match with `Path.read_text()`, which transparently follows
symbolic links. A remote APM dependency can commit a symlink inside its
own package -- the advisory's own PoC:
`ln -s /tmp/poc_secret .apm/agents/notes.agent.md` and
`ln -s /tmp/poc_secret .apm/prompts/welcome.prompt.md`. The symlink is
preserved verbatim into `apm_modules/` on `apm install`/clone, then
DEREFERENCED during integration -- the resolved target's content is
written as an ordinary file into the project's deploy directories
(`.github/`, `.claude/`, `.codex/`, etc.). Three independent controls
all miss this: `content_hash` is computed BEFORE the symlink is
resolved, `SecurityGate.scan_files` walks with `followlinks=False` (so
it never inspects the symlink's actual target), and the auto-generated
`.gitignore` covers only `apm_modules/`, not the deploy directories the
dereferenced content lands in -- so the resulting files get `git add`ed
by default.
Detection logic flags the structural artifact this chain produces: a
file created under one of the deploy directories (`.github/`,
`.claude/`, `.codex/`) whose SOURCE, per the file-system event, was a
symlink under `apm_modules/.apm/agents/` or `apm_modules/.apm/prompts/`
-- an ordinary APM package install never needs a symlink there, since
legitimate prompt/agent files are plain text.
Aggregated from 1 first-party vendor security advisory (Microsoft, via
GHSA and NVD), fixed in 0.13.0 (routes finders through
`BaseIntegrator.find_files_by_glob()`, which checks
`f.is_symlink()`/`f.stat().st_nlink > 1` before reading).
references:
- https://github.com/microsoft/apm/security/advisories/GHSA-q5pp-gvjg-h7v4
- https://attack.mitre.org/techniques/T1195/002/
author: WinstonRedGuard -- sigma plugin observed rules (derived from breach corpus)
date: '2026-08-11'
logsource:
category: file_event
product: linux
detection:
selection_symlink_source:
Image|endswith: /ln
CommandLine|contains:
- .apm/agents/
- .apm/prompts/
selection_symlink_flag:
CommandLine|contains: '-s'
selection_deploy_dir_target:
CommandLine|contains:
- .github/
- .claude/
- .codex/
condition: selection_symlink_source and selection_symlink_flag and selection_deploy_dir_target
falsepositives:
- A deployment already upgraded to 0.13.0+, where every finder routes
through `find_files_by_glob()`'s symlink/hardlink check before reading
-- a matching symlink on a fixed deployment would be rejected rather
than dereferenced into a deploy directory
- A legitimate APM package maintainer intentionally symlinking a shared
prompt/agent file within their OWN package for deduplication -- this
rule cannot distinguish that from a malicious dependency by command-line
content alone; correlate with whether the symlink originates from a
THIRD-PARTY dependency package vs. the project's own source
- Review for environment-specific tuning before
deployment; the two-step selection (symlink creation, THEN dereference
into a deploy dir) approximates the advisory's chain but a real file
system event log may capture these as a single rename/copy operation
rather than two observable steps
level: high
tags:
- attack.t1195.002
- wrg.observed.cve.cve_2026_45539
- wrg.observed.source.microsoft_ghsa_q5pp_gvjg_h7v4
- wrg.observed.vector.symlink_dereference_on_read_text
- wrg.observed.vector.security_gate_followlinks_false_gap
- wrg.severity.high
- wrg.observed
- wrg.tactic.initial_access