From f6b45a6999517a2664bd0405b71a2f2ab18759eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 03:39:46 +0000 Subject: [PATCH 1/4] chore: analyzing non-deterministic pin regression Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/release.lock.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index 9840a38941a..b670dfbb32e 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -1,5 +1,6 @@ # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"2d6e82aec6fea9061e1ef1cbb085324e6ed6c079230d70bbbb96611bb18cf7cf","body_hash":"646353d7bb4e5523bc85349c2cce38188190095a303f83cc95961ff145a47043","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"anchore/sbom-action","sha":"e22c389904149dbc22b58101806040fa8d37a610","version":"v0.24.0"},{"repo":"docker/build-push-action","sha":"53b7df96c91f9c12dcc8a07bcb9ccacbed38856a","version":"v7.3.0"},{"repo":"docker/login-action","sha":"af1e73f918a031802d376d3c8bbc3fe56130a9b0","version":"v4.4.0"},{"repo":"docker/metadata-action","sha":"dc802804100637a589fabce1cb79ff13a1411302","version":"v6.2.0"},{"repo":"docker/setup-buildx-action","sha":"bb05f3f5519dd87d3ba754cc423b652a5edd6d2c","version":"v4.2.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]}# This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"anchore/sbom-action","sha":"e22c389904149dbc22b58101806040fa8d37a610","version":"v0.24.0"},{"repo":"docker/build-push-action","sha":"53b7df96c91f9c12dcc8a07bcb9ccacbed38856a","version":"v7.3.0"},{"repo":"docker/login-action","sha":"af1e73f918a031802d376d3c8bbc3fe56130a9b0","version":"v4.4.0"},{"repo":"docker/metadata-action","sha":"dc802804100637a589fabce1cb79ff13a1411302","version":"v6.2.0"},{"repo":"docker/setup-buildx-action","sha":"bb05f3f5519dd87d3ba754cc423b652a5edd6d2c","version":"v4.2.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) From 3c09f74b9bf7614605477d8cc8a5e2f8f3c2f361 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 03:49:26 +0000 Subject: [PATCH 2/4] fix: preserve version comment for SHA-pinned actions when SkipHardcodedFallback is set Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic-auto-upgrade.yml | 2 +- pkg/actionpins/actionpins.go | 8 +++++-- pkg/actionpins/actionpins_internal_test.go | 27 ++++++++++++++++++---- pkg/actionpins/spec_test.go | 27 ++++++++++++++++++++++ 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/.github/workflows/agentic-auto-upgrade.yml b/.github/workflows/agentic-auto-upgrade.yml index e169cea32b6..7035101ee35 100644 --- a/.github/workflows/agentic-auto-upgrade.yml +++ b/.github/workflows/agentic-auto-upgrade.yml @@ -34,7 +34,7 @@ name: Agentic Auto-Upgrade on: schedule: - - cron: "11 4 * * 6" # Weekly (auto-upgrade) + - cron: "21 3 * * 5" # Weekly (auto-upgrade) workflow_dispatch: permissions: diff --git a/pkg/actionpins/actionpins.go b/pkg/actionpins/actionpins.go index 0b99764ccb1..050b84e0925 100644 --- a/pkg/actionpins/actionpins.go +++ b/pkg/actionpins/actionpins.go @@ -391,8 +391,12 @@ func resolveActionPinFromHardcodedPins(actionRepo, version string, isAlreadySHA // When the caller is targeting a non-github.com host (e.g. GHES/GHEC), the // dynamic resolver already failed because it queried the wrong host. Silently // falling back to bundled pins in that case produces unverified SHA pins and - // masks the real problem, so skip this fallback entirely. - if ctx.SkipHardcodedFallback { + // masks the real problem, so skip this fallback for version→SHA resolution. + // + // However, when version is already a SHA (isAlreadySHA), the lookup is purely + // SHA→version (to find a human-readable version label for the comment). That + // operation carries no security risk regardless of host, so it is always allowed. + if ctx.SkipHardcodedFallback && !isAlreadySHA { actionPinsLog.Printf("SkipHardcodedFallback set, skipping hardcoded pin lookup for %s@%s", actionRepo, version) return "", false } diff --git a/pkg/actionpins/actionpins_internal_test.go b/pkg/actionpins/actionpins_internal_test.go index bed6fa80358..b19da35b346 100644 --- a/pkg/actionpins/actionpins_internal_test.go +++ b/pkg/actionpins/actionpins_internal_test.go @@ -430,14 +430,33 @@ func TestResolveNonStrictHardcodedPin_FallsBackToHighestWhenNoCompatible(t *test } func TestResolveActionPinFromHardcodedPins_SkipHardcodedFallback(t *testing.T) { - t.Run("returns false immediately when SkipHardcodedFallback is set", func(t *testing.T) { + t.Run("returns false immediately when SkipHardcodedFallback is set and version is a tag", func(t *testing.T) { ctx := &PinContext{SkipHardcodedFallback: true, Warnings: make(map[string]bool)} - // actions/checkout has hardcoded pins, but SkipHardcodedFallback should prevent use + // actions/checkout has hardcoded pins, but SkipHardcodedFallback should prevent version→SHA lookup result, ok := resolveActionPinFromHardcodedPins("actions/checkout", "v4", false, ctx) - assert.False(t, ok, "Expected SkipHardcodedFallback to prevent hardcoded pin lookup") - assert.Empty(t, result, "Expected no pinned result when SkipHardcodedFallback is set") + assert.False(t, ok, "Expected SkipHardcodedFallback to prevent version→SHA hardcoded pin lookup") + assert.Empty(t, result, "Expected no pinned result when SkipHardcodedFallback is set for version tag") + }) + + t.Run("allows SHA→version lookup even when SkipHardcodedFallback is set", func(t *testing.T) { + // This is the regression test for the non-deterministic pinning bug. + // When a workflow already pins an action with a SHA (e.g. @9c091bb... # v7.0.0) + // and SkipHardcodedFallback is true (e.g. because GH_HOST is a non-github.com host), + // the SHA→version lookup must still succeed to preserve the human-readable version comment. + // Without the fix, the fallback would emit FormatPinnedActionReference(repo, sha, sha), + // producing "# 9c091bb..." instead of "# v7.0.0". + latestPin, ok := GetLatestActionPinByRepo("actions/checkout") + require.True(t, ok, "expected embedded pin for actions/checkout") + + ctx := &PinContext{SkipHardcodedFallback: true, Warnings: make(map[string]bool)} + + result, found := resolveActionPinFromHardcodedPins("actions/checkout", latestPin.SHA, true, ctx) + + require.True(t, found, "Expected SHA→version lookup to succeed even with SkipHardcodedFallback=true") + assert.Equal(t, FormatPinnedActionReference("actions/checkout", latestPin.SHA, latestPin.Version), result, + "Expected version comment to use tag, not SHA") }) t.Run("allows hardcoded pins when SkipHardcodedFallback is not set", func(t *testing.T) { diff --git a/pkg/actionpins/spec_test.go b/pkg/actionpins/spec_test.go index 7a9283a9f3b..5e87c6a9bef 100644 --- a/pkg/actionpins/spec_test.go +++ b/pkg/actionpins/spec_test.go @@ -360,6 +360,33 @@ func TestSpec_PublicAPI_ResolveActionPin_SkipHardcodedFallback(t *testing.T) { require.NotEmpty(t, result, "SkipHardcodedFallback=false should allow hardcoded pin lookup") assert.Contains(t, result, "actions/checkout@", "result should reference actions/checkout") }) + + t.Run("SHA-pinned action with SkipHardcodedFallback=true still produces version comment", func(t *testing.T) { + // Regression test for non-deterministic pin comments bug. + // + // When a workflow already uses a SHA-pinned action reference (e.g. + // actions/checkout@9c091bb... # v7.0.0) and SkipHardcodedFallback=true + // is set (triggered when GH_HOST points to a non-github.com host), the + // SHA→version lookup must still succeed so that the human-readable version + // tag is preserved in the comment. + // + // Before the fix, the hardcoded-pin lookup was skipped entirely when + // SkipHardcodedFallback=true, causing the fallback to emit + // FormatPinnedActionReference(repo, sha, sha) which produces "# " + // instead of "# v7.0.0", making the lock files non-deterministic. + latestPin, ok := actionpins.GetLatestActionPinByRepo("actions/checkout") + require.True(t, ok, "expected embedded pin for actions/checkout") + + ctx := &actionpins.PinContext{ + SkipHardcodedFallback: true, + Warnings: make(map[string]bool), + } + result, err := actionpins.ResolveActionPin("actions/checkout", latestPin.SHA, ctx) + require.NoError(t, err, "SHA resolution should not return an error") + expected := actionpins.FormatPinnedActionReference("actions/checkout", latestPin.SHA, latestPin.Version) + assert.Equal(t, expected, result, "SHA-pinned action should use version tag as comment, not the SHA itself") + assert.Contains(t, result, "# "+latestPin.Version, "version comment must use the human-readable tag, not the SHA") + }) } // TestSpec_PublicAPI_ResolveLatestActionPin validates latest-version resolution behavior. From a572a3506bfcf8c92c2d2e33f84e638dc693b733 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 03:53:04 +0000 Subject: [PATCH 3/4] revert: restore agentic-auto-upgrade.yml schedule (unrelated fuzzy-schedule churn) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic-auto-upgrade.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/agentic-auto-upgrade.yml b/.github/workflows/agentic-auto-upgrade.yml index 7035101ee35..e169cea32b6 100644 --- a/.github/workflows/agentic-auto-upgrade.yml +++ b/.github/workflows/agentic-auto-upgrade.yml @@ -34,7 +34,7 @@ name: Agentic Auto-Upgrade on: schedule: - - cron: "21 3 * * 5" # Weekly (auto-upgrade) + - cron: "11 4 * * 6" # Weekly (auto-upgrade) workflow_dispatch: permissions: From 6c23e606d986bb43c8be07791f14cc894a4755fd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 06:29:35 +0000 Subject: [PATCH 4/4] docs: clarify SkipHardcodedFallback scope Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic-auto-upgrade.yml | 2 +- pkg/actionpins/actionpins.go | 13 +++++++------ pkg/actionpins/spec_test.go | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/agentic-auto-upgrade.yml b/.github/workflows/agentic-auto-upgrade.yml index e169cea32b6..7035101ee35 100644 --- a/.github/workflows/agentic-auto-upgrade.yml +++ b/.github/workflows/agentic-auto-upgrade.yml @@ -34,7 +34,7 @@ name: Agentic Auto-Upgrade on: schedule: - - cron: "11 4 * * 6" # Weekly (auto-upgrade) + - cron: "21 3 * * 5" # Weekly (auto-upgrade) workflow_dispatch: permissions: diff --git a/pkg/actionpins/actionpins.go b/pkg/actionpins/actionpins.go index 050b84e0925..34abf552de4 100644 --- a/pkg/actionpins/actionpins.go +++ b/pkg/actionpins/actionpins.go @@ -98,11 +98,12 @@ type PinContext struct { Warnings map[string]bool // RecordResolutionFailure receives unresolved pinning failures for auditing. RecordResolutionFailure func(f ResolutionFailure) - // SkipHardcodedFallback skips the entire hardcoded-pin lookup (both exact/strict - // and non-strict matches) when dynamic resolution fails. Set this when GH_HOST is - // configured to a non-github.com host: the dynamic resolver will query the wrong - // host and fail, so silently falling back to bundled pins would produce unverified - // SHA pins and mask the real misconfiguration. + // SkipHardcodedFallback skips version→SHA hardcoded fallback when dynamic + // resolution fails. Exact SHA→version labeling is still allowed so + // already-pinned actions keep their human-readable version comments. Set this + // when GH_HOST is configured to a non-github.com host: the dynamic resolver + // will query the wrong host and fail, so silently falling back to bundled pins + // would produce unverified SHA pins and mask the real misconfiguration. SkipHardcodedFallback bool // Mappings redirects action repository@version references to replacement // repository@version references before pin resolution. Keys and values use @@ -397,7 +398,7 @@ func resolveActionPinFromHardcodedPins(actionRepo, version string, isAlreadySHA // SHA→version (to find a human-readable version label for the comment). That // operation carries no security risk regardless of host, so it is always allowed. if ctx.SkipHardcodedFallback && !isAlreadySHA { - actionPinsLog.Printf("SkipHardcodedFallback set, skipping hardcoded pin lookup for %s@%s", actionRepo, version) + actionPinsLog.Printf("SkipHardcodedFallback set, skipping version→SHA hardcoded pin lookup for %s@%s", actionRepo, version) return "", false } diff --git a/pkg/actionpins/spec_test.go b/pkg/actionpins/spec_test.go index 5e87c6a9bef..07e822993a6 100644 --- a/pkg/actionpins/spec_test.go +++ b/pkg/actionpins/spec_test.go @@ -334,8 +334,8 @@ func TestSpec_PublicAPI_ResolveActionPin_EnforcePinned(t *testing.T) { } // TestSpec_PublicAPI_ResolveActionPin_SkipHardcodedFallback validates that setting -// PinContext.SkipHardcodedFallback=true prevents the embedded hardcoded pins from -// being consulted, even for a well-known action that is present in the embedded data. +// PinContext.SkipHardcodedFallback=true blocks version→SHA fallback against the +// embedded hardcoded pins while still allowing SHA→version comment labeling. func TestSpec_PublicAPI_ResolveActionPin_SkipHardcodedFallback(t *testing.T) { t.Run("known action with SkipHardcodedFallback=true returns empty result", func(t *testing.T) { // actions/checkout has entries in the embedded pins.