Warn about container images the lockfile can't pin by nodeselector · Pull Request #193 · github/gh-actions-lock · GitHub
Skip to content

Warn about container images the lockfile can't pin - #193

Closed
nodeselector wants to merge 8 commits into
nodeselector-opaque-local-action-policyfrom
nodeselector-docker-image-pin-warnings
Closed

nodeselector wants to merge 8 commits into
nodeselector-opaque-local-action-policyfrom
nodeselector-docker-image-pin-warnings

Conversation

@nodeselector

@nodeselector nodeselector commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #168. Addresses #128 as far as static analysis allows.

Part 6 of 7 in the stack; it sits on #196 (nodeselector-opaque-local-action-policy). Review only this PR's commits: compare.

The lockfile pins action commits, not the container images Docker actions pull or build. This PR reports those images as a non-blocking container-image warning, in the terminal and in JSON. It reads only files the CLI already fetches and never pulls an image. valid and exit codes don't change.

Changes

  • Classifier (internal/dockerimage): any @sha256: reference is pinned, ${{ }} expressions are skipped, and a tag, an untagged image or a Dockerfile build warns. The remediation depends on who controls the image.
  • Repository images: docker:// steps, job container: and services: images, and the repository's own $/… Docker actions. These are also checked by --verify-local.
  • Remote actions: a Docker action's runs.image, docker:// steps in remote composites (including transitive ones), and Dockerfile-only actions, using the existing action-file lookup. required_by names the action.
  • Dedupe: each image is one finding per workflow, listing every job and step that uses it.
  • lock the pypa/gh-action-pypi-publish action because of an unpinnable local docker image #128: the local-action notice now says that a ./ action's container images aren't covered either.
  • Docs: a README coverage table, and the new category recorded in RELEASING. This also restores two Dependabot notes in RELEASING (schema support and the major-release classification).
  • Fix generated lockfile documentation link #185: includes the lockfile docs-link fix by @jihtsan, with authorship kept.

Reading order

  1. internal/dockerimage/image.go
  2. internal/workflowfile/workflowfile.go (containerImages, $/ Docker actions)
  3. internal/resolve/discovery.go and graph.go (images collected beside local-action notices)
  4. internal/pipeline/diagnose.go (appendContainerImageFinding)
  5. cmd/gh-actions-lock/format/terminal.go
  6. Tests: run_test.go (TestRemoteContainerImageWarnings, TestContainerImagesNamedInRepository), discovery_test.go, and the Docker scenarios in test/scenarios/catalog.yml.

Copilot AI max review requested due to automatic review settings October 20, 2026 05:28
@nodeselector
nodeselector requested a review from a team as a code owner October 20, 2026 05:28
@nodeselector
nodeselector requested a max review from Copilot and removed request for Copilot October 20, 2026 06:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

In-repository Docker actions silently bypass the new image warnings, leaving a required coverage gap.

No open issues 🎉

What changed in this PR

Adds static, non-blocking container-image warnings for images outside action-lockfile coverage. The supplied diff also includes the underlying release stack.

Changes:

  • Classify images and collect references from workflows and remote actions.
  • Report warnings in terminal, JSON, and offline verification without changing validity or exit codes.
  • Add regression coverage and document coverage limitations.

Review findings:

  • Moderate, 2 votes: internal/workflowfile/workflowfile.go:388 skips runs.image in in-repository $/… Docker actions, including offline checks. Collect those images, add coverage, and avoid empty Dockerfile built by attribution.
  • Moderate, 2 votes — optional: test/scenarios/catalog.yml:489 and three related scenarios duplicate command tests. Retain scenarios proving distinct real-binary behavior and explain that distinction, or remove the duplicates.
File Description
test/​scenarios/​catalog.go Updates fixture documentation.
test/​integration/​harness.rb Updates example repository text.
RELEASING.md Records release and compatibility considerations.
internal/​workflowfile/​source_scan_test.go Tests source-backed action traversal.
internal/​workflowfile/​rewrite.go Excludes reusable calls; removes local migration.
internal/​workflowfile/​reusable_test.go Tests reusable-workflow separation.
internal/​workflowfile/​remoteref.go Detects malformed remote references.
internal/​workflowfile/​remoteref_test.go Tests malformed-reference handling.
internal/​tag/​testing.go Disables default cooldown in tests.
internal/​tag/​tags.go Caches release metadata.
internal/​tag/​adoption.go Enforces cooldown for adopted commits.
internal/​tag/​adoption_test.go Tests cooldown boundaries and failures.
internal/​resolve/​warnings_test.go Tests image discovery and deduplication.
internal/​resolve/​reverse_lookup.go Filters unacceptable provenance references.
internal/​resolve/​reverse_lookup_test.go Updates tag-lookup fixtures.
internal/​resolve/​resolver.go Separates locked-manifest caching.
internal/​resolve/​resolver_test.go Tests traversal and local-action notices.
internal/​resolve/​discovery_test.go Tests malformed nested references.
internal/​resolve/​ancestry.go Preserves underlying ancestry-check errors.
internal/​repospec/​repospec.go Parses remote repository targets.
internal/​pipeline/​verify_local.go Adds offline image and reusable-workflow findings.
internal/​pipeline/​verify_local_test.go Tests non-blocking offline warnings.
internal/​pipeline/​run.go Integrates sources, parity, and completeness checks.
internal/​pipeline/​run_test.go Tests accept-moved filtering.
internal/​pipeline/​parse.go Propagates sources and image references.
internal/​pipeline/​parse_test.go Tests source-backed parsing.
internal/​pipeline/​diagnose_test.go Tests warning severity and attribution.
internal/​pipeline/​completeness.go Checks locked manifests and collects warnings.
internal/​pipeline/​checks/​structural.go Checks reusable calls and recorded closure.
internal/​pipeline/​checks/​run.go Adds recorded-closure validation.
internal/​pipeline/​checks/​parsed.go Carries image and reusable-call metadata.
internal/​pipeline/​checks/​parsed_test.go Removes obsolete immutability tests.
internal/​pipeline/​checks/​misleading.go Skips missing-commit comparisons.
internal/​pipeline/​checks/​finding.go Keeps image warnings non-blocking.
internal/​pipeline/​checks/​finding_test.go Tests severity-based presentation.
internal/​pipeline/​checks/​category.go Adds image and cooldown categories.
internal/​pipeline/​checks/​category_test.go Tests category identifier stability.
internal/​pin/​semver_policy_test.go Tests version-narrowing policy.
internal/​pin/​record.go Tracks refreshes and verified entries.
internal/​pin/​normalization_test.go Updates provenance fixtures.
internal/​pin/​commit.go Validates lock updates before rewrites.
internal/​pin/​commit_test.go Tests no-write behavior on validation failure.
internal/​lockfile/​state_test.go Tests state validation and serialization.
internal/​lockfile/​hosts_test.go Updates host terminology.
internal/​lockfile/​entryref_test.go Tests recorded-reference restrictions.
internal/​lockfile/​convertor.go Validates recorded references.
internal/​ghapi/​tagsource.go Exposes release, commit, and repository metadata.
internal/​ghapi/​snapshot.go Restricts snapshot redirects.
internal/​ghapi/​snapshot_test.go Tests redirect boundaries.
internal/​ghapi/​rest_fallback.go Resolves explicit tag and branch namespaces.
internal/​ghapi/​rest_fallback_test.go Tests anonymous comparison fallback.
internal/​ghapi/​repos.go Adds SSO comparison fallback.
internal/​ghapi/​repo_metadata_test.go Tests canonical repository names.
internal/​ghapi/​refname.go Validates accepted reference names.
internal/​ghapi/​refname_test.go Tests reference-name rules.
internal/​ghapi/​ref.go Defines ambiguous-reference errors.
internal/​ghapi/​ref_test.go Tests namespace selection and collisions.
internal/​ghapi/​httpmock/​httpmock.go Supports reusable HTTP stubs.
internal/​ghapi/​httpmock/​githubfixtures.go Adds reference-lookup fixture helpers.
internal/​ghapi/​hosts.go Renames tenant-host terminology.
internal/​ghapi/​hosts_test.go Updates host-routing tests.
internal/​ghapi/​graphql_action_files.go Resolves tag and branch namespaces.
internal/​ghapi/​graphql_action_files_test.go Updates query expectations.
internal/​ghapi/​contents.go Reads snapshot files and directories.
internal/​ghapi/​client.go Installs snapshot redirect protection.
internal/​ghapi/​brief.go Provides concise terminal errors.
internal/​filesource/​filesource.go Abstracts local and remote reads.
internal/​filesource/​filesource_test.go Tests sources and path boundaries.
internal/​dockerimage/​image.go Classifies images and generates remediation.
internal/​dockerimage/​image_test.go Tests classification and diagnostic messages.
cmd/​gh-actions-lock/​verify.go Selects verification implementations.
cmd/​gh-actions-lock/​verify_test.go Updates local-action expectations.
cmd/​gh-actions-lock/​verify_select_test.go Tests verifier selection.
cmd/​gh-actions-lock/​verify_local.go Implements standalone offline verification.
cmd/​gh-actions-lock/​verify_local_test.go Tests job-image warnings end-to-end.
cmd/​gh-actions-lock/​target.go Selects remote verification targets.
cmd/​gh-actions-lock/​semver_policy_test.go Tests preserved partial references.
cmd/​gh-actions-lock/​selfrepository_test.go Updates self-repository verification fixtures.
cmd/​gh-actions-lock/​runlog.go Preserves detailed logged errors.
cmd/​gh-actions-lock/​runlog_test.go Tests log content and ordering.
cmd/​gh-actions-lock/​root.go Updates target wiring and CLI help.
cmd/​gh-actions-lock/​reusable_workflow_test.go Tests reusable-workflow coverage boundaries.
cmd/​gh-actions-lock/​prune_workflow_test.go Updates pruning fixtures.
cmd/​gh-actions-lock/​pin_summary.go Reports housekeeping and reference repairs.
cmd/​gh-actions-lock/​pin_summary_test.go Tests summary ordering and repairs.
cmd/​gh-actions-lock/​parallel_steps_test.go Updates parallel-step fixtures.
cmd/​gh-actions-lock/​no_onboard_sha_test.go Supplies recorded provenance in fixtures.
cmd/​gh-actions-lock/​missing_ref_test.go Tests explicit namespace lookups.
cmd/​gh-actions-lock/​lockrecovery.go Clarifies invalid-content recovery messages.
cmd/​gh-actions-lock/​lockrecovery_test.go Tests recovery wording.
cmd/​gh-actions-lock/​fresh_tag.go Updates cooldown comments.
cmd/​gh-actions-lock/​format/​terminal_test.go Tests grouped image-warning output.
cmd/​gh-actions-lock/​format/​json.go Retains non-blocking coverage findings.
cmd/​gh-actions-lock/​format/​json_test.go Updates host terminology.
cmd/​gh-actions-lock/​cooldown_flag_test.go Tests cooldown flag precedence.
cmd/​gh-actions-lock/​atomic_test.go Updates no-write guarantees and fixtures.
.github/​workflows/​actions.lock Updates the documentation URL.

Review effort: Max

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Image deduplication and case-insensitive action detection need fixes; duplicated test scenarios also remain unresolved.

No open issues 🎉

2 previously missed issues

In code that hasn't changed since last review

Medium severity Normalize image identity across workflow findings

cmd/​gh-actions-lock/​format/​terminal.go:369

Cross-workflow grouping also treats equivalent image spellings as different images: if a.yml uses docker://node:20 and b.yml has container: node:20, their details differ and the terminal prints two warning headers instead of one with both workflow locations. Use a normalized image identity for imageMap and imageOrder, rendering the original finding detail separately.

Medium severity Merge findings with equivalent docker image spellings

internal/​pipeline/​diagnose.go:375

[P2] A workflow using both container: node:20 and uses: docker://node:20 produces two container-image findings because Detail() preserves the docker:// prefix. The terminal consequently prints the same image twice rather than merging its locations. Normalize the optional prefix when comparing findings, preserving the pulling-action attribution, and add a mixed job/step regression case.

Review effort: Max

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Missing Docker-step locations and collapsed sub-action identities leave image warnings incomplete or misleading.

No open issues 🎉

Review effort: Max

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The unresolved required_by contract concern warrants human confirmation before approval.

No open issues 🎉

Review effort: Max

@nodeselector
nodeselector requested a max review from Copilot October 20, 2026 07:14
@nodeselector
nodeselector force-pushed the nodeselector-docker-image-pin-warnings branch from 038e24d to 35fa98c Compare October 20, 2026 07:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Detailed review excludes inherited release changes; full-PR approval requires final human review.

No open issues 🎉

Review effort: Max

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Cross-cutting image discovery and reporting warrant final human review; the three findings are non-blocking nits.

No open issues 🎉

Review effort: Max

nodeselector and others added 8 commits October 20, 2026 19:05
The lockfile pins an action's commit, not the image a Docker action pulls
or builds. One classifier decides for every source: any @sha256: digest
is pinned (digests are not validated), a ${{ }} expression or an empty
docker:// can't be judged statically and is skipped, and a tag, an
untagged image or a Dockerfile build is mutable. Warnings name the image
as the registry does, so docker://node:20 and node:20 read as one, and
the remediation depends on who controls the reference.

Refs #168, #128

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Classify the images a workflow and its in-repo $/ actions name:
docker:// steps, job containers and services in string or image: form,
and a $/ Docker action's runs.image. Each mutable image is one
non-blocking container-image finding per workflow, listing every job and
step that uses it, in the terminal and in JSON; valid and exit codes are
unchanged. They need no network, so --verify-local reports them too.
runs.using is matched case-insensitively, as the runner does.

Refs #168

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Read the images a remote action runs from the manifests discovery
already fetches: a Docker action's runs.image, a composite's docker://
steps including transitive ones, and a Dockerfile-only action, which the
existing action-file probe already reports. Images are collected per
graph node beside local-action notices and become container-image
findings naming the action and sub-action path; required_by keeps the
owner/repo@ref dependency key. No API request is added.

Refs #168, #128

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A remote composite can write and run a ./ action at run time, as
pypa/gh-action-pypi-publish does to run its container. The CLI can't see
that image, so the local-action notice now says so.

Refs #128

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Exercise the real binary against tag-pinned, digest-pinned and Dockerfile
Docker actions, a remote composite's docker:// step, and job containers
and services beside an own $/ Docker action. Each scenario's stubbed
action.yml is declared in the catalog as stub_manifest, so it ports to
any runner, and its description states the real-binary delta over the
Go command tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
State that the lockfile pins action commits, not container images, and
list what is and is not scanned, including called reusable workflows in
other repositories, ./ actions, expressions and Dockerfile FROM lines.
Record the additive container-image category for Dependabot review.

Closes the docs request in #168.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Dependabot accepts lockfile schemas v0.0.2 and v0.0.3, and the
recorded-ref validation candidate needs a major release classification
and Dependabot coordination. Both notes were corrected on the earlier
integration branch (0199a06) and are carried here unchanged; no schema
or version decision is made.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Fixes #184

(cherry picked from commit 1946ecb)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@nodeselector
nodeselector force-pushed the nodeselector-docker-image-pin-warnings branch from 35fa98c to 5db86b2 Compare October 21, 2026 02:43
@nodeselector
nodeselector changed the base branch from main to nodeselector-opaque-local-action-policy October 21, 2026 02:43
@nodeselector
nodeselector requested a max review from Copilot October 21, 2026 02:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Cross-layer image diagnostics and unresolved attribution issues require human review.

3 open issues

Review effort: Max

Comment on lines +64 to +65
for _, image := range manifest.ContainerImages {
appendContainerImageFinding(wr, image)
Comment on lines +78 to +79
for _, image := range graph.ContainerImages {
appendContainerImageFinding(&wr, image)
Comment thread RELEASING.md
Comment on lines +76 to +78
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.

Warn when a locked Docker container action (or a remote composite's docker:// step) pulls its image by tag

3 participants