Container Scanning skill for Codex
Fix the base, not one CVE at a time. This skill scans container images for vulnerable OS packages and app dependencies with Trivy and Grype, generates an SBOM with Syft, and drives fixes to the source with the most impact: the base image. It favors rebasing and distroless or multi-stage builds over one-off patches, tracks unfixable CVEs honestly, and gates CI on fixable High/Critical findings.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- container-scanning Installs to ~/.agents/skills/container-scanning/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install container-scanning The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project container-scanning Installs to .agents/skills/container-scanning/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/container-scanning.zipunzip -o container-scanning.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) container-scanning PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $container-scanning, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Scans container images for vulnerable OS packages and app dependencies with Trivy and Grype, generates an SBOM with Syft, and drives the fixes — rebase to a patched base, update packages, use distroless/slim, and gate CI on new fixable CVEs. Use when asked to scan a Docker image, reduce image CVEs, produce an SBOM, or add image scanning to a pipeline. Not for scanning source code (SAST).
Use it when
- Scanning a container image for vulnerabilities
- Reducing image CVEs or hardening a Dockerfile base
- Producing an SBOM or adding image scanning to CI
Use something else when
- The vulnerable app packages need lockfile fixes: use dependency-scanning
- You want deep layer slimming and size analysis: use docker-image-slimming
- You need signing, attestation and provenance: use supply-chain-security
How to check the result
Rebuild and rescan so fixable High/Critical drops to zero or a documented unfixable remainder, confirm the image shrank, and run smoke tests on the new base.
The mistake it prevents
Patching individual OS packages a base rebase would fix wholesale, or changing to a distroless/alpine base without smoke-testing that the app still runs.
An image scan is useful when it ends in a smaller, patched image and a gate that keeps it that way — not a CVE count you stare at. Most image CVEs come from the base image and OS packages, so the fix with the most impact is usually the base, not individual patches.
Inspect first
- Read the
DockerfileandAGENTS.md. Note the base image and tag, whether it is pinned by digest, what packages are installed, and whether it is multi-stage. - Decide the target: scan a built image (
repo:tag), a tarball, or the filesystem. Scanners pull vulnerability databases over the network; Codex’s sandbox may block that — note when a step needs network, and cache the DB where possible.
Scan
trivy is the fast default; grype is a good cross-check (different DB, catches different
things).
# Trivy: OS + language deps, fail on fixable High/Critical
trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 myapp:latest
# Grype as a second opinion
grype myapp:latest --only-fixed --fail-on high
# Scan the Dockerfile/IaC for misconfig too (Trivy config scanning)
trivy config .
Key flags:
--ignore-unfixed/--only-fixed— hide CVEs with no available patch; you cannot fix those by updating, so they are noise in the action list (track them separately).--severity— focus on High/Critical first.--scanners vuln,secret,misconfig(Trivy) — one pass for CVEs, leaked secrets and Dockerfile misconfig.
Triage
For each fixable finding, identify where it lives:
| Source | Fix |
|---|---|
| Base image OS package | Rebase to a newer/patched base tag; apt-get upgrade in build as a stopgap |
| App dependency (npm/pip/etc.) | Bump it in the lockfile (hand off to dependency-scanning) |
| Build tool left in final image | Use multi-stage so it never reaches the runtime layer |
| No fix available | Record, assess reachability, monitor; do not block CI on it |
The biggest wins come from the base image. Prefer, in order: a distroless base
(no shell/package manager, minimal surface), a slim/-slim variant, then Alpine (small,
but musl can bite some apps — test). Pin the base by digest so a rebuild is reproducible:
FROM gcr.io/distroless/nodejs22-debian12@sha256:<digest>
Shrink the surface (fewer packages = fewer CVEs)
- Multi-stage build: compile/install in a builder stage, copy only artifacts into a minimal runtime stage. Hand off deep slimming (dive layer analysis, distroless trade-offs) to docker-image-slimming.
- Do not install
curl/bash/build tools in the final stage. - Run as a non-root user; drop capabilities (defense in depth, not a CVE fix).
SBOM
Generate a Software Bill of Materials so you can answer “are we affected by CVE-X?” fast without rescanning everything:
syft myapp:latest -o spdx-json > sbom.spdx.json
# Scan an existing SBOM later instead of the image
grype sbom:./sbom.spdx.json
Store the SBOM as a build artifact. For signing/attestation and supply-chain provenance, hand off to supply-chain-security.
CI gate
Fail the build only on fixable CVEs at/above your threshold, so the gate is actionable:
trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 "$IMAGE"
Scan on every image build and on a schedule (new CVEs appear against an unchanged image). Wire the pipeline details via github-actions-workflows; keep DB caching on to stay fast.
Verification
- Rebuild after fixes and rescan: fixable High/Critical count drops to zero (or to a documented, unfixable remainder).
- Image size shrank if you rebased/slimmed — confirm with
docker images. - The app still runs on the new base (distroless/alpine can change runtime assumptions) — run the smoke tests.
- CI fails on a deliberately outdated base and passes when patched.
Output contract
## Container scan — <image:tag> (<date>)
Base: <image@digest>. Scanners: trivy <ver>, grype <ver>. SBOM: sbom.spdx.json.
### Fixable findings
| CVE | Severity | Package | Installed → Fixed | Source | Action |
|-----|----------|---------|-------------------|--------|--------|
| CVE-2024-xxxx | CRITICAL | openssl | 3.0.11 → 3.0.14 | base OS | rebase to debian12 latest |
### Unfixable (tracked): <list + reachability note>
### Changes: base → <new>, size <old> → <new>. CI gate: fixable HIGH/CRITICAL.
Hard rules
- Prioritize the base image; patching individual OS packages that a rebase would fix wholesale is busywork.
- Use
--ignore-unfixedfor the action list, but still record unfixable CVEs — do not pretend they are gone. - Pin the base by digest; a floating tag makes scans non-reproducible.
- Always smoke-test after a base change; a passing scan on a broken image is not progress.
- Image scanning is not source SAST — hand off to semgrep-sast for code bugs.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Container Scanning"
short_description: "Fix the base, not one CVE at a time"
default_prompt: "$container-scanning Scan this Docker image for CVEs and tell me how to cut them" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.