diff --git a/.github/workflows/apm-audit.yml b/.github/workflows/apm-audit.yml index ce39d99..8404b7b 100644 --- a/.github/workflows/apm-audit.yml +++ b/.github/workflows/apm-audit.yml @@ -2,20 +2,23 @@ name: apm-audit # Reusable workflow shipped by zava-agent-config for downstream APM consumers. # -# v5.1.0+: the reusable workflow now runs `apm audit --ci --policy org` -# explicitly. v5.0.x called microsoft/apm-action with audit-report only, -# which generated SARIF but did NOT enforce the org policy or fail the -# job on violations — drift-only check, no policy gate. This regression -# was caught during the D2 governance demo wiring (see DevExpGbb/.github -# apm-policy.yml v2.0.0) where required-packages, dependency-denylist, -# and unmanaged-files violations passed CI silently. +# v5.1.2: switched apm-action to setup-only and removed `apm install` from +# the audit path. Reason: install RE-DEPLOYS files from upstream, OVERWRITING +# any in-PR tamper of a deployed managed file. That silently disabled drift +# detection in CI (caught during D2 demo Beat 5 wiring). Audit now runs +# directly against the contents of the PR, so content-integrity / drift / +# unmanaged-files all see what the PR author committed. +# +# v5.1.1: auto-detect repo apm-policy.yml override and use it as policy +# source so `extends: org` actually layers (vs --policy org which loads +# only the org layer). +# +# v5.1.0: explicit `apm audit --ci --policy ...` — the apm-action default +# only generates SARIF and does not enforce policy. # # jobs: # audit: -# uses: DevExpGbb/zava-agent-config/.github/workflows/apm-audit.yml@v5.1.0 -# -# Pinning the workflow to a tag means the consumer's audit step is part of -# the supply chain they audit — no implicit "latest" reusable workflow. +# uses: DevExpGbb/zava-agent-config/.github/workflows/apm-audit.yml@v5.1.2 on: workflow_call: @@ -49,26 +52,18 @@ jobs: echo "::notice::No APM dependencies declared — audit is a no-op." fi - # Install + run apm install via the action. setup-only would skip install, - # but we WANT install to also run so policy enforcement at install time - # (denylist on uninstalled new deps) catches violations even before audit. - - name: Install APM + dependencies + # setup-only installs the apm CLI on PATH and skips `apm install`. + # We must NOT run install here: it would re-deploy primitives from + # upstream and overwrite any tampered file in the PR, masking drift. + - name: Install APM CLI if: steps.detect.outputs.has_deps == 'true' uses: microsoft/apm-action@v1 with: - audit-report: true + setup-only: true - # Explicit policy enforcement. The action generates SARIF but does NOT - # call `--ci` or `--policy` — it runs drift-only. This step closes the - # gap: `--policy org` resolves the consumer org's apm-policy.yml from - # GitHub (DevExpGbb/.github/apm-policy.yml in our case) and `--ci` - # makes block-enforcement violations exit non-zero. - # Detect repo-level override. apm policy supports tighten-only override - # layering via `extends: org` in a repo apm-policy.yml. When present, - # use the repo file as policy source so the audit gates on the EFFECTIVE - # policy (org ∪ repo override). Falls back to `--policy org` otherwise. - name: Detect repo policy override id: policy + if: steps.detect.outputs.has_deps == 'true' run: | if [ -f apm-policy.yml ]; then echo "source=./apm-policy.yml" >> "$GITHUB_OUTPUT" @@ -77,32 +72,29 @@ jobs: echo "source=org" >> "$GITHUB_OUTPUT" fi - - name: Enforce APM policy (org + repo override) + - name: Enforce APM policy + drift (apm audit --ci) if: steps.detect.outputs.has_deps == 'true' env: - GITHUB_APM_PAT: ${{ github.token }} APM_FAIL_ON_WARN: ${{ inputs.fail-on-warn }} POLICY_SOURCE: ${{ steps.policy.outputs.source }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - set -e + set -euo pipefail if [ "$APM_FAIL_ON_WARN" = "true" ]; then - apm audit --ci --policy "$POLICY_SOURCE" --fail-on-warn + apm audit --ci --policy "$POLICY_SOURCE" --fail-on-warning else apm audit --ci --policy "$POLICY_SOURCE" fi - - name: Summarize on PR - if: always() && github.event_name == 'pull_request' + - name: Step summary + if: always() && steps.detect.outputs.has_deps == 'true' run: | { - echo "## 🛡️ APM Audit" + echo "## APM audit" + echo "" + echo "- Policy source: \`${{ steps.policy.outputs.source }}\`" + echo "- Layered enforcement: repo override (\`extends: org\`) when present, else org floor" + echo "- Checks: lockfile-exists, ref-consistency, deployed-files-present, no-orphaned-packages, skill-subset-consistency, config-consistency, content-integrity, drift, required-packages, dependency-allowlist, dependency-denylist, unmanaged-files" echo "" - if [ "${{ steps.detect.outputs.has_deps }}" = "true" ]; then - echo "Ran \`apm audit --ci --policy org\` against the consumer's org policy via \`microsoft/apm-action@v1\`." - echo "(Org policy resolved from \`/.github/apm-policy.yml\` automatically.)" - echo "" - echo "SARIF report uploaded by apm-action; see job logs and Code scanning tab." - else - echo "No APM dependencies declared in \`apm.yml\` — audit skipped." - fi + echo "Ran \`apm audit --ci --policy $POLICY_SOURCE\` against PR contents (no \`apm install\` — drift detection sees the as-committed file state)." } >> "$GITHUB_STEP_SUMMARY"