Repository navigation
feat(scan-image): fail scans of images with pip - #74
Conversation
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>
Reviewer's GuideAPIAdd the public
ImplementationValidate Trivy’s package report, identify pip across supported package types, and safely report matches in workflow annotations and the step summary.
ConfigurationExercise the action’s enabled and default-off behavior in CI, including scans of clean and pip-containing images.
DocumentationDocument the input, detection rules and known gap, package-list behavior, and the added test script.
TestsTest pip detection, malformed reports, output sanitization, summaries, and representative Trivy fixtures.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Linked issuesChecked at commit 76fd80c #55 ci: fail image scans when pip is in the image · 1 of 3 met
|
There was a problem hiding this comment.
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
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>
Summary
This PR adds the opt-in input
fail-on-pip(defaultfalse) to the sharedscan-imageaction. When it istrue, the action lists all packages of the image with Trivy (trivy-packages.json, using the Trivy that the action already sets up).check-pip.shthen fails the step if pip is installed in either form:pip-*.dist-infoanywhere in the image) or a conda package;pip,py*-pip,python*-pip, orplatform-python-pip. Packages that only hold pip's wheel or bytecode, such aspython3-pip-whlandpy3-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 staysfalse, 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. Apipon thePATHthat comes from an installed package is caught through that package. The repo README's development section now lists the new test script, and alsotests/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: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:::and%in the output;tests/scan-image/fixtures/.--list-all-pkgs --scanners vuln, as the action runs it):pip-26.0.dist-infofails;alpine:3.22withpy3-pipfails 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-filespasses: actionlint, shellcheck, schemas, markdownlint and gitleaks.testjob:fail-on-pip(it must pass and write the package list);fail-on-pip: trueandfail: false, so that only the pip check can fail it (it must fail, with pip in the package list).trivy-packages.jsonis removed at the start of every scan, and a missingjqgives a clear error. Two are declined, see below.Declined review findings
tests/*/test.shsteps (quality, low): thetestjob groups its steps by action, with the gate rules next to the gate action, so the step stays with the scan-image steps.trivy-packages.jsonwith 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.pipexecutable on thePATHorpip.pyz(adversarial, low): this would need the image's file system, viadocker exportordocker 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.trueandfalse(adversarial, low): the existingupload,reportandfailinputs work the same way. If this is wanted, it belongs in a separate change that covers all of them.imageagain, and the README asks callers to pass a digest for that reason. The new step follows that convention.Open questions
None.
Closes #55
🤖 Generated with Claude Code