Files
AI/playbooks/docker-security-audit.md
2026-03-22 12:56:25 -07:00

10 KiB

Playbook: Docker Image Security Audit

Use this playbook to audit Docker images across your environment for vulnerabilities, misconfigurations, secrets, and anything that could compromise the host or network.

When invoked, read containers.local.txt in the current working directory and work through each section for every image listed.


How to Use

Tell the AI: "Run this playbook: https://git.chns.tech/CHNS/AI/raw/branch/main/playbooks/docker-security-audit.md"

The AI will:

  1. Read containers.local.txt to get the list of images to audit
  2. For each image — pull it, run all checks below, and evaluate results
  3. For each finding — report CRITICAL, HIGH, MEDIUM, LOW, or INFO
  4. Generate a timestamped report file in the reports/ directory
  5. Remove the image from the local host after scanning unless it is currently active (docker ps)
  6. At the end, give an overall environment summary

Do not skip or abbreviate checks. Run every tool listed for every image.


Pre-Flight

Before scanning any images:

  • Confirm trivy is installed: trivy --version
  • Confirm grype is installed: grype version
  • Confirm hadolint is installed: hadolint --version
  • Confirm Docker daemon is running: docker info
  • Confirm a reports/ directory exists in the working directory

AI Action: Run the above checks. If any tool is missing, stop and tell the user what needs to be installed before continuing. Do not proceed with a partial toolset.

containers.local.txt Bootstrap

Check if containers.local.txt exists in the current working directory.

  • If it does not exist: Create it with the following content, then stop and instruct the user to populate it before running the audit:
# Local Docker image inventory
# Format: image:tag — one per line
# This file is excluded from git. Keep it updated per host.
# Images without a tag will be treated as :latest (flagged as WARN in audit).

Tell the user: "containers.local.txt was not found and has been created. Add your image:tag entries (one per line) and re-run the playbook."

  • If it exists but is empty or contains only comments: Same as above — stop and prompt the user to add images.
  • If it exists and has valid entries: Continue to scanning.

Per-Image Procedure

Repeat this entire procedure for every image:tag entry in containers.local.txt.

Step 1 — Check if Already Active

Run: docker ps --format '{{.Image}}'

  • If the image is in the active list → mark as ACTIVE, skip Step 6 (do not remove)
  • If not active → mark as NOT ACTIVE, will be removed after scan

Step 2 — Pull the Image

Run: docker pull <image:tag>

  • If pull fails → mark as WARN (image may be private, unavailable, or tag deleted) and skip to next image
  • Note the image digest and creation date

Step 3 — CVE Scan (Trivy)

Run: trivy image --severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL --format json <image:tag>

Report findings grouped by severity:

Severity Count Notable CVEs
CRITICAL
HIGH
MEDIUM
LOW
UNKNOWN
  • List every CRITICAL and HIGH CVE individually with: CVE ID, affected package, fixed version (if available), and a one-line description of the risk
  • Summarise MEDIUM/LOW as counts only unless the user asks for detail
  • Flag any CVE with a known public exploit (Trivy marks these with --ignore-unfixed info)

Step 4 — CVE Scan (Grype)

Run: grype <image:tag> --output json

  • Cross-reference with Trivy results — note any findings unique to Grype that Trivy missed
  • Report new findings in the same severity table format as Step 3
  • If Grype and Trivy agree, note "Confirmed by both scanners"

Step 5 — Dockerfile Static Analysis (Hadolint)

Locate the Dockerfile for this image if available in the current working directory or a subdirectory.

Run: hadolint <path/to/Dockerfile>

  • Report every rule violation with: rule ID, severity, line number, and description
  • Flag DL (Dockerfile) and SC (ShellCheck) rule violations separately
  • If no Dockerfile is found locally, note it as INFO and skip this step — do not fail the audit

Step 7 — Extended Checks (Trivy)

Run each of the following and report all findings:

Secrets scan: trivy image --scanners secret <image:tag>

  • Report any detected secrets: type, file path inside image, line number if available
  • Even LOW confidence matches should be reported — flag clearly as low confidence

Misconfiguration scan: trivy image --scanners misconfig <image:tag>

  • Dockerfile best practice violations
  • CIS Docker Benchmark failures
  • Report each finding with: check ID, title, severity, description, and remediation

License scan: trivy image --scanners license --severity UNKNOWN,HIGH <image:tag>

  • Flag any unknown or restrictive licenses (GPL in a closed deployment, etc.)

SBOM generation: trivy image --format cyclonedx --output reports/sbom-<image-name>-<date>.json <image:tag>

  • Generate and save an SBOM for the image — no pass/fail, this is for records

Step 8 — Image Cleanup

After all scans are complete for this image:

  • If NOT ACTIVE: run docker rmi <image:tag>
    • Confirm removal succeeded
    • If removal fails (e.g. stopped container referencing it), note the reason and flag for manual cleanup
  • If ACTIVE: skip removal, note it in the report

Report Generation

After all images are scanned, generate the following files. All output goes into a timestamped folder:

Folder structure:

reports/audit-<YYYY-MM-DD>/
  audit-<YYYY-MM-DD>.md          ← overview report
  <image-name>/                  ← one folder per image (name only, no tag, slashes replaced with -)
    <image-name>-<tag>.md        ← per-image detail report
    sbom-<image-name>-<tag>.json ← SBOM

Create reports/audit-<YYYY-MM-DD>/ and each per-image subfolder before writing any files. If folders already exist, continue writing into them.


File 1 — Overview Report (replaces previous audit file)

Filename: reports/audit-<YYYY-MM-DD>/audit-<YYYY-MM-DD>.md

This is the top-level summary. It must link to each per-image detail file.

Summary Table

Image CRITICAL HIGH MEDIUM LOW Secrets Misconfigs Hadolint Status Removed Detail File
image:tag 0 0 0 0 0 0 0 ACTIVE/NOT ACTIVE YES/NO/FAILED image-name/image-name-tag.md

Critical & High Findings (All Images)

List every CRITICAL or HIGH CVE across all images — abbreviated to one line each:

[image:tag] CVE-XXXX-XXXXX — <package> — <one-line risk> — Fixed in: <version or "none">

Secrets Detected (All Images)

List every secret finding across all images — one line each:

[image:tag] <type> — <file path> — Confidence: HIGH/MEDIUM/LOW — Action: INVESTIGATE/ROTATE IMMEDIATELY

Cleanup Log

List every image and whether it was removed, skipped (active), or failed to remove.

Overall Risk Rating

After all findings:

  • CRITICAL findings present → Environment status: AT RISK — immediate action required
  • HIGH findings only → Environment status: ELEVATED — remediate within 7 days
  • MEDIUM/LOW only → Environment status: ACCEPTABLE — remediate in next maintenance window
  • No findings → Environment status: CLEAN

File 2 — Per-Image Detail Reports

Filename per image: reports/audit-<YYYY-MM-DD>/<image-name>/<image-name>-<tag>.md

  • <image-name> is the image name only — no tag, slashes replaced with - (e.g. nginx, myrepo-myapp)
  • <tag> is the image tag (e.g. latest, 1.2.3)
  • Example: nginx:latestreports/audit-2026-03-22/nginx/nginx-latest.md
  • Example: myrepo/myapp:1.2.3reports/audit-2026-03-22/myrepo-myapp/myrepo-myapp-1.2.3.md
  • Generate one file per image — do not combine images into a single detail file

Each per-image file must include:

Header

# Image Audit: <image:tag>
Date:   <YYYY-MM-DD>
Digest: <image digest>
Status: ACTIVE / NOT ACTIVE

CVE Findings (Trivy + Grype)

Full detail for every CRITICAL and HIGH CVE:

CVE:      CVE-XXXX-XXXXX
Package:  <package name and version>
Fixed in: <version> (or "No fix available")
Scanner:  Trivy / Grype / Both
Risk:     <one-line plain-English description>

Summarise MEDIUM and LOW as counts with a table — do not list individually unless they have a known public exploit.

Secrets

Full detail for every secret finding:

Type:       <e.g. AWS key, generic password, private key>
Confidence: HIGH / MEDIUM / LOW
Path:       <file path inside image>
Action:     INVESTIGATE / ROTATE IMMEDIATELY

Hadolint Findings

Full detail for every Hadolint rule violation:

Rule:     <DL or SC rule ID>
Line:     <line number in Dockerfile>
Severity: <error / warning / info>
Detail:   <description>

If no Dockerfile was found, note: Hadolint: Skipped — no Dockerfile located

Misconfigurations

Full detail for every misconfiguration finding:

Check:    <CIS ID or Trivy check ID>
Title:    <short title>
Severity: <severity>
Detail:   <description>
Fix:      <remediation>

License Findings

List any unknown or restrictive licenses flagged by Trivy.

SBOM

Note the SBOM filename saved alongside this report:

SBOM saved: reports/audit-<YYYY-MM-DD>/<image-name>/sbom-<image-name>-<tag>.json

Update the SBOM output path to match this folder: trivy image --format cyclonedx --output reports/audit-<YYYY-MM-DD>/<image-name>/sbom-<image-name>-<tag>.json <image:tag>

Cleanup

Note whether the image was removed, skipped (active), or failed to remove — and the reason if failed.


Severity Reference

Severity Action Required
CRITICAL Stop and flag immediately. Do not defer. Notify user before continuing.
HIGH Report in full. User must acknowledge before moving to next image.
MEDIUM Report and continue. Include in final report.
LOW Count only unless user requests detail.
INFO Include in SBOM/records only.

Notes for the AI

  • Never skip an image without documenting why
  • Never abbreviate CVE lists for CRITICAL or HIGH findings
  • If a scan command fails (not just returns no findings), report the error and the command that failed
  • Do not remove an image that appears in docker ps output under any circumstances
  • If containers.local.txt references an image with no tag, assume :latest and note it as a WARN (unpinned tag)
  • SBOM files are supplemental — save them but do not report their contents unless the user asks