Skip to content

Refresh container pins before daily image scans - #51083

Merged
pelikhan merged 9 commits into
mainfrom
copilot/rebuild-scanned-container-images
Aug 7, 2026
Merged

Refresh container pins before daily image scans#51083
pelikhan merged 9 commits into
mainfrom
copilot/rebuild-scanned-container-images

Conversation

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The daily container image security scan could keep scanning stale digest-pinned images even after mutable upstream tags were rebuilt with patched packages. This left real critical CVEs visible in the gate until the local container pin cache was refreshed.

  • Compile support

    • Added gh aw compile --refresh-container-pins.
    • Refreshes existing container digest pins before compiling so generated lock files embed current image digests.
  • Daily scan workflow

    • Updated the image scan workflow to refresh pins before running Syft, Grype, and Grant.
gh aw compile --refresh-container-pins --syft --grype --grant
  • Pin cache updates
    • Pruned stale container pins no longer referenced by compiled workflows.
    • Refreshed the current python:alpine digest and synced embedded action-pin data.

Run context: https://github.com/github/gh-aw/actions/runs/31188101705> Generated by 👨‍🍳 PR Sous Chef · gpt54 · 14.3 AIC · ⊞ 5.9K ·

Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 10.3 AIC · ⊞ 5.9K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Comment thread cmd/gh-aw/main.go Outdated
compileCmd.Flags().BoolP("force", "f", false, "Force overwrite of existing dependency files (only applies when --dependabot is set; e.g., dependabot.yml)")
compileCmd.Flags().Bool("refresh-stop-time", false, "Force regeneration of stop-after times instead of preserving existing values from lock files")
compileCmd.Flags().Bool("force-refresh-action-pins", false, "Force refresh of action pins by clearing the cache and resolving all action SHAs from GitHub API")
compileCmd.Flags().Bool("refresh-container-pins", false, "Refresh existing container image digest pins before compiling")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot align name with force refresh action pins, force prefix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 654bc81 by renaming the compile flag to --force-refresh-container-pins and updating the workflow, tests, docs, and changeset references.

Copilot AI changed the title [WIP] Rebuild scanned container images to address critical CVEs Refresh container pins before daily image scans Aug 7, 2026
Copilot AI requested a review from pelikhan August 7, 2026 14:02
@pelikhan
pelikhan marked this pull request as ready for review August 7, 2026 14:07
Copilot AI balanced review requested due to automatic review settings August 7, 2026 14:07
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Design Decision Gate 🏗️. Review the logs for details.

No ADR enforcement needed: PR #51083 does not have the 'implementation' label and has only 12 new lines of code in business logic directories (threshold: 100).

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Matt Pocock Skills Reviewer. Review the logs for details.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Test Quality Sentinel. Review the logs for details.

test

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for PR Code Quality Reviewer. Review the logs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds container-pin refresh support so daily security scans target current mutable image tags rather than stale cached digests.

Changes:

  • Adds and propagates compile --refresh-container-pins.
  • Refreshes pins before daily Syft, Grype, and Grant scans.
  • Updates generated pin caches and workflow lock files.
Show a summary per file
File Description
pkg/cli/compile_config.go Adds refresh configuration.
pkg/cli/compile_orchestrator.go Runs pin refresh before compilation.
cmd/gh-aw/main.go Registers and propagates the CLI flag.
cmd/gh-aw/compile_flags_test.go Tests flag registration and propagation.
.github/workflows/daily-squid-image-scan.md Enables refresh for daily scans.
.github/workflows/daily-squid-image-scan.lock.yml Recompiles the scan workflow.
.github/workflows/mcp-inspector.lock.yml Embeds the refreshed Python digest.
.github/aw/actions-lock.json Refreshes and prunes cached pins.
pkg/actionpins/data/action_pins.json Synchronizes embedded action-pin data.
pkg/workflow/data/action_pins.json Synchronizes workflow pin data.
.changeset/patch-refresh-container-pins-before-scans.md Records the patch release change.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 11/11 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread pkg/cli/compile_orchestrator.go Outdated
Comment on lines +111 to +114
if config.RefreshContainerPins && !config.NoEmit {
compileOrchestratorLog.Print("Refreshing container image digest pins before compilation")
if _, err := updateContainerPins(ctx, defaultContainerPinUpdateDeps(), workflowDir, config.Verbose, containerPinUpdateOptions{refreshExisting: true}); err != nil {
return nil, fmt.Errorf("failed to refresh container pins: %w", err)
Comment thread cmd/gh-aw/main.go Outdated
compileCmd.Flags().BoolP("force", "f", false, "Force overwrite of existing dependency files (only applies when --dependabot is set; e.g., dependabot.yml)")
compileCmd.Flags().Bool("refresh-stop-time", false, "Force regeneration of stop-after times instead of preserving existing values from lock files")
compileCmd.Flags().Bool("force-refresh-action-pins", false, "Force refresh of action pins by clearing the cache and resolving all action SHAs from GitHub API")
compileCmd.Flags().Bool("refresh-container-pins", false, "Refresh existing container image digest pins before compiling")

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --refresh-container-pins flag is correctly wired end-to-end (CLI flag → compileCmdOptionsCompileConfig.RefreshContainerPins → orchestrator). The !config.NoEmit guard correctly skips the refresh in dry-run mode. Tests cover flag existence, default value, and propagation. Lock files and action-pin JSON files are consistently updated. No blocking issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 14.2 AIC · ⊞ 5.3K

@github-actions github-actions Bot mentioned this pull request Aug 7, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /codebase-design and /tdd — two targeted observations; no blocking issues found.

📋 Key Themes

Issues

  • Naming inconsistency (cmd/gh-aw/main.go line 771): --refresh-container-pins breaks the force- prefix pattern established by --force-refresh-action-pins. @pelikhan already flagged this inline.
  • Orchestrator error path untested (pkg/cli/compile_orchestrator.go line 115): the RefreshContainerPins branch calls updateContainerPins but there is no test that exercises the error-return path. Propagation relies solely on the struct-field tests.

Positive Highlights

  • ✅ Clean end-to-end wiring: config struct → flag registration → orchestrator guard, all consistent
  • !config.NoEmit guard correctly skips the network call in dry-run mode
  • ✅ Two focused unit tests added covering both flag presence and config propagation
  • ✅ Changeset entry is accurate and concise

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 36.5 AIC · ⊞ 7.1K
Comment /matt to run again

Comment thread cmd/gh-aw/main.go Outdated
compileCmd.Flags().BoolP("force", "f", false, "Force overwrite of existing dependency files (only applies when --dependabot is set; e.g., dependabot.yml)")
compileCmd.Flags().Bool("refresh-stop-time", false, "Force regeneration of stop-after times instead of preserving existing values from lock files")
compileCmd.Flags().Bool("force-refresh-action-pins", false, "Force refresh of action pins by clearing the cache and resolving all action SHAs from GitHub API")
compileCmd.Flags().Bool("refresh-container-pins", false, "Refresh existing container image digest pins before compiling")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The flag name --refresh-container-pins does not follow the force- prefix convention used by the adjacent --force-refresh-action-pins flag — users who know --force-refresh-action-pins will expect --force-refresh-container-pins.

💡 Suggested rename
compileCmd.Flags().Bool("force-refresh-container-pins", false, "Force refresh of container image digest pins before compiling")

Also rename struct fields: forceRefreshContainerPins in compileCmdOptions and ForceRefreshContainerPins in CompileConfig, mirroring ForceRefreshActionPins.

@copilot please address this.

Comment thread pkg/cli/compile_orchestrator.go Outdated
compileOrchestratorLog.Print("Refreshing container image digest pins before compilation")
if _, err := updateContainerPins(ctx, defaultContainerPinUpdateDeps(), workflowDir, config.Verbose, containerPinUpdateOptions{refreshExisting: true}); err != nil {
return nil, fmt.Errorf("failed to refresh container pins: %w", err)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The refresh path in the orchestrator has no integration test — only flag existence and config propagation are tested. If updateContainerPins returns an error (e.g. network failure on the runner), compile silently fails with no regression guard.

💡 What to add

Add a test in pkg/cli/ that sets RefreshContainerPins: true in CompileConfig, stubs or injects the updateContainerPins dependency to return an error, and asserts that CompileWorkflows surfaces the error rather than silently swallowing it. This would have caught any future breakage in the error-propagation path.

@copilot please address this.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

test body

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 67.4 AIC · ⊞ 7.7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 Test Quality Sentinel Report

Test Quality Score: 90/100 — Excellent

Analyzed 2 test(s): 2 design, 0 implementation, 0 violation(s).

📊 Metrics (2 tests)
Metric Value
Analyzed 2 (Go: 2, JS: 0)
✅ Design 2 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 2 (100%)
Duplicate clusters 0
Inflation Yes (~3:1; expected for multi-flag coverage file)
🚨 Violations 0
Test File Classification Issues
TestCompileCommandShortFlags cmd/gh-aw/compile_flags_test.go:7 design_test / behavioral_contract / high_value None
TestCompileOptionsPropagateRefreshContainerPins cmd/gh-aw/compile_flags_test.go:42 design_test / behavioral_contract / high_value None

Test Analysis

TestCompileCommandShortFlags: Verifies CLI flag contract — --force/-f, --logical-repo/-l, --grant default, and the new --refresh-container-pins flag with its default value (false). Each check guards against accidental flag renames or default changes. Error paths covered via t.Fatal on nil flag lookup. Design contract: high-value.

TestCompileOptionsPropagateRefreshContainerPins: Enforces that compileCmdOptions.refreshContainerPins propagates to CompileConfig.RefreshContainerPins. Prevents silent feature disablement of the new refresh behaviour. Design contract: high-value.

Inflation note: 15 test additions vs ~5 production additions in main.go gives ~3:1. Not a hard violation — the test covers multiple pre-existing flags alongside the new one.

Score Breakdown

design_ratio    = 2/2  → 40/40
edge_coverage   = 2/2  → 30/30
duplicate_score =       20/20
inflation_score =        0/10  (ratio > 2:1)
──────────────────────────────
Total           =       90/100

Verdict

passed. 0% implementation tests (threshold: 30%). No violations. Build tag (go/redacted):build !integration present. No mock libraries used.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Request changes — the refresh feature has real correctness/reliability gaps that undercut its stated purpose.

The intent (re-resolve container digests before daily scans so mutable tags don't hide patched CVEs) is sound, but the implementation has an unscoped, expensive refresh, a failure path that gets masked by || true in the very workflow this PR targets, and a silent no-op interaction with --no-emit. Test coverage for the new orchestrator logic itself is effectively absent.

Themes
  • Unscoped refresh cost: updateContainerPins with refreshExisting: true re-resolves every cached image in every lock file under workflowDir, not just images relevant to the current compile target — turning every --refresh-container-pins compile into a full-repo network refresh.
  • Masked failure in the primary consumer: The daily-scan workflow's compile ... || true swallows the new hard-fail error path (failed to refresh container pins: %w), so exactly the workflow this feature was built for can silently proceed on stale pins if the refresh itself fails.
  • Silent NoEmit interaction: RefreshContainerPins && !NoEmit has no logging when skipped, making the flag's actual effect undiscoverable in dry-run/validate contexts.
  • Test gap: New tests only assert CLI flag → config field wiring; the actual conditional-refresh/error-propagation logic added to compile_orchestrator.go is untested.

🔎 Code quality review by PR Code Quality Reviewer · auto · 114.2 AIC · ⊞ 7.8K
Comment /review to run again

Comment thread pkg/cli/compile_orchestrator.go Outdated
// Create and configure compiler
if config.RefreshContainerPins && !config.NoEmit {
compileOrchestratorLog.Print("Refreshing container image digest pins before compilation")
if _, err := updateContainerPins(ctx, defaultContainerPinUpdateDeps(), workflowDir, config.Verbose, containerPinUpdateOptions{refreshExisting: true}); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refresh resolves digests for every container image referenced in workflowDir's lock files, not just the ones relevant to the workflow(s) being compiled — every compile --refresh-container-pins invocation now does a full network round-trip (docker buildx/crane/pull) per cached image.

💡 Unscoped network refresh on every compile call

updateContainerPins calls collectImagesFromLockFiles(workflowDir), which scans all *.lock.yml files in the directory, and passes refreshExisting: true, which bypasses the cache-skip check in the loop:

if hasExistingPin && existingPin.Digest != "" && !opts.refreshExisting {
    // skip — never reached when refreshExisting is true
}

So the daily image-scan workflow (and any future caller passing this flag) triggers digest resolution attempts for every image in every compiled workflow in the repo, regardless of whether config.MarkdownFiles targets a subset. That is a large, unbounded amount of external network calls per compile invocation, with no scoping to the files actually being (re)compiled, and no way to opt into a narrower refresh.

Consider scoping the refresh to only the images referenced by config.MarkdownFiles (or documenting/accepting the full-repo cost explicitly), and/or adding a timeout/parallelism cap given the sequential loop with three fallback strategies each per image.

output_dir="/tmp/gh-aw/agent/image-scan"
mkdir -p "$output_dir"
"$GITHUB_WORKSPACE/gh-aw" compile --syft --grype --grant 2>&1 | tee "$output_dir/compile-output.txt" || true
"$GITHUB_WORKSPACE/gh-aw" compile --refresh-container-pins --syft --grype --grant 2>&1 | tee "$output_dir/compile-output.txt" || true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The || true at the end of this pipeline swallows the new hard failure path in compile_orchestrator.go (return nil, fmt.Errorf("failed to refresh container pins: %w", err)), so a broken pin refresh is silently ignored by the scan workflow.

💡 Refresh failures are masked by `|| true`

With --refresh-container-pins, if updateContainerPins returns an error (e.g. collectImagesFromLockFiles failing, or actionCache.Load()/Save() failing), CompileWorkflows now aborts the whole compile with an error. But this step still ends in || true, so the workflow proceeds as if compilation succeeded — with the security scan then running against a stale compile-output.txt/lock files, defeating the entire purpose of this PR (ensuring pins are current before scanning).

Consider checking the tee'd output or the underlying command's exit status for the container-pin-refresh failure explicitly before falling back to || true, or fail the job when the refresh step specifically errors.

Comment thread pkg/cli/compile_orchestrator.go Outdated
}

// Create and configure compiler
if config.RefreshContainerPins && !config.NoEmit {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting --refresh-container-pins together with --no-emit silently skips the refresh with zero feedback to the user.

💡 Silent no-op when NoEmit is set
if config.RefreshContainerPins && !config.NoEmit {

When a user runs gh aw compile --refresh-container-pins --no-emit (e.g. in a validate/dry-run CI job), this condition is false, so the refresh never runs — but there is no warning or log message explaining that the flag was ignored. The user has no way to tell whether pins were actually refreshed or the flag was silently dropped, which is confusing given the flag exists specifically to be explicit and predictable for the daily scan workflow.

Add a verbose/warning log (similar to the shellcheckEnabled skip-with-NoEmit handling a few lines above) so the behavior is discoverable.

Comment thread cmd/gh-aw/compile_flags_test.go Outdated
t.Fatalf("expected --grant default to be false, got %s", grantFlag.DefValue)
}

refreshContainerPinsFlag := compileCmd.Flags().Lookup("refresh-container-pins")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new test only verifies flag-to-config wiring; it does not cover the actual orchestrator behavior this PR introduces (the conditional refresh call, its interaction with NoEmit, or error propagation).

💡 No test coverage for the core new behavior

TestCompileOptionsPropagateRefreshContainerPins confirms compileCmdOptions{refreshContainerPins: true} maps to CompileConfig.RefreshContainerPins, which is trivial plumbing. The actual feature — the if config.RefreshContainerPins && !config.NoEmit { updateContainerPins(...) } branch added in pkg/cli/compile_orchestrator.go — has no direct test:

  • No test that updateContainerPins is invoked with refreshExisting: true when the flag is set.
  • No test that the branch is skipped when NoEmit is true.
  • No test that a updateContainerPins error surfaces as a CompileWorkflows error (failed to refresh container pins: %w).

Given compile_orchestrator_test.go/update_container_pins_test.go already have patterns for injecting fake containerPinUpdateDeps, this should be straightforward to add and would catch regressions in the new conditional logic.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please address the current review feedback, refresh the branch if GitHub can update it cleanly, and run the pr-finisher skill before handing back to maintainers.

Open review items to address (newest first):

Branch refresh was requested.

Run context: https://github.com/github/gh-aw/actions/runs/31188101705

Generated by 👨🍳 PR Sous Chef
Comment /souschef to run again

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 14.3 AIC · ⊞ 5.9K ·
Comment /souschef to run again

@pelikhan

pelikhan commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Copilot AI and others added 3 commits August 7, 2026 16:46
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please address the current review feedback, refresh the branch if GitHub can update it cleanly, and run the pr-finisher skill before handing back to maintainers.

Open review items to address (newest first):

Branch refresh was requested.

Run context: https://github.com/github/gh-aw/actions/runs/31200043159

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 10.3 AIC · ⊞ 5.9K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Done. I ran the pr-finisher workflow and pushed follow-up fixes in ca52a39 and 743ad9f, including forced container-pin refresh error propagation and orchestrator coverage for refresh/skip/error paths.

@pelikhan
pelikhan merged commit 52c29d4 into main Aug 7, 2026
4 checks passed
@pelikhan
pelikhan deleted the copilot/rebuild-scanned-container-images branch August 7, 2026 17:15
Copilot stopped work on behalf of pelikhan due to an error August 7, 2026 17:15
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.86.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[aw-failures] Rebuild scanned container images — critical CVEs have failed the security gate 5+ days straight

4 participants