Skip to content

feat(rule): add rules for unpinned images in Compose and Dockerfile - #95

Open
nozaq wants to merge 1 commit into
mainfrom
claude/reproducibility-rule-candidates-gwdw8n
Open

feat(rule): add rules for unpinned images in Compose and Dockerfile#95
nozaq wants to merge 1 commit into
mainfrom
claude/reproducibility-rule-candidates-gwdw8n

Conversation

@nozaq

@nozaq nozaq commented Aug 2, 2026

Copy link
Copy Markdown
Member

Summary

This PR adds two new linting rules to detect unpinned container images in Compose-based and Dockerfile-based devcontainer configurations, complementing the existing no-image-latest rule for direct image references.

Key Changes

  • no-compose-image-latest rule: Reports when a Compose service (the one the dev container runs in) uses an image without an explicit tag or with the "latest" tag. Reads and parses referenced Compose files to extract the service's image configuration.

  • no-dockerfile-image-latest rule: Reports when a Dockerfile that a devcontainer.json builds from has FROM instructions naming images without explicit tags or with "latest". Parses Dockerfile syntax to extract base image references.

  • Shared utilities in util.go:

    • readConfigFile(): Safely reads referenced configuration files (Compose, Dockerfile) with size limits and path validation
    • ociFeatureRefs(): Extracts OCI Feature references from configuration objects
    • isLocalFeature() and isTarballFeature(): Helpers to identify Feature references that don't need version pinning
    • unpinnedFeatureVersion(): Generates consistent messaging for unpinned Feature versions
  • dockerfile.go: New module providing Dockerfile parsing utilities:

    • dockerfileRef(): Locates the Dockerfile path from devcontainer.json (handles both top-level dockerFile and nested build.dockerfile)
    • dockerfileBaseImages(): Extracts base images from Dockerfile, filtering out stage references, scratch, and variable references
    • dockerfileBuildImages(): Combines the above to get images with their source location
  • Refactored pin_feature_version.go: Extracted common Feature reference handling logic into shared utilities in util.go to reduce duplication and support the new pin-depends-on-version and pin-feature-exact-version rules.

  • Comprehensive test coverage: Added test suites for all new rules covering edge cases like missing files, parse errors, variable references, and file merging behavior.

Implementation Details

  • Both new image rules follow the same pattern as no-image-latest: they report at the configuration property that declares the image source (the Compose file path or Dockerfile path), making the finding location clear even though the fix belongs in the referenced file.

  • Compose file handling respects the merge semantics: multiple files are read in order with later ones overriding earlier ones, matching Docker Compose's behavior.

  • Dockerfile parsing uses the buildkit parser to properly handle multi-stage builds, filtering out stage-to-stage references and variable substitutions that cannot be statically resolved.

  • File reading is bounded by maxConfigFileBytes (4 MB) to prevent issues with truncated configuration analysis, and paths are validated to prevent directory traversal.

https://claude.ai/code/session_01Y7ohBnPuPSezRvwrkzaRaA

The reproducibility rules only looked at a devcontainer.json's "image",
"features", and "customizations", so the same moving reference went
unreported wherever else it is written: a Dockerfile-based or
Compose-based configuration escaped image pinning entirely, and a
Feature's own dependencies were never checked.

- no-dockerfile-image-latest and pin-dockerfile-image-digest read the
  Dockerfile named by "build.dockerfile" (or the legacy "dockerFile")
  and judge each FROM. A reference to an earlier stage, "scratch", and
  one containing a variable are left out: none names an image the
  configuration pins.
- no-compose-image-latest reads the "image" of the Compose service the
  dev container runs in. A service that builds its own image, an image
  written as a variable, and a service no declared file defines are
  left out.
- pin-depends-on-version checks a Feature's "dependsOn", where an
  unpinned reference installs a moving dependency into every project
  using the Feature, with no way for those projects to pin it.
- pin-feature-exact-version requires a full "major.minor.patch",
  since the "major" and "major.minor" tags are reassigned on release.
  It stands to pin-feature-version as pin-image-digest stands to
  no-image-latest.

A file another configuration file names is read through the directory
the linted file was discovered in, so a path leading outside that
boundary reports nothing rather than reaching for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7ohBnPuPSezRvwrkzaRaA
@nozaq nozaq changed the title Add rules for unpinned images in Compose and Dockerfile feat(rule): add rules for unpinned images in Compose and Dockerfile Aug 2, 2026
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.

2 participants