Skip to content

feat(scan-image): fail scans of images with pip - #74

Merged
uniqueg merged 5 commits into
mainfrom
feat/55-fail-on-pip
Nov 13, 2026
Merged

uniqueg merged 5 commits into
mainfrom
feat/55-fail-on-pip

Conversation

@uniqueg

@uniqueg uniqueg commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR adds the opt-in input fail-on-pip (default false) to the shared scan-image action. When it is true, the action lists all packages of the image with Trivy (trivy-packages.json, using the Trivy that the action already sets up). check-pip.sh then fails the step if pip is installed in either form:

  • as a Python package (e.g. a pip-*.dist-info anywhere in the image) or a conda package;
  • as an OS package: pip, py*-pip, python*-pip, or platform-python-pip. Packages that only hold pip's wheel or bytecode, such as python3-pip-whl and py3-pip-pyc, don't count.

The log annotation and the step summary list the packages that were found. Fields from the image are sanitised: control characters and :: are removed, % is escaped, and the summary shows them as code. The check runs before the step that fails on fixable findings. The default stays false, so callers keep passing after the Dependabot bump and can turn the check on one image at a time. The issue asks for repos to opt in with one input, and this does that.

The action README now documents the input, the rule and the known gap. The gap: pip's zipapp (pip.pyz) and other copies of pip that aren't installed as a package aren't found. A pip on the PATH that comes from an installed package is caught through that package. The repo README's development section now lists the new test script, and also tests/labels/test.sh, which was missing.

Which images ship pip today

I scanned the published images of the latest releases with Trivy v0.75.0 on 2026-11-13, using --list-all-pkgs:

image (docker.io/elixircloud/...)       pip
cloud-registry:latest                    yes (pip 26.2.2, python-pkg)
pubgrade-updater:latest                  yes (pip 26.3, python-pkg)
foca:latest                              no
protes:latest                            no
trs-filer:latest                         no
pubgrade:latest                          no
pubgrade-sidecar:latest                  no
tesk-core-taskmaster:latest              no
tesk-core-filer:latest                   no
tesk-api:latest                          no (Java)
cloud-registry-ui                        not checked (no latest tag)

The images that contain no pip can turn the check on as soon as they bump to the release that contains this change.

Verification

  • bash tests/scan-image/test.sh: 27 cases pass. They cover:
    • pip as a Python, conda or OS package, with names from several distributions;
    • wheel-only, bytecode-only and unrelated packages, and other ecosystems;
    • results without packages, and missing or invalid reports;
    • control characters, :: and % in the output;
    • the step summary;
    • two reports cut down from real Trivy v0.75.0 scans in tests/scan-image/fixtures/.
  • Locally, against real Trivy v0.75.0 reports (--list-all-pkgs --scanners vuln, as the action runs it):
    • a scratch image with only pip-26.0.dist-info fails;
    • a clean scratch image passes;
    • alpine:3.22 with py3-pip fails on the OS package. Trivy leaves out the dist-info that an OS package owns, which is why the OS check is needed.
  • pre-commit run --all-files passes: actionlint, shellcheck, schemas, markdownlint and gitleaks.
  • New CI steps in the test job:
    • the unit tests;
    • the clean image, scanned with fail-on-pip (it must pass and write the package list);
    • the pip image with the default (it must pass and write no package list);
    • the pip image with fail-on-pip: true and fail: false, so that only the pip check can fail it (it must fail, with pip in the package list).
  • CI on this PR: green (Lint, Test, CI passed, CodeQL). The log shows "No pip in the image" for the clean image, no package list with the default, and "The image contains pip: pip 26.0 (...)" for the pip image. After the Sourcery fixes (76fd80c), CI and Sourcery are green again.
  • Sourcery: five threads, all answered and resolved. Three are fixed in 76fd80c: malformed reports now fail, an earlier trivy-packages.json is removed at the start of every scan, and a missing jq gives a clear error. Two are declined, see below.

Declined review findings

  • Moving the unit test step next to the other tests/*/test.sh steps (quality, low): the test job groups its steps by action, with the gate rules next to the gate action, so the step stays with the scan-image steps.
  • Uploading trivy-packages.json with the report artifact (quality, medium, partly addressed): the report artifact is uploaded before the pip step runs, and changing that would mean restructuring the action. The README now says that the list stays in the workspace.
  • Detecting a pip executable on the PATH or pip.pyz (adversarial, low): this would need the image's file system, via docker export or docker run. That is not always possible for registry references, and it costs much more than a package list. It is documented as a known gap instead.
  • Rejecting boolean values other than true and false (adversarial, low): the existing upload, report and fail inputs work the same way. If this is wanted, it belongs in a separate change that covers all of them.
  • Resolving the image once for all Trivy steps (Sourcery, medium): every existing Trivy step of the action resolves image again, and the README asks callers to pass a digest for that reason. The new step follows that convention.
  • Detecting pip that isn't installed as a package (Sourcery, medium): this is the same gap as the zipapp finding above.
  • Squashing the fix commits (quality, low): the PR is squash-merged.

Open questions

None.

Closes #55

🤖 Generated with Claude Code

uniqueg and others added 4 commits November 13, 2026 07:35
Add the input fail-on-pip (default false) to the shared scan action.
If true, the action lists all packages of the image with Trivy and
fails if pip is installed, as a Python package (a pip-*.dist-info
anywhere in the image) or as an OS package (python3-pip, py3-pip and
the like). The check runs before the check of the findings and lists
the packages found in the log and the step summary.

Test the check on Trivy reports (tests/scan-image/test.sh) and the
action on a clean image and on an image with pip in CI, and document
the input, what counts as pip and which published images contain it.

Closes #55

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also fail on pip installed as a conda package (Trivy type conda-pkg).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Match more OS package names of pip (py3.13-pip, platform-python-pip,
  a bare pip) and strip control characters and escape % in the output.
- Test on reports cut down from real Trivy scans, results without
  packages, and in CI that the check is off by default; scan the pip
  image with fail false so that only the pip check can fail it.
- Keep the README generic (no per-repo status), document the zipapp
  gap and that the package list stays in the workspace; list all test
  scripts in the development section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Render the packages in the step summary as code, so that metadata from
the image is not rendered as Markdown, and widen the README wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

API

Add the public fail-on-pip input and wire the action to collect the package report and run the pip check before vulnerability-finding checks.

  • actions/scan-image/action.yml L10-15, L36-38, L90-110

Implementation

Validate Trivy’s package report, identify pip across supported package types, and safely report matches in workflow annotations and the step summary.

  • actions/scan-image/check-pip.sh L1-69

Configuration

Exercise the action’s enabled and default-off behavior in CI, including scans of clean and pip-containing images.

  • .github/workflows/ci.yml L186-194, L200-206, L240-273 (+1 small edits)

Documentation

Document the input, detection rules and known gap, package-list behavior, and the added test script.

  • README.md L145-147
  • actions/scan-image/README.md L11-12, L58-59, L64-113

Tests

Test pip detection, malformed reports, output sanitization, summaries, and representative Trivy fixtures.

  • tests/scan-image/fixtures/alpine-py3-pip.json L1-30
  • tests/scan-image/fixtures/scratch-pip-dist-info.json L1-19
  • tests/scan-image/test.sh L1-137

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@uniqueg
uniqueg marked this pull request as ready for review November 13, 2026 08:09
@sourcery-ai

sourcery-ai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Linked issues

Checked at commit 76fd80c

#55 ci: fail image scans when pip is in the image · 1 of 3 met

# Requirement Status Notes
1 The shared action can fail scans when pip is in the image, and its docs say how to enable this. 🟡 Partly met The action detects pip recorded as a Python, conda, or OS package and documents the opt-in, but it does not inspect the image for a standalone pip executable on PATH.
2 Enable the check in FOCA and the apps once each is free of pip. ❌ Not addressed The change adds the shared input but does not enable it in any FOCA or app scan workflow.
3 Test the check in this repo's CI. ✅ Met The CI test job runs the check's unit tests and integration scans with the check enabled and disabled.

@sourcery-ai sourcery-ai Bot 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.

Hey - I've found 5 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="actions/scan-image/action.yml" line_range="94" />
<code_context>
+      if: inputs.fail-on-pip == 'true'
+      uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
+      with:
+        image-ref: ${{ inputs.image }}
+        scanners: vuln
+        list-all-pkgs: "true"
</code_context>
<issue_to_address>
**Findings and pip check inspect different images**

When a mutable image tag is retargeted between the findings scan and the package-list scan, the package-list scan resolves the tag again, so the pip check can pass or fail against a different image from the one whose findings are reported.

Resolve the image once and use the same immutable image reference for both scans.
</issue_to_address>

### Comment 2
<location path="actions/scan-image/check-pip.sh" line_range="27-32" />
<code_context>
+    | .Packages[]?
+    | ((.Name // "") | tostring | ascii_downcase) as $name
+    | select(
+        ($result.Class == "lang-pkgs"
+          and ($result.Type | IN("python-pkg", "conda-pkg"))
+          and $name == "pip")
+        or ($result.Class == "os-pkgs"
+          and ($name | test("^((platform-)?(py|python)[0-9.]*-)?pip$"))))
+    | "\(.Name) \(.Version // "?") (\(.FilePath // $result.Target // "?"))"
+    | gsub("[[:cntrl:]]"; "")]
+  | unique[]' "${report}")"
</code_context>
<issue_to_address>
**Misses un-packaged pip**

The issue asks for scans to fail when pip is on PATH or present as a dist-info package. This check only matches packages in Trivy's package report, so an un-packaged pip executable or zipapp is not detected.
</issue_to_address>

### Comment 3
<location path="actions/scan-image/check-pip.sh" line_range="12-14" />
<code_context>
+
+report="${SCAN_PACKAGES:-}"
+
+if [[ -z "${report}" || ! -f "${report}" ]] \
+  || ! jq -e 'type == "object" and has("ArtifactName")' "${report}" \
+    >/dev/null 2>&1; then
+  echo "::error title=Scan image::No package list of the image to check for pip"
+  exit 1
</code_context>
<issue_to_address>
**Malformed package report passes**

When the package report is valid JSON with an `ArtifactName` but has a non-array `Results` value, the `jq` check accepts any object with `ArtifactName`, and the later `.Results[]?` traversal suppresses errors for a wrongly typed `Results` field and produces no matches. `check-pip.sh` then reports no pip and lets the scan pass.

Validate that `Results` is absent or an array before traversing it, and fail on other shapes.
</issue_to_address>

### Comment 4
<location path="actions/scan-image/action.yml" line_range="91" />
<code_context>
           trivy-report.txt
         if-no-files-found: error
+    - name: List packages of the image
+      if: inputs.fail-on-pip == 'true'
+      uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
+      with:
</code_context>
<issue_to_address>
**Previous package inventory appears current**

When the action runs more than once in the same workspace, with an earlier invocation enabling `fail-on-pip` and a later invocation disabling it, the later invocation skips the package-list step and leaves `trivy-packages.json` in place, so downstream consumers see the earlier image’s inventory as the current scan’s result.

Remove `trivy-packages.json` before the conditional package-list step runs.

Also at `actions/scan-image/action.yml:98`.
</issue_to_address>

### Comment 5
<location path="actions/scan-image/check-pip.sh" line_range="13" />
<code_context>
+report="${SCAN_PACKAGES:-}"
+
+if [[ -z "${report}" || ! -f "${report}" ]] \
+  || ! jq -e 'type == "object" and has("ArtifactName")' "${report}" \
+    >/dev/null 2>&1; then
+  echo "::error title=Scan image::No package list of the image to check for pip"
</code_context>
<issue_to_address>
**Enabled checks fail without jq**

When a caller enables `fail-on-pip` on a runner without `jq`, the `jq` validation and extraction commands fail with stderr redirected, so `check-pip.sh` reports “No package list” and exits 1 even for a clean image.

Ensure `jq` is available on the runner before the check runs, or remove the `jq` dependency.
</issue_to_address>

Sourcery assessment

Approval pending. 5 findings to address first.

Blocking findings: actions/scan-image/action.yml:94, actions/scan-image/check-pip.sh:32, actions/scan-image/check-pip.sh:14, actions/scan-image/action.yml:91, actions/scan-image/check-pip.sh:13


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread actions/scan-image/action.yml Resolved
Comment thread actions/scan-image/check-pip.sh Resolved
Comment thread actions/scan-image/check-pip.sh Resolved
Comment thread actions/scan-image/action.yml Resolved
Comment thread actions/scan-image/check-pip.sh Resolved
Fail on malformed package reports (results, packages or entries of the
wrong type) and on runners without jq, and remove an earlier package
list at the start of every scan, so that none is left from another
image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@uniqueg
uniqueg merged commit 9f6b576 into main Nov 13, 2026
7 checks passed
@uniqueg
uniqueg deleted the feat/55-fail-on-pip branch November 13, 2026 08:39
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.

ci: fail image scans when pip is in the image

1 participant