Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 24 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,12 @@ jobs:
retention-days: 30
if-no-files-found: error

# Dogfood the two CI gates we ship and document to users:
# - Gate A (consumer-side): `apm audit --ci` -- lockfile / install fidelity.
# - Gate B (producer-side): regeneration drift -- did someone hand-edit
# a regenerated file under .github/ without updating canonical .apm/?
# Dogfood the audit-only CI gate we ship and document to users:
# - Gate A (consumer-side): `apm audit --ci --no-drift` -- lockfile /
# content-integrity check. setup-only: true keeps managed files
# untouched so the SHA-256 content-integrity check can detect tampered
# files. The install-replay (--no-drift) is skipped because there is
# no warm cache; content-integrity covers the same tamper signal.
Comment thread
sergio-sisternes-epam marked this conversation as resolved.
Outdated
# See microsoft/apm#883 for context. Tier 1 (no secrets needed).
apm-self-check:
name: APM Self-Check
Expand All @@ -140,32 +142,29 @@ jobs:
steps:
- uses: actions/checkout@v4

# Installs the APM CLI (latest stable) and runs `apm install` against
# this repo's apm.yml. Auto-detects target from the existing .github/
# directory and re-integrates local .apm/ content, regenerating
# .github/instructions/, .github/agents/, .github/skills/, etc.
# Adds `apm` to PATH for subsequent steps.
# Installs the APM CLI (latest stable) and adds `apm` to PATH.
# setup-only: true skips `apm install` so managed files on disk are
# not overwritten before the audit runs. This preserves the committed
# state so content-integrity can detect any tampered file hashes.
- uses: microsoft/apm-action@v1
with:
setup-only: true

# Gate A: lockfile / install fidelity (consumer-side).
# Verifies every file in lockfile.deployed_files exists, ref consistency
# between apm.yml and apm.lock.yaml, no orphan packages, and
# content-integrity (hidden Unicode) on deployed package content.
# Does NOT verify deployed-file content vs lockfile (see #684).
- name: apm audit --ci
run: apm audit --ci

# Gate B: regeneration drift (producer-side).
# NOTE: Once `apm-action` ships a CLI version that includes the
# default-on `apm audit` drift detection (issue #1071), this entire
# step becomes redundant -- Gate A above already catches the same
# divergence via install-replay. Keep this bash check until then as
# a defense-in-depth fallback.
#
# The action's `apm install` step re-integrated local .apm/ into
# .github/ via target auto-detection. If anything in the governed
# integration directories changed, someone edited the regenerated
# output without updating the canonical .apm/ source.
# content-integrity (SHA-256 hashes against deployed_file_hashes in the
# lockfile) on deployed package content. --no-drift skips the
# install-replay because there is no warm cache (setup-only did not
# run apm install); content-integrity still catches tampered files.
- name: apm audit --ci --no-drift
run: apm audit --ci --no-drift

# Gate B: regeneration drift (producer-side) -- legacy bash fallback.
# With setup-only: true, apm install did not run and the working tree
# is unchanged, so this check trivially passes. It is kept so the
# pattern is visible; a full install+audit workflow would rely on this
# step to detect hand-edits to regenerated .github/ files.
Comment thread
sergio-sisternes-epam marked this conversation as resolved.
Outdated
- name: Check APM integration drift (legacy bash fallback, see #1071)
run: |
if [ -n "$(git status --porcelain -- .github/ .claude/ .cursor/ .opencode/)" ]; then
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- CI self-check job now uses `setup-only: true` + `apm audit --ci --no-drift` so managed files are not overwritten by `apm install` before `content-integrity` runs; documented the audit-only CI pattern and the install-before-audit blind spot in the enterprise and CI/CD guides. (#1291)
Comment thread
sergio-sisternes-epam marked this conversation as resolved.
- Pin `Path.home()` under unit tests via a session-scoped autouse conftest fixture, fixing 56 Windows runner failures on the new `windows-2025-vs2026` GitHub-hosted image where `USERPROFILE`/`HOMEDRIVE`+`HOMEPATH` are not seeded for pytest workers; also patch the `_check_and_notify_updates` import binding in the disabled-self-update test so it no longer races on the version-check cache. (#1270)
- `apm install` now works on macOS git 2.53.0 (Homebrew): bare-cache commands switch to `--git-dir` to satisfy the `safe.bareRepository=explicit` default; fetched SHAs are pinned as synthetic refs so `git clone --local --shared` no longer silently omits them. (#1268)
- Set the unit-test hermetic HOME at conftest import time so a single xdist worker on the `windows-2025-vs2026` runner can no longer race fixture setup and re-trigger the 53 `Path.home()` failures the session-scoped autouse fixture was supposed to prevent. (#1271)
Expand Down
34 changes: 34 additions & 0 deletions docs/src/content/docs/enterprise/drift-detection.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,40 @@ as either eviction-and-refetch (silent self-heal) or a hard failure
when the cache cannot be repopulated -- never as wrong content under
the right name.

## Install before audit and tamper detection

Running `apm install` before `apm audit --ci` is the correct pattern when
the goal is detecting a developer who forgot to run `apm install` after
editing `apm.yml`. The install step regenerates deployed files so the
subsequent audit can compare them against the lockfile.

That sequence has a blind spot: `apm install` overwrites every managed file
with a clean copy before the audit runs. If a deployed file was modified on
disk after the last install -- for example a hand-edit to
`.github/instructions/` -- the install step restores the original bytes.
The `content-integrity` check then compares the restored file against a
matching hash and reports no finding.

To detect post-install modification, use `setup-only: true` on the action
so it only provides the CLI without running `apm install`, then audit with
`--no-drift`:

```yaml
- uses: microsoft/apm-action@v1
with:
setup-only: true
- run: apm audit --ci --no-drift
```

`--no-drift` skips the install-replay (which requires a warm cache that
`setup-only` does not populate). The `content-integrity` check verifies
SHA-256 hashes of every deployed file against `deployed_file_hashes` in
`apm.lock.yaml` without needing to replay the install. Any byte-level
change to a deployed file since the last install is caught by this check.

See [Enforce in CI](../enforce-in-ci/#audit-only-ci-pattern) for the full
recipe and a comparison table of the two patterns.

## Org-wide sweeps

APM runs per repository. There is no built-in fleet console. The
Expand Down
48 changes: 48 additions & 0 deletions docs/src/content/docs/enterprise/enforce-in-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,54 @@ jobs:
Make this job a required status check via
[GitHub Rulesets](../github-rulesets/) and a violating PR cannot merge.

## Audit-only CI pattern

The default `microsoft/apm-action@v1` runs `apm install` before any
subsequent steps. That is the right default for most workflows: it ensures
the lockfile and deployed files are present before the audit reads them.

However, `apm install` overwrites every managed file with a fresh copy
before `apm audit --ci` runs. If a managed file was modified on disk after
the last install -- its bytes changed without updating the lockfile hash --
the install step silently restores the clean copy. The `content-integrity`
check then compares the freshly restored file against a hash that matches,
and the tampering goes undetected.

To detect post-install file modification, run the action in setup-only mode
so it only adds the CLI to `PATH` without touching deployed files:

```yaml
jobs:
audit:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: microsoft/apm-action@v1
with:
setup-only: true # CLI only; does not run apm install
- run: apm audit --ci --no-drift
env:
GITHUB_APM_PAT: ${{ secrets.APM_PAT }}
```

`setup-only: true` leaves every deployed file exactly as checked out.
`--no-drift` skips the install-replay check because no warm cache exists;
the `content-integrity` check still verifies that every deployed file's
SHA-256 hash matches the `deployed_file_hashes` recorded in `apm.lock.yaml`.
Any file whose bytes were changed after the last install fails this check.

The two patterns serve different goals:

| Pattern | Use when |
|---|---|
| Full install then audit | Catching developers who skipped `apm install` after editing `apm.yml`; ensuring deployed files are present on a fresh runner |
| Audit-only (`setup-only: true`) | Detecting modification of deployed files after install; committed files and lockfile are the ground truth |

Both patterns enforce policy and the eight baseline lockfile checks. The
difference is only in whether content-integrity can see tampered bytes.

## Recipe: SARIF for GitHub Code Scanning

Emit SARIF and upload it so each violation appears inline on the PR
Expand Down
21 changes: 20 additions & 1 deletion docs/src/content/docs/integrations/ci-cd.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,27 @@ runs, hand-edited deployed files, and orphaned files. See the
[Drift Detection guide](../../guides/drift-detection/) for details and
opt-out (`--no-drift`).

For tamper detection -- catching deployed files modified after the last
install -- use the audit-only pattern instead. `apm install` overwrites
managed files before audit runs, which erases any tampered bytes before
`content-integrity` can see them. Pass `setup-only: true` to the action so
it only provides the CLI, then audit with `--no-drift`:

```yaml
- uses: microsoft/apm-action@v1
with:
setup-only: true
- name: Audit (audit-only, tamper detection)
run: apm audit --ci --no-drift
```

`content-integrity` verifies the SHA-256 hash of every deployed file
against `deployed_file_hashes` in `apm.lock.yaml` without replaying the
install. See [Audit-only CI pattern](../../enterprise/enforce-in-ci/#audit-only-ci-pattern)
for the full recipe and when to use each approach.

:::tip[We dogfood this]
APM's own repo uses the `APM Self-Check` job in [`microsoft/apm`'s `ci.yml`](https://github.com/microsoft/apm/blob/main/.github/workflows/ci.yml) as a reference implementation for installing APM and running `apm audit --ci`. Use it as a practical example when wiring these checks into your own workflow.
APM's own repo uses the `APM Self-Check` job in [`microsoft/apm`'s `ci.yml`](https://github.com/microsoft/apm/blob/main/.github/workflows/ci.yml) as a reference implementation of the audit-only CI pattern: `setup-only: true` keeps deployed files untouched so `content-integrity` can detect tampered bytes, and `--no-drift` skips the replay that requires a warm cache. Use it as a practical example when wiring the audit-only check into your own workflow.
:::

## Azure Pipelines
Expand Down
Loading