Files
Anthropic-Cybersecurity-Skills/skills/performing-container-image-hardening/references/api-reference.md
T

5.1 KiB

API Reference: Container Image Hardening Audit

Libraries Used

Library Purpose
subprocess Execute Trivy, Docker, and hadolint CLI commands
json Parse vulnerability scan and inspection results
re Analyze Dockerfile instructions
pathlib Handle Dockerfile and image paths

Installation

# Trivy vulnerability scanner
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# Hadolint Dockerfile linter
wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64
chmod +x /usr/local/bin/hadolint

# Docker CLI (already installed in most environments)

Trivy Image Scanning

Scan Image for Vulnerabilities

import subprocess
import json

def scan_image(image_name, severity="CRITICAL,HIGH"):
    cmd = [
        "trivy", "image",
        "--format", "json",
        "--severity", severity,
        "--exit-code", "0",
        image_name,
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
    return json.loads(result.stdout) if result.stdout else {}

Scan for Secrets in Image

def scan_secrets(image_name):
    cmd = [
        "trivy", "image",
        "--format", "json",
        "--scanners", "secret",
        image_name,
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
    return json.loads(result.stdout) if result.stdout else {}

Scan for Misconfigurations

def scan_misconfig(image_name):
    cmd = [
        "trivy", "image",
        "--format", "json",
        "--scanners", "misconfig",
        image_name,
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
    return json.loads(result.stdout) if result.stdout else {}

Docker Image Inspection

Inspect Image Metadata

def inspect_image(image_name):
    cmd = ["docker", "inspect", image_name]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
    data = json.loads(result.stdout)[0]
    config = data.get("Config", {})
    return {
        "image": image_name,
        "user": config.get("User", "root"),
        "exposed_ports": list(config.get("ExposedPorts", {}).keys()),
        "env_vars": config.get("Env", []),
        "entrypoint": config.get("Entrypoint"),
        "cmd": config.get("Cmd"),
        "labels": config.get("Labels", {}),
        "layers": len(data.get("RootFS", {}).get("Layers", [])),
        "size_mb": round(data.get("Size", 0) / 1048576, 1),
    }

Check for Root User

def check_non_root(image_name):
    inspection = inspect_image(image_name)
    user = inspection["user"]
    return {
        "image": image_name,
        "runs_as_root": user in ("", "root", "0"),
        "user": user or "root (default)",
        "severity": "high" if user in ("", "root", "0") else "pass",
    }

Hadolint Dockerfile Linting

def lint_dockerfile(dockerfile_path):
    cmd = [
        "hadolint",
        "--format", "json",
        str(dockerfile_path),
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
    findings = json.loads(result.stdout) if result.stdout else []
    return [
        {
            "line": f["line"],
            "code": f["code"],
            "level": f["level"],
            "message": f["message"],
        }
        for f in findings
    ]

Hardening Checks

Common Dockerfile Issues

def audit_dockerfile(dockerfile_path):
    findings = []
    with open(dockerfile_path) as f:
        lines = f.readlines()

    has_user = False
    has_healthcheck = False

    for i, line in enumerate(lines, 1):
        stripped = line.strip()
        if stripped.startswith("USER") and stripped.split()[-1] not in ("root", "0"):
            has_user = True
        if stripped.startswith("HEALTHCHECK"):
            has_healthcheck = True
        if stripped.startswith("FROM") and ":latest" in stripped:
            findings.append({
                "line": i, "severity": "medium",
                "issue": "Using :latest tag — pin specific version",
            })
        if "ADD" in stripped and ("http://" in stripped or "https://" in stripped):
            findings.append({
                "line": i, "severity": "high",
                "issue": "ADD with remote URL — use COPY + curl for verification",
            })

    if not has_user:
        findings.append({"line": 0, "severity": "high", "issue": "No USER instruction — runs as root"})
    if not has_healthcheck:
        findings.append({"line": 0, "severity": "low", "issue": "No HEALTHCHECK instruction"})

    return findings

Output Format

{
  "image": "myapp:v1.2.3",
  "vulnerabilities": {
    "critical": 2,
    "high": 8,
    "medium": 15,
    "low": 23
  },
  "runs_as_root": false,
  "size_mb": 142.5,
  "layers": 12,
  "dockerfile_issues": 3,
  "secrets_found": 0,
  "findings": [
    {
      "type": "vulnerability",
      "package": "openssl",
      "installed": "3.0.2",
      "fixed": "3.0.13",
      "severity": "CRITICAL",
      "cve": "CVE-2024-0727"
    }
  ]
}