diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4affd8cc..7c015190 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -55,6 +55,10 @@ updates: - dependency-name: '*' update-types: ['version-update:semver-major'] + # Every action is referenced by commit SHA with the version in a trailing comment; + # action-pins.yml fails any workflow change that reintroduces a mutable tag. Dependabot + # is what keeps those pins current: it rewrites the SHA and the comment together, so the + # pins are reviewed in a PR instead of drifting silently or being bumped by hand. - package-ecosystem: github-actions directory: / schedule: diff --git a/.github/workflows/action-pins.yml b/.github/workflows/action-pins.yml new file mode 100644 index 00000000..597778c5 --- /dev/null +++ b/.github/workflows/action-pins.yml @@ -0,0 +1,70 @@ +name: Action Pin Gate + +# Every third-party action must be referenced by a full commit SHA, never by a tag. +# Tags are mutable: whoever controls an action repository can repoint v4 at new code, +# and that code runs inside jobs that hold the code-signing identity, the storage +# credentials and a token that can push to main. A SHA is the only reference an +# upstream owner cannot change under us. +# +# Lives in its own workflow rather than in ci.yml: CI deliberately ignores changes to +# the other workflow files, so a guard placed there would stay silent for exactly the +# commits it needs to inspect. +# +# Like CI, this reports rather than blocks: main is deliberately unprotected, so a red +# run is a signal to act on, not a merge barrier. Making it binding means adding +# 'Verify action pins' as a required status check -- a branch-protection decision, not +# something this file can assert. +# +# The pins are not maintained by hand — .github/dependabot.yml opens a monthly grouped +# PR that rewrites both the SHA and the trailing version comment. The comment is +# therefore required too: it is what makes a pinned line reviewable. + +on: + push: + branches: [main] + paths: ['.github/workflows/**'] + pull_request: + branches: [main] + paths: ['.github/workflows/**'] + workflow_dispatch: + +permissions: + contents: read + +jobs: + verify-pins: + name: Verify action pins + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Every third-party action is pinned to a SHA + run: | + set -uo pipefail + failed=0 + while IFS=: read -r file line ref; do + # Reusable workflows from this repository move with the commit that calls + # them, so there is nothing to pin. + case "$ref" in ./*) continue ;; esac + + if [[ "$ref" =~ ^[^@]+@[0-9a-f]{40}[[:space:]]+#[[:space:]]*[^[:space:]] ]]; then + printf 'ok %s:%s %s\n' "$file" "$line" "$ref" + continue + fi + + if [[ "$ref" =~ ^[^@]+@[0-9a-f]{40}[[:space:]]*$ ]]; then + echo "::error file=$file,line=$line::Pinned but the version comment is missing: '$ref'. Write 'owner/action@ # vN' so the line stays reviewable." + else + echo "::error file=$file,line=$line::Action '$ref' is not pinned to a commit SHA. Tags are mutable -- resolve it with: gh api repos///git/ref/tags/" + fi + failed=1 + # -H is load-bearing: with a single matching file grep omits the name, the + # fields shift by one and every diagnostic points at nothing. + done < <(grep -nHE '^[[:space:]]*(-[[:space:]]+)?uses:' .github/workflows/*.yml \ + | sed -E 's/^([^:]+):([0-9]+):[[:space:]]*(-[[:space:]]+)?uses:[[:space:]]*/\1:\2:/') + + if [ "$failed" -ne 0 ]; then + echo "::error::Unpinned action references found -- fix them before this reaches main." + exit 1 + fi + echo "All action references are pinned to commit SHAs." diff --git a/.github/workflows/bootstrap-script-gates.yml b/.github/workflows/bootstrap-script-gates.yml index 46cd1045..71484214 100644 --- a/.github/workflows/bootstrap-script-gates.yml +++ b/.github/workflows/bootstrap-script-gates.yml @@ -18,7 +18,7 @@ jobs: name: Bootstrap guard tests (PS 5.1) runs-on: windows-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Install Pester 5+ shell: powershell diff --git a/.github/workflows/build-agent.yml b/.github/workflows/build-agent.yml index 454fade1..ae60e556 100644 --- a/.github/workflows/build-agent.yml +++ b/.github/workflows/build-agent.yml @@ -72,7 +72,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 # Every released build is signed; a missing variable stops the run before a build # number is burnt. @@ -122,7 +122,7 @@ jobs: # SDK 10 = single build toolchain (parity with local build.ps1 / global.json). - name: Setup .NET - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6 with: dotnet-version: '10.0.x' @@ -462,7 +462,7 @@ jobs: # Skipped on runs that publish nothing -- no log entry for bytes nobody receives. - name: Attest build provenance if: inputs.upload_blob || inputs.create_release - uses: actions/attest-build-provenance@v4 + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4 with: subject-path: | ${{ steps.artifact.outputs.versioned_zip_release }} @@ -470,13 +470,13 @@ jobs: staging/agent/AutopilotMonitor.SummaryDialog.exe - name: Upload build artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: AutopilotMonitor-Agent-v${{ steps.version.outputs.version }} path: ${{ steps.artifact.outputs.versioned_zip_release }} - name: Upload version metadata - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: version-metadata path: ${{ env.MANIFEST_NAME }} diff --git a/.github/workflows/build-msi.yml b/.github/workflows/build-msi.yml index 9bdf87f9..7e196694 100644 --- a/.github/workflows/build-msi.yml +++ b/.github/workflows/build-msi.yml @@ -40,7 +40,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 # The loader executes as SYSTEM on every enrolling device — a parse error or # non-ASCII mojibake (PS 5.1 codepage roulette) must fail the build, not the fleet. @@ -111,8 +111,11 @@ jobs: tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + # Exact version, not a "5.*" range: wix writes the bytes of the MSI that customers + # install, so a compromised or simply broken point release must not flow into a build + # unnoticed. Bumping is a reviewed commit. - name: Install WiX toolset - run: dotnet tool install --global wix --version "5.*" + run: dotnet tool install --global wix --version 5.0.2 # Version pinned; roll-forward because the tool targets an older runtime than the SDK. - name: Install AzureSignTool @@ -235,12 +238,12 @@ jobs: # gh attestation verify AutopilotMonitor-Bootstrap.msi --repo okieselbach/AutopilotMonitor - name: Attest build provenance (MSI) if: inputs.upload_blob - uses: actions/attest-build-provenance@v4 + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4 with: subject-path: ${{ env.STABLE_MSI }} - name: Upload build artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: AutopilotMonitor-Bootstrap-v${{ steps.version.outputs.version }} path: ${{ env.STABLE_MSI }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43d511e1..2e2df4ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ on: # Every other workflow: nothing here builds or tests anything CI covers. paths-ignore # cannot negate, so they are listed one by one; ci.yml itself stays out of the list # so a change to CI runs CI. A new workflow file is opt-in — add it here too. + - '.github/workflows/action-pins.yml' - '.github/workflows/bootstrap-script-gates.yml' - '.github/workflows/build-agent.yml' - '.github/workflows/build-msi.yml' @@ -67,6 +68,7 @@ on: # Every other workflow: nothing here builds or tests anything CI covers. paths-ignore # cannot negate, so they are listed one by one; ci.yml itself stays out of the list # so a change to CI runs CI. A new workflow file is opt-in — add it here too. + - '.github/workflows/action-pins.yml' - '.github/workflows/bootstrap-script-gates.yml' - '.github/workflows/build-agent.yml' - '.github/workflows/build-msi.yml' @@ -94,9 +96,9 @@ jobs: # windows-latest is required: the agent test projects target net48. runs-on: windows-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/setup-dotnet@v6 + - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6 with: dotnet-version: '10.0.x' @@ -125,9 +127,9 @@ jobs: run: working-directory: src/Web/autopilot-monitor-web steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: '22' cache: npm @@ -159,9 +161,9 @@ jobs: run: working-directory: src/McpServer/autopilot-monitor-mcp steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: '22' cache: npm @@ -179,7 +181,7 @@ jobs: # (run 31252869254). Persist the model across runs instead; HF_CACHE_DIR # is honored by vector-search-provider.ts. - name: Cache embedding model - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ${{ github.workspace }}/.hf-cache key: hf-model-Xenova-all-MiniLM-L6-v2 @@ -193,9 +195,9 @@ jobs: name: Generated guardrails match rules/guardrails.json runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: '22' @@ -214,9 +216,9 @@ jobs: name: Generated wire types match shared-manifests.json runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: '22' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0031dc44..cb871018 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -102,15 +102,15 @@ jobs: build-mode: none steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/setup-dotnet@v6 + - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6 if: matrix.language == 'csharp' with: dotnet-version: '10.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.9 + uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -121,6 +121,6 @@ jobs: run: dotnet build AutopilotMonitor.sln --configuration Release - name: Analyze - uses: github/codeql-action/analyze@v4.37.9 + uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: category: /language:${{ matrix.language }} diff --git a/.github/workflows/combine-rules.yml b/.github/workflows/combine-rules.yml index 38dae3bc..ea12c623 100644 --- a/.github/workflows/combine-rules.yml +++ b/.github/workflows/combine-rules.yml @@ -24,14 +24,17 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: '22' + # Exact versions and no lifecycle scripts: this job holds contents:write and pushes to + # main, so an install-time script from a hijacked package would run with a token that + # can rewrite the repository. ajv is pure JS and needs no install hooks. - name: Install ajv-cli - run: npm install -g ajv-cli ajv-formats + run: npm install -g --ignore-scripts ajv-cli@5.0.0 ajv-formats@3.0.1 - name: Validate Gather Rules run: | diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 0de02b84..99889e56 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -19,7 +19,7 @@ jobs: name: Build and Deploy Backend steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Login to Azure (OIDC) uses: azure/login@7ddb5af1ef8758cf1353cf3b42f940aee27ba21c # v3 @@ -29,7 +29,7 @@ jobs: subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: Setup .NET 10 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6 with: dotnet-version: '10.0.x' diff --git a/.github/workflows/deploy-mcp.yml b/.github/workflows/deploy-mcp.yml index 7ca211cd..6d38787b 100644 --- a/.github/workflows/deploy-mcp.yml +++ b/.github/workflows/deploy-mcp.yml @@ -26,7 +26,7 @@ jobs: name: Build and Deploy MCP Server steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 # The published customer documentation lives in its own PUBLIC repo and is # baked into the image as the search_docs corpus (see the Dockerfile). No @@ -36,7 +36,7 @@ jobs: # re-run actually rolls a new revision). /health reports the SHA captured # below so that staleness is checkable. - name: Check out documentation bundle - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: autopilotmonitor/autopilotmonitor-docs path: mcp-docs diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml index 386f45b8..7887e7bf 100644 --- a/.github/workflows/deploy-web.yml +++ b/.github/workflows/deploy-web.yml @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest name: Build and Deploy Job steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: lfs: false fetch-depth: 0 # Full history needed for git-based sitemap lastmod dates @@ -56,7 +56,7 @@ jobs: # workflow runs again (push under src/Web/** or a manual dispatch); versions/web.json # records which docs commit is live. - name: Check out documentation bundle - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: autopilotmonitor/autopilotmonitor-docs path: whats-new-docs @@ -66,7 +66,7 @@ jobs: id: docs run: echo "sha=$(git -C whats-new-docs rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: '22' cache: npm @@ -114,12 +114,12 @@ jobs: # commit, workflow file) in a public transparency log. # Verify: gh attestation verify portal-.tar.gz --repo okieselbach/AutopilotMonitor - name: Attest build provenance (portal export) - uses: actions/attest-build-provenance@v4 + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4 with: subject-path: ${{ steps.package.outputs.archive }} - name: Upload build artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: portal-${{ github.sha }} path: ${{ steps.package.outputs.archive }} diff --git a/.github/workflows/publish-scripts.yml b/.github/workflows/publish-scripts.yml index f964764e..95f44f82 100644 --- a/.github/workflows/publish-scripts.yml +++ b/.github/workflows/publish-scripts.yml @@ -54,7 +54,7 @@ jobs: needs: gates runs-on: windows-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Check code-signing configuration shell: pwsh @@ -127,7 +127,7 @@ jobs: # when someone wants to look at the file. - name: Upload signed scripts if: always() - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: signed-scripts path: ${{ runner.temp }}/signed-scripts diff --git a/internal b/internal index 8204fc68..9a3a61f5 160000 --- a/internal +++ b/internal @@ -1 +1 @@ -Subproject commit 8204fc68af1bb10e17d50dcbc9999e99169df9fc +Subproject commit 9a3a61f56fc79a1cd076a852052f92d9589b10b2