container security

Scanning Container Images with Grype

Scan container images for known vulnerabilities using Anchore Grype with SBOM-based matching and configurable severity thresholds.

anchorecontainer-securitygrypesbomsupply-chainvulnerability-scanning
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Grype is an open-source vulnerability scanner from Anchore that inspects container images, filesystems, and SBOMs for known CVEs. It leverages Syft-generated SBOMs to match packages against multiple vulnerability databases including NVD, GitHub Advisories, and OS-specific feeds.

When to Use

  • When conducting security assessments that involve scanning container images with grype
  • When following incident response procedures for related security events
  • When performing scheduled security testing or auditing activities
  • When validating security controls through hands-on testing

Prerequisites

  • Docker or Podman installed
  • Grype CLI installed (curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin)
  • Syft CLI (optional, for SBOM generation)
  • Network access to pull vulnerability databases

Core Commands

Install Grype

# Install via script
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
 
# Verify installation
grype version
 
# Install via Homebrew (macOS/Linux)
brew install grype

Scan Container Images

# Scan a Docker Hub image
grype nginx:latest
 
# Scan from Docker daemon
grype docker:myapp:1.0
 
# Scan a local archive
grype docker-archive:image.tar
 
# Scan an OCI directory
grype oci-dir:path/to/oci/
 
# Scan a Singularity image
grype sif:image.sif
 
# Scan a local directory / filesystem
grype dir:/path/to/project

Output Formats

# Default table output
grype alpine:3.18
 
# JSON output for pipeline processing
grype alpine:3.18 -o json > results.json
 
# CycloneDX SBOM output
grype alpine:3.18 -o cyclonedx
 
# SARIF output for GitHub Security tab
grype alpine:3.18 -o sarif > grype.sarif
 
# Template-based custom output
grype alpine:3.18 -o template -t /path/to/template.tmpl

Filtering and Thresholds

# Fail if vulnerabilities meet or exceed a severity
grype nginx:latest --fail-on critical
 
# Show only fixed vulnerabilities
grype nginx:latest --only-fixed
 
# Show only non-fixed vulnerabilities
grype nginx:latest --only-notfixed
 
# Filter by severity
grype nginx:latest --only-fixed -o json | jq '[.matches[] | select(.vulnerability.severity == "High")]'
 
# Explain a specific CVE
grype nginx:latest --explain --id CVE-2024-1234

Working with SBOMs

# Generate SBOM with Syft then scan
syft nginx:latest -o spdx-json > nginx-sbom.json
grype sbom:nginx-sbom.json
 
# Scan CycloneDX SBOM
grype sbom:bom.json

Configuration File (.grype.yaml)

# .grype.yaml
check-for-app-update: false
fail-on-severity: "high"
output: "json"
scope: "squashed"  # or "all-layers"
quiet: false
 
ignore:
  - vulnerability: CVE-2023-12345
    reason: "False positive - not exploitable in our context"
  - vulnerability: CVE-2023-67890
    fix-state: unknown
 
db:
  auto-update: true
  cache-dir: "/tmp/grype-db"
  max-allowed-built-age: 120h  # 5 days
 
match:
  java:
    using-cpes: true
  python:
    using-cpes: true
  javascript:
    using-cpes: false

CI/CD Integration

# GitHub Actions
- name: Scan image with Grype
  uses: anchore/scan-action@v4
  with:
    image: "myregistry/myapp:${{ github.sha }}"
    fail-build: true
    severity-cutoff: high
    output-format: sarif
  id: scan
 
- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: ${{ steps.scan.outputs.sarif }}
# GitLab CI
container_scan:
  stage: test
  image: anchore/grype:latest
  script:
    - grype ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA} --fail-on high -o json > grype-report.json
  artifacts:
    reports:
      container_scanning: grype-report.json

Database Management

# Check database status
grype db status
 
# Manually update vulnerability database
grype db update
 
# Delete cached database
grype db delete
 
# List supported database providers
grype db list

Key Vulnerability Sources

Source Coverage
NVD CVEs across all ecosystems
GitHub Advisories Open source package vulnerabilities
Alpine SecDB Alpine Linux packages
Amazon Linux ALAS Amazon Linux AMI
Debian Security Tracker Debian packages
Red Hat OVAL RHEL, CentOS
Ubuntu Security Ubuntu packages
Wolfi SecDB Wolfi/Chainguard images

Best Practices

  1. Pin image tags - Always scan specific digests, not latest
  2. Fail on severity - Set --fail-on high or critical in CI gates
  3. Use SBOMs - Generate SBOMs with Syft for reproducible scanning
  4. Suppress false positives - Use .grype.yaml ignore rules with documented reasons
  5. Scan all layers - Use --scope all-layers to catch vulnerabilities in intermediate layers
  6. Automate database updates - Keep the vulnerability database current in CI runners
  7. Compare scans - Track vulnerability count over time for regression detection
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.7 KB

API Reference: Scanning Container Images with Grype

Grype CLI Commands

Command Description
grype <image> Scan a container image
grype <image> -o json JSON output for parsing
grype <image> -o sarif SARIF output for GitHub Security
grype <image> --fail-on critical Exit non-zero on severity
grype <image> --only-fixed Show only fixable vulns
grype sbom:<file> Scan a pre-generated SBOM
grype dir:<path> Scan a local directory
grype db status Check vulnerability DB status
grype db update Update vulnerability database

Input Sources

Source Syntax Description
Registry grype nginx:latest Pull from registry
Docker daemon grype docker:myapp:1.0 Local Docker image
Archive grype docker-archive:image.tar Saved tar archive
OCI dir grype oci-dir:path/ OCI layout directory
SBOM grype sbom:bom.json CycloneDX/SPDX SBOM
Directory grype dir:/path/ Filesystem scan

Severity Levels

Level CVSS Range Action
Critical 9.0 - 10.0 Immediate remediation
High 7.0 - 8.9 Fix before deployment
Medium 4.0 - 6.9 Plan remediation
Low 0.1 - 3.9 Accept or fix later
Negligible 0.0 Informational

Python Libraries

Library Version Purpose
subprocess stdlib Execute grype CLI
json stdlib Parse JSON output
pathlib stdlib File path handling

References

standards.md2.5 KB

Standards and References - Container Image Scanning with Grype

Industry Standards

NIST SP 800-190: Application Container Security Guide

  • Section 4.1: Image vulnerabilities - Recommends scanning images for known vulnerabilities before deployment
  • Section 4.2: Image configuration defects - Covers misconfigurations in container images
  • Recommends integrating vulnerability scanning into CI/CD pipelines

CIS Docker Benchmark v1.6

  • Rule 4.1: Ensure a user for the container has been created
  • Rule 4.6: Add HEALTHCHECK instruction to the container image
  • Rule 4.9: Ensure that COPY is used instead of ADD
  • Rule 4.10: Ensure secrets are not stored in Dockerfiles

NIST SP 800-53 Rev 5

  • RA-5: Vulnerability Monitoring and Scanning
  • SI-2: Flaw Remediation
  • CM-6: Configuration Settings
  • SA-11: Developer Security Testing and Evaluation

OWASP Container Security

  • VS-001: Vulnerability Scanning - Scan container images for known vulnerabilities
  • VS-002: SBOM Generation - Generate and maintain software bill of materials
  • VS-003: Base Image Selection - Use minimal, trusted base images

Vulnerability Databases

Database URL Update Frequency
NVD (National Vulnerability Database) https://nvd.nist.gov/ Continuous
GitHub Advisory Database https://github.com/advisories Continuous
OSV (Open Source Vulnerabilities) https://osv.dev/ Continuous
Alpine SecDB https://secdb.alpinelinux.org/ Daily
Debian Security Tracker https://security-tracker.debian.org/ Daily

CVSS Scoring Reference

Severity CVSS v3.1 Score Recommended Action
Critical 9.0 - 10.0 Block deployment, immediate remediation
High 7.0 - 8.9 Block deployment in production
Medium 4.0 - 6.9 Track and remediate within SLA
Low 0.1 - 3.9 Accept risk or remediate in next cycle
None 0.0 Informational

Compliance Mappings

PCI DSS v4.0

  • Requirement 6.3.1: Identify and manage security vulnerabilities
  • Requirement 6.3.3: Update system components to address known vulnerabilities

SOC 2

  • CC7.1: To meet its objectives, the entity uses detection and monitoring procedures to identify changes to configurations that result in the introduction of new vulnerabilities

FedRAMP

  • RA-5(2): Update the vulnerabilities scanned within every 30 days prior to a new scan
  • RA-5(5): Implement privileged access authorization for vulnerability scanning activities
workflows.md4.0 KB

Workflow - Container Image Scanning with Grype

Phase 1: Environment Setup

Install Grype and Syft

# Install Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
 
# Install Syft for SBOM generation
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
 
# Verify
grype version
syft version

Configure Grype

# Create config directory
mkdir -p ~/.grype
 
# Create configuration file
cat > ~/.grype/.grype.yaml <<EOF
check-for-app-update: false
fail-on-severity: "high"
db:
  auto-update: true
  cache-dir: "/tmp/grype-db"
  max-allowed-built-age: 120h
ignore:
  # Add known false positives here
  []
EOF

Phase 2: Image Scanning Workflow

Step 1 - Generate SBOM

syft ${IMAGE_REF} -o spdx-json > sbom.spdx.json
syft ${IMAGE_REF} -o cyclonedx-json > sbom.cdx.json

Step 2 - Run Vulnerability Scan

# Scan directly
grype ${IMAGE_REF} -o json > vulnerability-report.json
 
# Or scan from SBOM (faster for repeated scans)
grype sbom:sbom.spdx.json -o json > vulnerability-report.json

Step 3 - Evaluate Results

# Count by severity
cat vulnerability-report.json | jq '.matches | group_by(.vulnerability.severity) | map({severity: .[0].vulnerability.severity, count: length})'
 
# List critical and high findings
cat vulnerability-report.json | jq '[.matches[] | select(.vulnerability.severity == "Critical" or .vulnerability.severity == "High") | {id: .vulnerability.id, severity: .vulnerability.severity, package: .artifact.name, version: .artifact.version, fix: .vulnerability.fix.versions}]'

Step 4 - Gate Decision

# Automated gate check
grype ${IMAGE_REF} --fail-on high
EXIT_CODE=$?
 
if [ $EXIT_CODE -ne 0 ]; then
    echo "GATE FAILED: High or Critical vulnerabilities found"
    exit 1
fi

Phase 3: CI/CD Integration

GitHub Actions Complete Workflow

name: Container Security Scan
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
 
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: myapp:${{ github.sha }}
          format: spdx-json
          output-file: sbom.spdx.json
 
      - name: Scan for vulnerabilities
        uses: anchore/scan-action@v4
        id: scan
        with:
          image: myapp:${{ github.sha }}
          fail-build: true
          severity-cutoff: high
          output-format: sarif
 
      - name: Upload SARIF to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: ${{ steps.scan.outputs.sarif }}
 
      - name: Upload SBOM artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.spdx.json

Phase 4: Reporting and Remediation

Generate Human-Readable Report

# Table output with full details
grype ${IMAGE_REF} -o table > scan-report.txt
 
# Generate custom HTML report using template
grype ${IMAGE_REF} -o template -t report.tmpl > report.html

Remediation Workflow

  1. Review critical/high findings from scan output
  2. Check if fix versions are available (fix.versions in JSON output)
  3. Update base image to latest patched version
  4. Update application dependencies
  5. Rebuild and rescan to verify remediation
  6. Add accepted risks to .grype.yaml ignore list with documented justification

Phase 5: Continuous Monitoring

Scheduled Rescans

# GitHub Actions scheduled scan
name: Scheduled Vulnerability Scan
on:
  schedule:
    - cron: '0 6 * * 1'  # Every Monday at 6 AM
 
jobs:
  rescan:
    runs-on: ubuntu-latest
    steps:
      - name: Scan production images
        run: |
          for image in $(cat image-inventory.txt); do
            grype ${image} --fail-on critical -o json > "report-$(echo $image | tr '/:' '-').json"
          done

Scripts 2

agent.py4.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for scanning container images with Anchore Grype.

Runs Grype CLI against container images, parses JSON results,
applies severity thresholds, and generates vulnerability reports
with remediation guidance.
"""

import json
import subprocess
import sys
from pathlib import Path
from datetime import datetime


class GrypeScanAgent:
    """Scans container images for vulnerabilities using Grype."""

    def __init__(self, output_dir="./grype_scan"):
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.results = []

    def _run_grype(self, target, extra_args=None):
        """Execute grype CLI and return parsed JSON output."""
        cmd = ["grype", target, "-o", "json", "--quiet"]
        if extra_args:
            cmd.extend(extra_args)
        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
            if result.returncode not in (0, 1):
                return {"error": result.stderr.strip()}
            return json.loads(result.stdout) if result.stdout.strip() else {}
        except FileNotFoundError:
            return {"error": "grype not found. Install: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh"}
        except subprocess.TimeoutExpired:
            return {"error": "Scan timed out after 300 seconds"}
        except json.JSONDecodeError:
            return {"error": "Failed to parse grype JSON output"}

    def scan_image(self, image_ref, fail_on=None, only_fixed=False):
        """Scan a container image for vulnerabilities."""
        args = []
        if only_fixed:
            args.append("--only-fixed")
        raw = self._run_grype(image_ref, args)
        if "error" in raw:
            return raw

        matches = raw.get("matches", [])
        vulns = []
        for m in matches:
            vuln = m.get("vulnerability", {})
            artifact = m.get("artifact", {})
            vulns.append({
                "id": vuln.get("id", ""),
                "severity": vuln.get("severity", "Unknown"),
                "package": artifact.get("name", ""),
                "version": artifact.get("version", ""),
                "fixed_in": vuln.get("fix", {}).get("versions", []),
                "type": artifact.get("type", ""),
            })

        summary = self._summarize(vulns)
        scan_result = {
            "image": image_ref,
            "scan_date": datetime.utcnow().isoformat(),
            "total_vulnerabilities": len(vulns),
            "summary": summary,
            "vulnerabilities": vulns,
        }

        if fail_on:
            sev_order = ["Critical", "High", "Medium", "Low", "Negligible"]
            threshold_idx = sev_order.index(fail_on) if fail_on in sev_order else -1
            for sev in sev_order[:threshold_idx + 1]:
                if summary.get(sev, 0) > 0:
                    scan_result["gate_status"] = "FAILED"
                    break
            else:
                scan_result["gate_status"] = "PASSED"

        self.results.append(scan_result)
        return scan_result

    def scan_sbom(self, sbom_path):
        """Scan a pre-generated SBOM file."""
        return self._run_grype(f"sbom:{sbom_path}")

    def scan_directory(self, dir_path):
        """Scan a local directory for vulnerabilities."""
        return self._run_grype(f"dir:{dir_path}")

    def check_db_status(self):
        """Check Grype vulnerability database status."""
        try:
            result = subprocess.run(
                ["grype", "db", "status"], capture_output=True, text=True, timeout=30
            )
            return {"status": result.stdout.strip()}
        except (FileNotFoundError, subprocess.TimeoutExpired) as e:
            return {"error": str(e)}

    def _summarize(self, vulns):
        summary = {}
        for v in vulns:
            sev = v["severity"]
            summary[sev] = summary.get(sev, 0) + 1
        return summary

    def generate_report(self):
        report = {
            "report_date": datetime.utcnow().isoformat(),
            "scans": self.results,
            "total_images": len(self.results),
        }
        out = self.output_dir / "grype_report.json"
        with open(out, "w") as f:
            json.dump(report, f, indent=2)
        print(json.dumps(report, indent=2))
        return report


def main():
    if len(sys.argv) < 2:
        print("Usage: agent.py <image_ref> [--fail-on critical|high|medium]")
        sys.exit(1)

    image = sys.argv[1]
    fail_on = None
    if "--fail-on" in sys.argv:
        idx = sys.argv.index("--fail-on")
        if idx + 1 < len(sys.argv):
            fail_on = sys.argv[idx + 1].capitalize()

    agent = GrypeScanAgent()
    result = agent.scan_image(image, fail_on=fail_on)
    agent.generate_report()

    if result.get("gate_status") == "FAILED":
        sys.exit(1)


if __name__ == "__main__":
    main()
process.py6.4 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Grype Container Image Scanner - Automated scanning and reporting utility.

Scans container images using Grype, parses results, and generates
summary reports with severity breakdowns and remediation guidance.
"""

import json
import subprocess
import sys
import argparse
from datetime import datetime
from pathlib import Path


def run_grype_scan(image: str, output_format: str = "json", fail_on: str = None) -> dict:
    """Run grype scan on a container image and return parsed results."""
    cmd = ["grype", image, "-o", output_format, "--quiet"]
    if fail_on:
        cmd.extend(["--fail-on", fail_on])

    result = subprocess.run(cmd, capture_output=True, text=True)

    if output_format == "json":
        try:
            return json.loads(result.stdout)
        except json.JSONDecodeError:
            print(f"Error parsing Grype output: {result.stderr}", file=sys.stderr)
            sys.exit(1)
    return {"raw": result.stdout, "returncode": result.returncode}


def parse_vulnerabilities(scan_data: dict) -> list:
    """Extract and structure vulnerability matches from scan results."""
    matches = scan_data.get("matches", [])
    vulns = []
    for match in matches:
        vuln = match.get("vulnerability", {})
        artifact = match.get("artifact", {})
        fix_info = vuln.get("fix", {})
        vulns.append({
            "id": vuln.get("id", "UNKNOWN"),
            "severity": vuln.get("severity", "Unknown"),
            "package": artifact.get("name", "unknown"),
            "version": artifact.get("version", "unknown"),
            "type": artifact.get("type", "unknown"),
            "fix_versions": fix_info.get("versions", []),
            "fix_state": fix_info.get("state", "unknown"),
            "data_source": vuln.get("dataSource", ""),
            "description": vuln.get("description", ""),
        })
    return vulns


def severity_summary(vulns: list) -> dict:
    """Generate severity count summary."""
    summary = {"Critical": 0, "High": 0, "Medium": 0, "Low": 0, "Negligible": 0, "Unknown": 0}
    for v in vulns:
        sev = v["severity"]
        if sev in summary:
            summary[sev] += 1
        else:
            summary["Unknown"] += 1
    return summary


def fixable_summary(vulns: list) -> dict:
    """Count fixable vs non-fixable vulnerabilities."""
    fixable = sum(1 for v in vulns if v["fix_state"] == "fixed")
    not_fixable = sum(1 for v in vulns if v["fix_state"] != "fixed")
    return {"fixable": fixable, "not_fixable": not_fixable, "total": len(vulns)}


def generate_report(image: str, vulns: list, output_path: str = None) -> str:
    """Generate a markdown vulnerability report."""
    summary = severity_summary(vulns)
    fix_info = fixable_summary(vulns)
    timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")

    report = f"""# Vulnerability Scan Report

**Image:** `{image}`
**Scan Date:** {timestamp}
**Scanner:** Grype (Anchore)
**Total Vulnerabilities:** {len(vulns)}

## Severity Summary

| Severity | Count |
|----------|-------|
| Critical | {summary['Critical']} |
| High | {summary['High']} |
| Medium | {summary['Medium']} |
| Low | {summary['Low']} |
| Negligible | {summary['Negligible']} |

## Fix Availability

- **Fixable:** {fix_info['fixable']}
- **Not Fixable:** {fix_info['not_fixable']}

## Critical and High Findings

| CVE | Severity | Package | Version | Fix Available |
|-----|----------|---------|---------|---------------|
"""
    critical_high = [v for v in vulns if v["severity"] in ("Critical", "High")]
    critical_high.sort(key=lambda x: (0 if x["severity"] == "Critical" else 1, x["id"]))

    for v in critical_high:
        fix = ", ".join(v["fix_versions"]) if v["fix_versions"] else "No fix"
        report += f"| {v['id']} | {v['severity']} | {v['package']} | {v['version']} | {fix} |\n"

    report += f"\n## All Vulnerabilities ({len(vulns)} total)\n\n"
    report += "| CVE | Severity | Package | Version | Type | Fix State |\n"
    report += "|-----|----------|---------|---------|------|----------|\n"

    for v in sorted(vulns, key=lambda x: (
        {"Critical": 0, "High": 1, "Medium": 2, "Low": 3, "Negligible": 4}.get(x["severity"], 5),
        x["id"]
    )):
        report += f"| {v['id']} | {v['severity']} | {v['package']} | {v['version']} | {v['type']} | {v['fix_state']} |\n"

    if output_path:
        Path(output_path).write_text(report)
        print(f"Report written to {output_path}")

    return report


def gate_check(vulns: list, max_critical: int = 0, max_high: int = 0) -> bool:
    """Evaluate vulnerabilities against gate thresholds. Returns True if pass."""
    summary = severity_summary(vulns)
    passed = True

    if summary["Critical"] > max_critical:
        print(f"GATE FAIL: {summary['Critical']} critical vulnerabilities (max: {max_critical})")
        passed = False
    if summary["High"] > max_high:
        print(f"GATE FAIL: {summary['High']} high vulnerabilities (max: {max_high})")
        passed = False

    if passed:
        print("GATE PASS: Vulnerability thresholds met")

    return passed


def main():
    parser = argparse.ArgumentParser(description="Grype Container Image Scanner Utility")
    parser.add_argument("image", help="Container image reference to scan")
    parser.add_argument("--report", "-r", help="Output report file path (markdown)")
    parser.add_argument("--json-output", "-j", help="Output raw JSON results to file")
    parser.add_argument("--max-critical", type=int, default=0, help="Max allowed critical vulns (default: 0)")
    parser.add_argument("--max-high", type=int, default=0, help="Max allowed high vulns (default: 0)")
    parser.add_argument("--gate", action="store_true", help="Enable gate check mode")

    args = parser.parse_args()

    print(f"Scanning {args.image}...")
    scan_data = run_grype_scan(args.image)
    vulns = parse_vulnerabilities(scan_data)

    if args.json_output:
        Path(args.json_output).write_text(json.dumps(scan_data, indent=2))
        print(f"JSON results written to {args.json_output}")

    report = generate_report(args.image, vulns, args.report)

    if not args.report:
        print(report)

    summary = severity_summary(vulns)
    print(f"\nSummary: C={summary['Critical']} H={summary['High']} M={summary['Medium']} L={summary['Low']}")

    if args.gate:
        passed = gate_check(vulns, args.max_critical, args.max_high)
        sys.exit(0 if passed else 1)


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 2.1 KB
Keep exploring