The image scan writes its reports to the host, so the gate never runs and its failure reads as a finding #192
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#192
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What happens
The first time
image.ymlruns, the Scan it, with both scanners step fails withand nothing is published. The image is fine. The scanners are fine. What is broken is where
their output goes.
The gate never runs.
scan-image.shdies at the unguardedcaton line 91, which isbefore the two commands that actually decide anything — the
--exit-code 1trivy on line 92and the
--fail-ongrype on line 95. The script exits 1 either way, so from the outside aplumbing failure and a genuine finding are the same event: a red Scan it, with both scanners
step. That is the part worth fixing quickly, ahead of the lost files. A gate that cannot be
told apart from its own failure is not reporting anything.
Why
scan-image.shruns the scanners as containers and passes the report directory in as a bindmount (lines 60 and 68):
That is correct when a person runs the script on their own machine, which is the only way it
has ever been run —
$OUTis a real path on the same filesystem as the daemon, and--ignore-unfixedoutput lands where the script then reads it.In CI it is not the same filesystem. The job runs in a container, and
$DOCKERtalks tothe host's daemon through the mounted socket. So
-v "$OUT:/out"is resolved by the host,against a path that only exists inside the job container. The host has nothing there, creates
an empty directory, and the scanners write their reports into it — on the host, where the job
cannot see them.
$GITHUB_WORKSPACEin a Forgejo job is a per-task named volume, not a bind from the host.Measured on the runner rather than assumed:
So there is no host path that corresponds to
$OUTat all, and no arrangement ofSCAN_OUTPUT_DIRinside the workspace will produce one.What survives and what does not
Everything that goes through a bind mount is lost; everything the job's own shell writes is
fine.
trivy-full.txt,trivy-fixable.txt,sbom.cdx.json--output /out/...— lost to the hostgrype-fixable.txt(line 95)$GITHUB_STEP_SUMMARYblockif: always(), finds nothing, printsno reportupload-artifactfor the SBOMif: always(), matches nothing, warnsLine 82 also prints
wrote $OUT/sbom.cdx.jsonfor a file that is not there, which is worthremoving whatever else changes — it is the one line that actively says the wrong thing.
The fix
The smallest change that works in both places is to stop bind-mounting for output and let the
calling shell place the bytes. Both tools write to stdout when
--outputis omitted:The redirect is performed by whoever ran the script, so it lands in the job container in CI and
on the host for a person, with no branch between the two cases and no
-v "$OUT:/out"at all.That is the same shape line 95 already uses for grype, which is why grype is the one that works.
Two alternatives, both worse here and noted so they do not get rediscovered:
--volumes-fromthe job's own container inherits the workspace correctly but only works when the script is
already inside a container, so it needs a branch on something the script cannot reliably
detect; and mounting the task volume by name requires knowing a name that only the runner
knows.
Whatever the mechanism, line 91 should not be the thing that decides the job's exit code.
Guarding it like line 76 already is would at least mean a missing report no longer impersonates
a finding.
Why this has not been seen before
Nothing had ever built an image in CI —
image.ymlneeded a runner advertisingdockerandnone existed, which is #81 and the Check this first section of #190. That runner now exists,
registered to this repository only, so this is reachable for the first time. Expect it to be
the first of the usual first-run problems rather than the last.
The scan itself is not in question: run by hand on a host,
scan-image.shdoes exactly what itsays, and it is what found #155 and #157.