diff --git a/.github/workflows/build-release-candidate.yml b/.github/workflows/build-release-candidate.yml index bebaa12c1..95f915092 100644 --- a/.github/workflows/build-release-candidate.yml +++ b/.github/workflows/build-release-candidate.yml @@ -462,7 +462,7 @@ jobs: - name: Submit SignPath Authenticode request if: ${{ inputs.windows_signing_backend == 'signpath' }} id: signpath - uses: signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2 + uses: signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 # v2 with: api-token: ${{ secrets.SIGNPATH_API_TOKEN }} organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} @@ -719,7 +719,7 @@ jobs: sudo apt-get install -y zip unzip - name: Set up Helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: 'v3.15.2' @@ -937,7 +937,7 @@ jobs: # downloaded asset without access to GitHub's attestation API. - name: Attest complete release candidate id: attest_release_candidate - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-checksums: ${{ runner.temp }}/release-candidate-subjects.sha256 diff --git a/.github/workflows/compile-release-payload.yml b/.github/workflows/compile-release-payload.yml index 7c1b219bf..7799807bb 100644 --- a/.github/workflows/compile-release-payload.yml +++ b/.github/workflows/compile-release-payload.yml @@ -155,7 +155,7 @@ jobs: - name: Attest hosted qualification subjects id: attest_secure_runtime - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-checksums: ${{ runner.temp }}/secure-runtime-qualification/secure-runtime-compiler-subjects.sha256 diff --git a/.github/workflows/helm-ci.yml b/.github/workflows/helm-ci.yml index 5dd2f7dfa..24ef98aff 100644 --- a/.github/workflows/helm-ci.yml +++ b/.github/workflows/helm-ci.yml @@ -31,7 +31,7 @@ jobs: persist-credentials: false - name: Set up Helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: v3.15.2 diff --git a/.github/workflows/helm-pages.yml b/.github/workflows/helm-pages.yml index 3ca3e13d3..de2464936 100644 --- a/.github/workflows/helm-pages.yml +++ b/.github/workflows/helm-pages.yml @@ -109,7 +109,7 @@ jobs: python3 scripts/write_github_output.py release_tag "${release_tag}" - name: Set up Helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: v3.15.2 diff --git a/.github/workflows/promote-floating-tags.yml b/.github/workflows/promote-floating-tags.yml index f80a00584..972aed214 100644 --- a/.github/workflows/promote-floating-tags.yml +++ b/.github/workflows/promote-floating-tags.yml @@ -96,13 +96,13 @@ jobs: fi - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to GHCR - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index a241e9c64..d23c07f0b 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -118,19 +118,19 @@ jobs: --source-sha "${EXPECTED_SOURCE_SHA}" - name: Set up QEMU - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to GHCR - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -139,7 +139,7 @@ jobs: - name: Assemble and push Pulse server image (multi-arch) if: matrix.image == 'server' id: build_server_image - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . target: runtime_prebuilt @@ -157,7 +157,7 @@ jobs: - name: Attest Pulse server image on Docker Hub if: matrix.image == 'server' - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: docker.io/rcourtman/pulse subject-digest: ${{ steps.build_server_image.outputs.digest }} @@ -166,7 +166,7 @@ jobs: - name: Attest Pulse server image on GHCR if: matrix.image == 'server' - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: ghcr.io/${{ github.repository_owner }}/pulse subject-digest: ${{ steps.build_server_image.outputs.digest }} @@ -176,7 +176,7 @@ jobs: - name: Assemble and push Pulse control-plane image (multi-arch) if: matrix.image == 'control-plane' id: build_control_plane_image - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: context: . file: deploy/provider-msp/Dockerfile.control-plane @@ -195,7 +195,7 @@ jobs: - name: Attest Pulse control-plane image on Docker Hub if: matrix.image == 'control-plane' - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: docker.io/rcourtman/pulse-control-plane subject-digest: ${{ steps.build_control_plane_image.outputs.digest }} @@ -204,7 +204,7 @@ jobs: - name: Attest Pulse control-plane image on GHCR if: matrix.image == 'control-plane' - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: ghcr.io/${{ github.repository_owner }}/pulse-control-plane subject-digest: ${{ steps.build_control_plane_image.outputs.digest }} @@ -249,16 +249,16 @@ jobs: persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to GHCR - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/publish-helm-chart.yml b/.github/workflows/publish-helm-chart.yml index 9b785a382..15113ce11 100644 --- a/.github/workflows/publish-helm-chart.yml +++ b/.github/workflows/publish-helm-chart.yml @@ -108,7 +108,7 @@ jobs: echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT" - name: Set up Helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: v3.15.2 @@ -175,7 +175,7 @@ jobs: # chart push authenticated through Helm, and authenticate the OCI # attestation client explicitly before it publishes provenance. - name: Authenticate OCI attestation client with GHCR - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -203,7 +203,7 @@ jobs: echo "chart_digest=${digests[0]}" >> "$GITHUB_OUTPUT" - name: Attest exact-version OCI chart - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-name: ghcr.io/${{ github.repository_owner }}/pulse-chart/pulse subject-digest: ${{ steps.push.outputs.chart_digest }} diff --git a/.github/workflows/qualify-release-containers.yml b/.github/workflows/qualify-release-containers.yml index 95fc6212e..3bd1e1cd5 100644 --- a/.github/workflows/qualify-release-containers.yml +++ b/.github/workflows/qualify-release-containers.yml @@ -113,7 +113,7 @@ jobs: ' - name: Set up Helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: v3.15.2 diff --git a/.github/workflows/recover-demo-server.yml b/.github/workflows/recover-demo-server.yml index d15713071..c482081c5 100644 --- a/.github/workflows/recover-demo-server.yml +++ b/.github/workflows/recover-demo-server.yml @@ -71,7 +71,7 @@ jobs: - name: Connect to the governed demo network id: tailscale - uses: tailscale/github-action@306e68a486fd2350f2bfc3b19fcd143891a4a2d8 # v4 + uses: tailscale/github-action@780049a30b6ff5c378a9e7b389d15ece7a204888 # v4.1.3 with: oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} diff --git a/.github/workflows/recover-release-activation.yml b/.github/workflows/recover-release-activation.yml index 370aa0f17..8ee25fe50 100644 --- a/.github/workflows/recover-release-activation.yml +++ b/.github/workflows/recover-release-activation.yml @@ -179,7 +179,7 @@ jobs: echo "[OK] GitHub's stored RC packet still matches the immutable source-run manifest." - name: Set up Helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: v3.15.2 @@ -196,16 +196,16 @@ jobs: "${GITHUB_REPOSITORY}" >> "$GITHUB_OUTPUT" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to GHCR - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index b40377503..9cfd24ec1 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -44,13 +44,13 @@ jobs: - name: Set up Helm if: ${{ github.event.schedule != '17 */6 * * *' }} - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: v3.15.2 - name: Set up Docker Buildx if: ${{ github.event.schedule != '17 */6 * * *' }} - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - name: Resolve the advertised stable release id: release diff --git a/.github/workflows/signpath-test-signing.yml b/.github/workflows/signpath-test-signing.yml index 2f57ebfbc..054815e6e 100644 --- a/.github/workflows/signpath-test-signing.yml +++ b/.github/workflows/signpath-test-signing.yml @@ -104,7 +104,7 @@ jobs: - name: Submit SignPath test-signing request id: signpath - uses: signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2 + uses: signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 # v2 with: api-token: ${{ secrets.SIGNPATH_API_TOKEN }} organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} diff --git a/.github/workflows/update-demo-server.yml b/.github/workflows/update-demo-server.yml index f76e4230a..663be34f7 100644 --- a/.github/workflows/update-demo-server.yml +++ b/.github/workflows/update-demo-server.yml @@ -363,7 +363,7 @@ jobs: - name: Tailscale id: tailscale - uses: tailscale/github-action@306e68a486fd2350f2bfc3b19fcd143891a4a2d8 # v4 + uses: tailscale/github-action@780049a30b6ff5c378a9e7b389d15ece7a204888 # v4.1.3 with: oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 5130c6efd..4f31bda5a 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -5407,3 +5407,22 @@ Retained provider delivery verification establishes archive SHA256 a valid pinned SSH signature and the extracted request expression with absent and synthetic email. It does not establish installed onboarding, server acceptance or legal identity. See the qualification evidence below. + +### Pinned release action consumer compatibility + +The grouped release actions use immutable revisions recorded in +`scripts/release_control/action_consumer_manifests.json`: Attest 4.2.2, +Helm setup 5.0.1, Docker build/push 7.3.0, login 4.6.0, Buildx/QEMU +setup 4.3.0, SignPath v2 and Tailscale 4.1.3. The snapshot records the +upstream action manifest hash, accepted inputs/outputs and Node 24 entry points. +It is reviewed input, not runtime attestation or permission to publish. + +`test_reviewed_action_manifests_cover_all_release_consumers` checks every +workflow consumer against those upstream inputs and immutable revisions. +Helm remains 3.15.2; attestation keeps checksum subjects; signing keeps the +existing organisation/token interface. Existing signing, checksum, Docker +promotion and exact-dispatch contract tests remain required. Action upgrades +must not alter permissions, environments, source identity, rollback, signing +backend selection or release qualification. Local contract success does not +establish hosted action execution, signature acceptance, image publication or +production deployment. diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 8ca59fcef..972b5aaba 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -157,7 +157,7 @@ func TestSecurityScanRevalidatesLatestStableDelivery(t *testing.T) { `github.event_name != 'workflow_run' || !contains(github.event.workflow_run.display_title, '-')`, "release-continuity:", "Latest stable release continuity", - "docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5", + "docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e", `"repos/${REPOSITORY}/releases/latest"`, `scripts/release_control/release_continuity.py release`, "release-diagnostic.json", @@ -1747,7 +1747,7 @@ func TestReleaseCandidateRequiresPlatformNativeAgentSigning(t *testing.T) { `sign-windows-agent:`, `collect-windows-signing:`, `windows_signing_backend:`, - `signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2`, + `signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 # v2`, `github-artifact-id: ${{ steps.upload-unsigned-windows.outputs.artifact-id }}`, `wait-for-completion: false`, `windows-signing-request.json`, @@ -1852,7 +1852,7 @@ func TestReleaseWorkflowsUseSecretSafeAttestedImageBuilds(t *testing.T) { `does not trust the configured release signing key.`, `id-token: write`, `attestations: write`, - `uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4`, + `uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2`, } containerJob := workflowJobBlock(t, string(qualifierWorkflowBytes), "qualify") for _, needle := range []string{ @@ -2272,7 +2272,7 @@ func TestUpdateDemoWorkflowUsesGovernedNetworkPath(t *testing.T) { workflow := string(workflowBytes) + "\n" + string(profileBytes) required := []string{ `- name: Tailscale`, - `uses: tailscale/github-action@306e68a486fd2350f2bfc3b19fcd143891a4a2d8 # v4`, + `uses: tailscale/github-action@780049a30b6ff5c378a9e7b389d15ece7a204888 # v4.1.3`, `oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}`, `oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}`, `tags: tag:infra`, @@ -3189,7 +3189,7 @@ func TestPublishHelmChartReachableViaWorkflowCall(t *testing.T) { `name: Verify public GHCR chart identity and provenance`, `helm registry logout ghcr.io || true`, `name: Authenticate OCI attestation client with GHCR`, - `uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0`, + `uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0`, `registry: ghcr.io`, `username: ${{ github.actor }}`, `password: ${{ github.token }}`, diff --git a/scripts/release_control/action_consumer_manifests.json b/scripts/release_control/action_consumer_manifests.json new file mode 100644 index 000000000..114486075 --- /dev/null +++ b/scripts/release_control/action_consumer_manifests.json @@ -0,0 +1,225 @@ +{ + "actions/attest": { + "sha": "1e69f48acb82d1966a394da916b4c1698aa569d6", + "manifest_sha256": "9e4a1b808433f9ec87120b534e11fc35469a039bdbdc62b019441444c9ad0449", + "inputs": [ + "create-storage-record", + "github-token", + "predicate", + "predicate-path", + "predicate-type", + "push-to-registry", + "sbom-path", + "show-summary", + "subject-checksums", + "subject-digest", + "subject-name", + "subject-path", + "subject-version" + ], + "outputs": [ + "attestation-id", + "attestation-url", + "bundle-path", + "storage-record-ids" + ], + "runs": { + "using": "node24", + "main": "./dist/index.js" + } + }, + "azure/setup-helm": { + "sha": "9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310", + "manifest_sha256": "a80a29bbe87c77663a47a75d4163cf54f0bd24ae2485b868dd81d6758ec29647", + "inputs": [ + "downloadBaseURL", + "token", + "version" + ], + "outputs": [ + "helm-path" + ], + "runs": { + "using": "node24", + "main": "lib/index.js" + } + }, + "docker/build-push-action": { + "sha": "53b7df96c91f9c12dcc8a07bcb9ccacbed38856a", + "manifest_sha256": "5c1ade75252eabe541b96cc39a2165a07c8850f9bcd3f9b75f5394fbdfb215fd", + "inputs": [ + "add-hosts", + "allow", + "annotations", + "attests", + "build-args", + "build-contexts", + "builder", + "cache-from", + "cache-to", + "call", + "cgroup-parent", + "context", + "file", + "github-token", + "labels", + "load", + "network", + "no-cache", + "no-cache-filters", + "outputs", + "platforms", + "provenance", + "pull", + "push", + "sbom", + "secret-envs", + "secret-files", + "secrets", + "shm-size", + "ssh", + "tags", + "target", + "ulimit" + ], + "outputs": [ + "digest", + "imageid", + "metadata" + ], + "runs": { + "using": "node24", + "main": "dist/index.cjs", + "post": "dist/index.cjs" + } + }, + "docker/login-action": { + "sha": "dbcb813823bdd20940b903addbd779551569679f", + "manifest_sha256": "e65e6bdc57fc10adcee036a393229aae6b9a9f451b69528f4ff50d4b0e93f156", + "inputs": [ + "ecr", + "logout", + "password", + "registry", + "registry-auth", + "scope", + "username" + ], + "outputs": [], + "runs": { + "using": "node24", + "main": "dist/index.cjs", + "post": "dist/index.cjs" + } + }, + "docker/setup-buildx-action": { + "sha": "37fe631027851001ddb9b187196cc803df7f5f0e", + "manifest_sha256": "9c55229011a0566758962f36c74309c8ab06f8905a5997591a6a46319401ab38", + "inputs": [ + "append", + "buildkitd-config", + "buildkitd-config-inline", + "buildkitd-flags", + "cache-binary", + "cleanup", + "driver", + "driver-opts", + "endpoint", + "keep-state", + "name", + "platforms", + "use", + "version" + ], + "outputs": [ + "driver", + "endpoint", + "flags", + "name", + "nodes", + "platforms", + "status" + ], + "runs": { + "using": "node24", + "main": "dist/index.cjs", + "post": "dist/index.cjs" + } + }, + "docker/setup-qemu-action": { + "sha": "1f40c72289eff860ee54a304f1438e3cff362e0a", + "manifest_sha256": "9c74c218c37b889976b0af59861f3fceaec87bd12da00bf6dc37ded77d8954f8", + "inputs": [ + "cache-image", + "image", + "platforms", + "reset" + ], + "outputs": [ + "platforms" + ], + "runs": { + "using": "node24", + "main": "dist/index.cjs", + "post": "dist/index.cjs" + } + }, + "signpath/github-action-submit-signing-request": { + "sha": "c92b958760219087e01f8d67a1669ed57afe2627", + "manifest_sha256": "d0b52fdfa234d87c1ff4d21c06a91d2b6e0dd45e1a8866ad3b58cbdeb9aef326", + "inputs": [ + "api-token", + "artifact-configuration-slug", + "connector-url", + "download-signed-artifact-timeout-in-seconds", + "github-artifact-id", + "github-token", + "organization-id", + "output-artifact-directory", + "parameters", + "project-slug", + "service-unavailable-timeout-in-seconds", + "signing-policy-slug", + "skip-decompress", + "wait-for-completion", + "wait-for-completion-timeout-in-seconds" + ], + "outputs": [ + "signed-artifact-download-url", + "signing-request-id", + "signing-request-web-url", + "signpath-api-url" + ], + "runs": { + "using": "node24", + "main": "index.js" + } + }, + "tailscale/github-action": { + "sha": "780049a30b6ff5c378a9e7b389d15ece7a204888", + "manifest_sha256": "d63e612f47dad1520586efec4b79f2c7f0e0aadabeeca53709aa616202e43c6c", + "inputs": [ + "args", + "audience", + "authkey", + "hostname", + "oauth-client-id", + "oauth-secret", + "ping", + "retry", + "sha256sum", + "statedir", + "tags", + "tailscaled-args", + "timeout", + "use-cache", + "version" + ], + "outputs": [], + "runs": { + "using": "node24", + "main": "dist/index.js", + "post": "dist/logout/index.js" + } + } +} diff --git a/scripts/release_control/release_promotion_policy_support_test.py b/scripts/release_control/release_promotion_policy_support_test.py index ad964b878..19bcc9d5b 100644 --- a/scripts/release_control/release_promotion_policy_support_test.py +++ b/scripts/release_control/release_promotion_policy_support_test.py @@ -10,6 +10,37 @@ from release_promotion_policy_support import ( class ReleasePromotionPolicySupportTest(unittest.TestCase): + def test_action_inputs_do_not_satisfy_dispatch_requirements(self) -> None: + # Signing action inputs are nested consumer data, not callable workflow + # inputs. A pin upgrade must not make this dispatch guard accept them. + content = """ +on: + workflow_dispatch: + inputs: + version: + type: string +jobs: + sign: + runs-on: ubuntu-24.04 + steps: + - uses: signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 + with: + organization-id: fixture + api-token: fixture + expected_head_sha: fixture +""" + self.assertEqual(parse_workflow_dispatch_inputs(content), ("version",)) + with patch("release_promotion_policy_support.branch_workflow_text", return_value=content), patch( + "release_promotion_policy_support.origin_default_branch", return_value="main" + ): + self.assertEqual( + missing_workflow_dispatch_inputs( + workflow_path=".github/workflows/signpath-test-signing.yml", + required_inputs=("version", "expected_head_sha"), + ), + ("main", ("expected_head_sha",)), + ) + def test_parse_workflow_dispatch_inputs_reads_top_level_inputs(self) -> None: content = """ name: Example diff --git a/scripts/release_control/release_promotion_policy_test.py b/scripts/release_control/release_promotion_policy_test.py index 192c69d33..f0f7a9d89 100644 --- a/scripts/release_control/release_promotion_policy_test.py +++ b/scripts/release_control/release_promotion_policy_test.py @@ -302,6 +302,40 @@ STAGED_GOVERNANCE_INPUT_ERRORS = ( class ReleasePromotionPolicyTest(unittest.TestCase): + + def test_reviewed_action_manifests_cover_all_release_consumers(self) -> None: + # Snapshot is derived from each immutable upstream action.yml, not from + # our consumers: unknown inputs therefore fail rather than being blessed. + manifests = read_json("scripts/release_control/action_consumer_manifests.json") + seen = set() + for path in (REPO_ROOT / ".github/workflows").glob("*.yml"): + workflow = yaml.safe_load(read(str(path.relative_to(REPO_ROOT)))) + for job in workflow.get("jobs", {}).values(): + for step in job.get("steps", []): + action, _, revision = step.get("uses", "").partition("@") + if action not in manifests: + continue + with self.subTest(workflow=path.name, action=action): + manifest = manifests[action] + self.assertEqual(revision, manifest["sha"]) + self.assertEqual(manifest["runs"]["using"], "node24") + self.assertRegex(manifest["manifest_sha256"], r"^[0-9a-f]{64}$") + self.assertLessEqual(set(step.get("with", {})), set(manifest["inputs"])) + seen.add(action) + if action == "azure/setup-helm": + self.assertEqual(str(step["with"]["version"]).lstrip("v"), "3.15.2") + if action == "actions/attest": + if path.name in {"publish-docker.yml", "publish-helm-chart.yml"}: + self.assertIn("subject-name", step["with"]) + self.assertIn("subject-digest", step["with"]) + self.assertTrue(step["with"]["push-to-registry"]) + else: + self.assertIn("subject-checksums", step["with"]) + if action == "signpath/github-action-submit-signing-request": + self.assertIn("api-token", step["with"]) + self.assertIn("organization-id", step["with"]) + self.assertEqual(seen, set(manifests)) + def setUp(self) -> None: if USE_STAGED_GOVERNANCE and not REQUIRES_STAGED_GOVERNANCE_INPUTS: self.skipTest("staged slice does not touch the promotion-proof surface") @@ -1546,7 +1580,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): workflow, ) self.assertIn( - "signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2", + "signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 # v2", workflow, ) self.assertIn("signedArtifactsPublished = $false", workflow) @@ -1924,7 +1958,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("windows_signing_backend: signpath", content) self.assertIn('if [[ "$REQUIRE_WINDOWS_SIGNING" == "true" ]]', candidate_workflow) self.assertIn("inputs.require_windows_signing", candidate_workflow) - self.assertIn("signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2", candidate_workflow) + self.assertIn("signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 # v2", candidate_workflow) self.assertIn("github-artifact-id: ${{ steps.upload-unsigned-windows.outputs.artifact-id }}", candidate_workflow) self.assertIn("windows-signing-evidence.json", candidate_workflow) for signpath_setting in ( @@ -1959,7 +1993,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("bash .github/scripts/setup-demo-ssh.sh", update_demo_workflow) self.assertIn("bash .github/scripts/check-demo-reachability.sh", update_demo_workflow) self.assertIn("ping: ${{ secrets.DEMO_SERVER_HOST }}", update_demo_workflow) - self.assertIn("tailscale/github-action@306e68a486fd2350f2bfc3b19fcd143891a4a2d8 # v4", update_demo_workflow) + self.assertIn("tailscale/github-action@780049a30b6ff5c378a9e7b389d15ece7a204888 # v4.1.3", update_demo_workflow) self.assertIn("uses: ./.github/workflows/update-demo-server.yml", deploy_demo_workflow) self.assertIn("verify_only: true", deploy_demo_workflow) self.assertIn('MAX_SSH_SETUP_ATTEMPTS="${DEMO_SSH_SETUP_ATTEMPTS:-3}"', demo_ssh_helper) @@ -2003,7 +2037,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("id-token: write", candidate_workflow) self.assertIn("attestations: write", candidate_workflow) self.assertIn( - "uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4", + "uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2", candidate_workflow, ) self.assertIn( @@ -2318,7 +2352,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("fail-fast: false", publish) self.assertIn("if: matrix.image == 'server'", publish) self.assertIn("if: matrix.image == 'control-plane'", publish) - self.assertIn("uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4", publish) + self.assertIn("uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2", publish) self.assertIn("subject-name: docker.io/rcourtman/pulse", publish) self.assertIn("subject-name: ghcr.io/${{ github.repository_owner }}/pulse", publish) # pulse-agent ships as release-asset binaries, not as a Docker image @@ -2362,7 +2396,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("tag: latest", dry_run_workflow) self.assertIn("verify_only: true", dry_run_workflow) self.assertIn("Verify Current Stable Demo Path (No Mutation)", dry_run_workflow) - self.assertIn("tailscale/github-action@306e68a486fd2350f2bfc3b19fcd143891a4a2d8 # v4", demo) + self.assertIn("tailscale/github-action@780049a30b6ff5c378a9e7b389d15ece7a204888 # v4.1.3", demo) self.assertIn("oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}", demo) self.assertIn("oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}", demo) self.assertIn("ping: ${{ secrets.DEMO_SERVER_HOST }}", demo)