CKS — Image Vulnerability Scanning

8 minute read

Image Vulnerability Scanning

CKS Exam Domain: Supply Chain Security (20%)

Vulnerability scanning is the process of inspecting container images for known CVEs (Common Vulnerabilities and Exposures) in OS packages, application libraries, and configuration files. On the CKS exam, Trivy is the go-to tool — you are expected to know its core commands, severity filtering, and output interpretation.

An image that passes a scan today may fail tomorrow as new CVEs are disclosed. Scanning must happen at build time, before push to a registry, and continuously on images already running in the cluster.


1. Trivy — The Standard Scanner

Trivy is an open-source vulnerability scanner from Aqua Security. It scans container images, filesystems, Git repositories, and Kubernetes clusters.

Install Trivy

# macOS
brew install trivy

# Linux (Debian/Ubuntu)
sudo apt-get install -y wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update && sudo apt-get install -y trivy

# Or run as a container
docker run --rm aquasec/trivy image nginx:1.27

2. Scanning Container Images

Basic Image Scan

trivy image nginx:1.27

Output (truncated):

nginx:1.27 (debian 12.5)
=========================
Total: 142 (UNKNOWN: 2, LOW: 85, MEDIUM: 38, HIGH: 14, CRITICAL: 3)

┌──────────────────┬────────────────┬──────────┬─────────────────────┬───────────────┬──────────────────────────────────┐
│     Library      │ Vulnerability  │ Severity │  Installed Version  │ Fixed Version │             Title                │
├──────────────────┼────────────────┼──────────┼─────────────────────┼───────────────┼──────────────────────────────────┤
│ libcurl4         │ CVE-2024-XXXXX │ CRITICAL │ 7.88.1-10+deb12u5  │ 7.88.1-10+... │ curl: use-after-free in ...     │
│ openssl          │ CVE-2024-YYYYY │ HIGH     │ 3.0.11-1~deb12u2   │ 3.0.13-1~...  │ openssl: buffer overread in ... │
│ zlib1g           │ CVE-2023-ZZZZZ │ MEDIUM   │ 1:1.2.13-1         │               │ zlib: heap buffer overflow in ..│
└──────────────────┴────────────────┴──────────┴─────────────────────┴───────────────┴──────────────────────────────────┘

Filter by Severity

# Show only CRITICAL and HIGH vulnerabilities
trivy image --severity CRITICAL,HIGH nginx:1.27

# Show only CRITICAL
trivy image --severity CRITICAL nginx:1.27

CKS Exam Tip: When the exam says “find images with critical vulnerabilities,” use --severity CRITICAL to filter. This is a commonly tested flag.

Exit Code for CI/CD

# Exit with code 1 if CRITICAL or HIGH vulnerabilities found
trivy image --severity CRITICAL,HIGH --exit-code 1 nginx:1.27
echo $?
# 1 (vulnerabilities found)

# Exit 0 if only LOW/MEDIUM
trivy image --severity CRITICAL,HIGH --exit-code 1 gcr.io/distroless/static-debian12
echo $?
# 0 (no CRITICAL/HIGH vulnerabilities)

3. Severity Levels

Trivy classifies vulnerabilities by severity based on CVSS scores:

Severity CVSS Score Action Required Example
CRITICAL 9.0 – 10.0 Immediate remediation. Block deployment. Remote code execution, privilege escalation
HIGH 7.0 – 8.9 Fix within days. Consider blocking. Buffer overflow, SQL injection
MEDIUM 4.0 – 6.9 Fix in next release cycle. Information disclosure
LOW 0.1 – 3.9 Track but low priority. Minor information leak
UNKNOWN N/A Unscored CVE. Assess manually. Newly disclosed, not yet scored

4. Scanning Filesystems and Repositories

Trivy can scan more than just container images.

Scan Local Filesystem

# Scan the current directory for vulnerabilities in dependencies
trivy fs .

# Scan a specific path
trivy fs /path/to/project

# Scan only for misconfigurations (Dockerfile, Kubernetes YAML, Terraform)
trivy config .

Scan a Git Repository

trivy repo https://github.com/example/app

Scan a Running Kubernetes Cluster

# Summary report of all vulnerabilities in running workloads
trivy k8s --report summary

# Detailed report
trivy k8s --report all

# Scan a specific namespace
trivy k8s --namespace production --report summary

5. Output Formats

# Default: table format (human-readable)
trivy image nginx:1.27

# JSON (for programmatic parsing)
trivy image --format json --output results.json nginx:1.27

# SARIF (for GitHub Code Scanning, Azure DevOps)
trivy image --format sarif --output results.sarif nginx:1.27

# Template output (custom format)
trivy image --format template --template "@/path/to/template.tpl" nginx:1.27

Parse JSON Output with jq

# Get CRITICAL vulnerability IDs
trivy image --format json nginx:1.27 | \
  jq -r '.Results[].Vulnerabilities[] | select(.Severity == "CRITICAL") | .VulnerabilityID'

# Count vulnerabilities by severity
trivy image --format json nginx:1.27 | \
  jq '[.Results[].Vulnerabilities[].Severity] | group_by(.) | map({(.[0]): length}) | add'

6. Grype — An Alternative Scanner

Grype by Anchore is another popular open-source vulnerability scanner.

Install Grype

# macOS
brew install grype

# Linux
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

Scan with Grype

# Basic scan
grype nginx:1.27

# Filter by severity
grype nginx:1.27 --only-fixed --fail-on critical

# Output as JSON
grype nginx:1.27 -o json

# Scan a directory (SBOM-based)
grype dir:/path/to/project

Trivy vs Grype

Feature Trivy Grype
Maintainer Aqua Security Anchore
Image scanning ✅ ✅
Filesystem scanning ✅ ✅
Kubernetes scanning ✅ (trivy k8s) ❌ (separate tool)
Misconfig scanning ✅ (trivy config) ❌
SBOM generation ✅ (trivy sbom) Via Syft
CKS exam relevance Primary Supplementary

CKS Exam Tip: Focus on Trivy. It is the scanner you are most likely to use on the exam.


7. Scanning Images Running in a Cluster

A common CKS exam task: “Find all images in namespace X with CRITICAL vulnerabilities.”

Step 1: List All Images in a Namespace

# Get all unique images in the "production" namespace
kubectl get pods -n production -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' | sort -u

Example output:

nginx:1.25
registry.example.com/api:v2.3
redis:7.2
postgres:16.1

Step 2: Scan Each Image

# Scan all images and report only CRITICAL/HIGH
for img in $(kubectl get pods -n production -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' | sort -u); do
  echo "=== Scanning: $img ==="
  trivy image --severity CRITICAL,HIGH --quiet "$img"
  echo ""
done

Step 3: Delete Pods with Vulnerable Images

# Find Pods running a specific vulnerable image
kubectl get pods -n production -o json | \
  jq -r '.items[] | select(.spec.containers[].image == "nginx:1.25") | .metadata.name'

# Delete the offending Pods
kubectl delete pod <pod-name> -n production

One-Liner: Scan and List Vulnerable Pods

# For each image, scan and if CRITICAL found, list Pods using that image
for img in $(kubectl get pods -n production -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' | sort -u); do
  if trivy image --severity CRITICAL --exit-code 1 --quiet "$img" 2>/dev/null; then
    : # clean image
  else
    echo "CRITICAL CVEs in: $img"
    kubectl get pods -n production -o json | \
      jq -r --arg IMG "$img" '.items[] | select(.spec.containers[].image == $IMG) | .metadata.name'
  fi
done

8. Integrating Scanning into CI/CD

GitHub Actions Example

name: Scan Container Image
on:
  push:
    branches: [main]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Build image
      run: docker build -t myapp:$ .

    - name: Scan with Trivy
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: myapp:$
        format: table
        severity: CRITICAL,HIGH
        exit-code: 1          # Fail the build if vulnerabilities found

Pipeline Strategy

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  Build   │───→│   Scan   │───→│  Sign    │───→│  Push    │
│  Image   │    │  (Trivy) │    │ (Cosign) │    │ Registry │
└──────────┘    └────┬─────┘    └──────────┘    └──────────┘
                     │
                     ▼ CRITICAL/HIGH found?
                ┌──────────┐
                │  FAIL    │
                │  Build   │
                └──────────┘

Scanning must happen before push to registry. An image that enters the registry without scanning has bypassed the gate.


9. Ignoring Known False Positives

# Create a .trivyignore file
cat > .trivyignore <<EOF
# Accepted risk: library not reachable in our code path
CVE-2023-44487
# Disputed vulnerability
CVE-2024-12345
EOF

# Scan with ignore file
trivy image --ignorefile .trivyignore nginx:1.27

Caution: Only ignore vulnerabilities after explicit security review. Document the reason in the ignore file.


10. Scanning Image Layers

# Show which layer introduced each vulnerability
trivy image --list-all-pkgs nginx:1.27

# Use dive to inspect layer contents
dive nginx:1.27

Understanding which layer introduced a vulnerability helps decide whether to update the base image or the application dependencies.


Summary

Command Purpose
trivy image <image> Scan a container image
trivy image --severity CRITICAL,HIGH <image> Filter by severity
trivy image --exit-code 1 <image> Fail CI pipeline
trivy image --format json <image> Machine-readable output
trivy fs . Scan local filesystem
trivy k8s --report summary Scan running cluster
trivy config . Scan for misconfigurations
grype <image> Alternative scanner

Practice Questions

Question 1

Scan the nginx:1.25 image and list only CRITICAL vulnerabilities. What command do you use?

Show Answer
trivy image --severity CRITICAL nginx:1.25

For a quiet output showing only the vulnerability table:

trivy image --severity CRITICAL --quiet nginx:1.25

Question 2

You need to find all Pods in the web namespace running images with HIGH or CRITICAL vulnerabilities and delete those Pods. Describe your approach.

Show Answer
# Step 1: Get all unique images in the namespace
kubectl get pods -n web -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' | sort -u > /tmp/images.txt

# Step 2: Scan each image
while read -r img; do
  echo "=== $img ==="
  trivy image --severity CRITICAL,HIGH --exit-code 1 --quiet "$img"
  if [ $? -ne 0 ]; then
    echo "VULNERABLE: $img"
    # Step 3: Find and delete Pods using this image
    kubectl get pods -n web -o json | \
      jq -r --arg IMG "$img" '.items[] | select(.spec.containers[].image == $IMG) | .metadata.name' | \
      xargs -I {} kubectl delete pod {} -n web
  fi
done < /tmp/images.txt

Question 3

What is the difference between trivy image, trivy fs, and trivy config?

Show Answer
Command Scans Detects
trivy image <image> Container image layers OS package CVEs, library CVEs
trivy fs <path> Local filesystem Application dependency CVEs (package-lock.json, go.sum, requirements.txt)
trivy config <path> Configuration files Misconfigurations in Dockerfiles, Kubernetes YAML, Terraform, CloudFormation
  • trivy image pulls and scans the image filesystem
  • trivy fs scans source code dependency files
  • trivy config checks for insecure configurations (e.g., USER root in Dockerfile, missing securityContext in Pod spec)

Question 4

How do you make a CI/CD pipeline fail if a container image has any CRITICAL vulnerabilities?

Show Answer

Use the --exit-code flag:

trivy image --severity CRITICAL --exit-code 1 myapp:latest
  • If CRITICAL vulnerabilities are found, Trivy exits with code 1, which fails the CI step
  • If no CRITICAL vulnerabilities are found, Trivy exits with code 0, and the pipeline continues

In a CI pipeline:

# GitHub Actions
- name: Scan image
  run: trivy image --severity CRITICAL --exit-code 1 myapp:$

Question 5

After scanning, you find that redis:7.2 has 3 CRITICAL vulnerabilities but redis:7.2.4 has 0. The cluster is running redis:7.2. What should you do?

Show Answer
  1. Update the image tag in the Deployment/StatefulSet to redis:7.2.4:

    kubectl set image deployment/redis-cache redis=redis:7.2.4 -n production
    
  2. Verify the rollout:

    kubectl rollout status deployment/redis-cache -n production
    
  3. Re-scan the new image to confirm:

    trivy image --severity CRITICAL redis:7.2.4
    # Should show 0 CRITICAL
    
  4. Pin by digest to prevent future tag mutation:

    # Get digest
    docker pull redis:7.2.4
    docker inspect --format='{{index .RepoDigests 0}}' redis:7.2.4
    # redis@sha256:abc123...
    
    # Update deployment to use digest
    kubectl set image deployment/redis-cache redis=redis@sha256:abc123... -n production
    
  5. Set up continuous scanning so you are alerted when new CVEs are disclosed against redis:7.2.4.

Updated: