'Are we affected?' in five minutes, not five days
A critical vulnerability in a widely used library is announced on a Friday evening. Leadership asks which of our images contain it. Nobody knows.
- Weakness
- Executive Order 14028 / NTIA SBOM minimum elements
- Target
- every image ShopLite builds
Run these techniques only against the lab you own. Using them on systems you don't have permission to test is illegal.
The threat
01The exposure window
When Log4Shell was disclosed in December 2021, exploitation attempts started within hours. Organisations spent days grepping repositories and unpacking images, because the vulnerable library was usually a TRANSITIVE dependency, invisible in their own build files. The risk isn't the vulnerability alone: it's the time it takes you to answer 'where do we run this?'
What's at risk
- Days of exposure while teams search manually, with the real risk of missing an affected service entirely.
Detect
01Generate an SBOM for an image
A Software Bill of Materials lists every component in an artifact: OS packages, language libraries, versions, licences, and package URLs (purls). syft builds one by inspecting the image, including dependencies of dependencies. CycloneDX and SPDX are the two standard formats.
terminal$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$PWD:/out" anchore/syft:v1.26.0 shoplite:good -o cyclonedx-json=/out/sbom.cdx.jsonjq '.components | length' sbom.cdx.json── output ──✔ Loaded image shoplite:good✔ Parsed image sha256:4b1f…✔ Cataloged contents 312 packages21402Answer the question from the SBOM
Searching a JSON file takes milliseconds. Across 40 images with stored SBOMs, it's a loop, or a single query in an SBOM platform like Dependency-Track.
terminal$ jq -r '.components[] | select(.name=="lodash") | "\(.name) \(.version) \(.purl)"' sbom.cdx.json── output ──lodash 4.17.21 pkg:npm/lodash@4.17.2103Scan the SBOM instead of the image
grype matches an SBOM against vulnerability databases. Because the SBOM is stored, you can re-scan old builds against TODAY's advisories without rebuilding or even pulling the image.
terminal$ docker run --rm -v "$PWD:/in" anchore/grype:v0.94.0 sbom:/in/sbom.cdx.json --fail-on high── output ──✔ Scanned for vulnerabilities [3 vulnerability matches]NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITYbusybox 1.37.0-r12 1.37.0-r13 apk CVE-2025-XXXXX Medium...
Defend
01Produce and keep an SBOM for every build
Generate the SBOM in the same pipeline that builds the image, attach it to the image as a signed attestation (Lab 1.4), and upload it to an inventory. When the next critical CVE lands, the question becomes a query.
Hardened
.github/workflows/build.ymladd to fileyaml - uses: anchore/sbom-action@<sha> # v0 with: image: ${{ env.IMAGE }}@${{ steps.build.outputs.digest }} format: cyclonedx-json output-file: sbom.cdx.json - run: cosign attest --yes --type cyclonedx --predicate sbom.cdx.json "$IMAGE@$DIGEST" - run: curl -sf -X POST "$DTRACK_URL/api/v1/bom" -H "X-Api-Key: $DTRACK_KEY" -F "project=$PROJECT_UUID" -F "bom=@sbom.cdx.json"02Re-scan continuously, not just at build time
New vulnerabilities are disclosed in software that hasn't changed. A nightly job re-scans the SBOMs of everything currently deployed and opens tickets for new high or critical findings. Dependency-Track does this continuously for every uploaded SBOM.
Verify
01The Friday-night drill
Pick any package name and time how long it takes to list every deployed image containing it, with versions. Minutes means the process works. Run the drill before you need it.
terminal$ for f in sboms/*.cdx.json; do jq -r --arg f "$f" '.components[] | select(.name=="openssl" or .name=="libssl3") | "\($f) \(.name) \(.version)"' "$f"; done── output ──sboms/shoplite-api.cdx.json libssl3 3.5.1-r0sboms/payments.cdx.json libssl3 3.5.1-r0sboms/worker.cdx.json libssl3 3.3.2-r1 ← older base image, rebuild
The concepts
What an SBOM is (and isn't)
An SBOM is an inventory: component names, versions, suppliers, identifiers (purl, CPE), relationships, and licences. It doesn't say whether anything is vulnerable. That comes from matching it against advisory databases, and optionally VEX documents, which state 'this CVE doesn't affect us because the vulnerable code isn't used'. Its value is that it's produced once, at build time, and queried forever.
Your turn
Generate an SPDX SBOM instead of CycloneDX for the same image.
Why scan the SBOM of what's DEPLOYED rather than what's on the main branch?
Interview questions
What is an SBOM and why do organisations need one?