
Managing Vulnerabilities
- 52 installs
- 426 repo stars
- Updated December 11, 2025
- ancoleman/ai-design-components
managing-vulnerabilities is a skill for multi-layer security scanning, SBOM generation, and risk-based vulnerability prioritization in CI/CD.
About
A skill for building multi-layer security scanning and risk-based vulnerability prioritization into CI/CD. A developer uses it to scan containers, code, and dependencies, generate SBOMs, and rank fixes with CVSS, EPSS, and KEV. It matters because it turns raw scanner output into prioritized, gated remediation rather than noise.
- Runs multi-layer scanning: container (Trivy/Grype), SAST (Semgrep), DAST (ZAP), SCA, and secret scanning
- Generates SBOMs in CycloneDX and SPDX for compliance with Trivy and Syft
- Prioritizes remediation with CVSS, EPSS, and CISA KEV using SLA tiers and CI/CD security gates
Managing Vulnerabilities by the numbers
- 52 all-time installs (skills.sh)
- Ranked #1,305 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
managing-vulnerabilities capabilities & compatibility
- Capabilities
- vulnerability scanning · sbom generation · risk prioritization · security gates
- Works with
- github
- Use cases
- security audit · ci cd
- Runs
- Runs locally
- Pricing
- Free
What managing-vulnerabilities says it does
Implementing multi-layer security scanning (container, SAST, DAST, SCA, secrets), SBOM generation, and risk-based vulnerability prioritization in CI/CD pipelines.
Not all vulnerabilities require immediate action. Prioritize based on actual risk using CVSS, EPSS, and KEV.
**CycloneDX** (Recommended for DevSecOps)
npx skills add https://github.com/ancoleman/ai-design-components --skill managing-vulnerabilitiesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 52 |
|---|---|
| repo stars | ★ 426 |
| Last updated | December 11, 2025 |
| Repository | ancoleman/ai-design-components ↗ |
What it does
Add container/SAST/DAST/SCA/secret scanning and SBOMs to CI/CD, then prioritize fixes with CVSS/EPSS/KEV.
Who is it for?
Teams adding DevSecOps scanning and risk-based remediation gates to pipelines
Skip if: A one-off manual pen test with no pipeline integration
When should I use this skill?
Building security scanning into CI/CD, generating SBOMs, or prioritizing vulnerability remediation
What you get
Layered scanning, SBOMs, and CVSS/EPSS/KEV-based prioritized remediation in CI/CD
- multi-layer scan setup
- SBOM (CycloneDX/SPDX)
- prioritization framework
By the numbers
- 5 scanning layers (container, SAST, DAST, SCA, secrets)
- 5-tier priority SLA table (P0 24h to P4 no SLA)
Files
Vulnerability Management
Implement comprehensive vulnerability detection and remediation workflows across containers, source code, dependencies, and running applications. This skill covers multi-layer scanning strategies, SBOM generation (CycloneDX and SPDX), risk-based prioritization using CVSS/EPSS/KEV, and CI/CD security gate patterns.
When to Use This Skill
Invoke this skill when:
- Building security scanning into CI/CD pipelines
- Generating Software Bills of Materials (SBOMs) for compliance
- Prioritizing vulnerability remediation using risk-based approaches
- Implementing security gates (fail builds on critical vulnerabilities)
- Scanning container images before deployment
- Detecting secrets, misconfigurations, or code vulnerabilities
- Establishing DevSecOps practices and automation
- Meeting regulatory requirements (SBOM mandates, Executive Order 14028)
Multi-Layer Scanning Strategy
Vulnerability management requires scanning at multiple layers. Each layer detects different types of security issues.
Layer Overview
Container Image Scanning
- Detects vulnerabilities in OS packages, language dependencies, and binaries
- Tools: Trivy (comprehensive), Grype (accuracy-focused), Snyk Container (commercial)
- When: Every container build, base image selection, registry admission control
SAST (Static Application Security Testing)
- Analyzes source code for security flaws before runtime
- Tools: Semgrep (fast, semantic), Snyk Code (developer-first), SonarQube (enterprise)
- When: Every commit, PR checks, main branch protection
DAST (Dynamic Application Security Testing)
- Tests running applications for vulnerabilities (black-box testing)
- Tools: OWASP ZAP (open-source), StackHawk (CI/CD native), Burp Suite (manual + automated)
- When: Staging environment testing, API validation, authentication testing
SCA (Software Composition Analysis)
- Analyzes third-party dependencies for known vulnerabilities
- Tools: Dependabot (GitHub native), Renovate (advanced), Snyk Open Source (commercial)
- When: Every build, dependency updates, license audits
Secret Scanning
- Prevents secrets from being committed to source code
- Tools: Gitleaks (fast, configurable), TruffleHog (entropy detection), GitGuardian (commercial)
- When: Pre-commit hooks, repository scanning, CI/CD artifact checks
Quick Tool Selection
Container Image → Trivy (default choice) OR Grype (accuracy focus)
Source Code → Semgrep (open-source) OR Snyk Code (commercial)
Running Application → OWASP ZAP (open-source) OR StackHawk (CI/CD native)
Dependencies → Dependabot (GitHub) OR Renovate (advanced automation)
Secrets → Gitleaks (open-source) OR GitGuardian (commercial)For detailed tool selection guidance, see references/tool-selection.md.
SBOM Generation
Software Bills of Materials (SBOMs) provide a complete inventory of software components and dependencies. Required for compliance and security transparency.
CycloneDX vs. SPDX
CycloneDX (Recommended for DevSecOps)
- Security-focused, OWASP-maintained
- Native vulnerability references
- Fast, lightweight (JSON/XML/ProtoBuf)
- Best for: DevSecOps pipelines, vulnerability tracking
SPDX (Recommended for Legal/Compliance)
- License compliance focus, ISO standard (ISO/IEC 5962:2021)
- Comprehensive legal metadata
- Government/defense preferred format
- Best for: Legal teams, compliance audits, federal requirements
Generating SBOMs
With Trivy (CycloneDX or SPDX):
# CycloneDX format (recommended for security)
trivy image --format cyclonedx --output sbom.json myapp:latest
# SPDX format (for compliance)
trivy image --format spdx-json --output sbom-spdx.json myapp:latest
# Scan SBOM (faster than re-scanning image)
trivy sbom sbom.json --severity HIGH,CRITICALWith Syft (high accuracy):
# Generate CycloneDX
syft myapp:latest -o cyclonedx-json=sbom.json
# Generate SPDX
syft myapp:latest -o spdx-json=sbom-spdx.json
# Pipe to Grype for scanning
syft myapp:latest -o json | grypeFor comprehensive SBOM patterns and storage strategies, see references/sbom-guide.md.
Vulnerability Prioritization
Not all vulnerabilities require immediate action. Prioritize based on actual risk using CVSS, EPSS, and KEV.
Modern Risk-Based Prioritization
Step 1: Gather Metrics
| Metric | Source | Purpose |
|---|---|---|
| CVSS Base Score | NVD, vendor advisories | Vulnerability severity (0-10) |
| EPSS Score | FIRST.org API | Exploitation probability (0-1) |
| KEV Status | CISA KEV Catalog | Actively exploited CVEs |
| Asset Criticality | Internal CMDB | Business impact if compromised |
| Exposure | Network topology | Internet-facing vs. internal |
Step 2: Calculate Priority
Priority Score = (CVSS × 0.3) + (EPSS × 100 × 0.3) + (KEV × 50) + (Asset × 0.2) + (Exposure × 0.2)
KEV: 1 if in KEV catalog, 0 otherwise
Asset: 1 (Critical), 0.7 (High), 0.4 (Medium), 0.1 (Low)
Exposure: 1 (Internet-facing), 0.5 (Internal), 0.1 (Isolated)Step 3: Apply SLA Tiers
| Priority | Criteria | SLA | Action |
|---|---|---|---|
| P0 - Critical | KEV + Internet-facing + Critical asset | 24 hours | Emergency patch immediately |
| P1 - High | CVSS ≥ 9.0 OR (CVSS ≥ 7.0 AND EPSS ≥ 0.1) | 7 days | Prioritize in sprint, patch ASAP |
| P2 - Medium | CVSS 7.0-8.9 OR EPSS ≥ 0.05 | 30 days | Normal sprint planning |
| P3 - Low | CVSS 4.0-6.9, EPSS < 0.05 | 90 days | Backlog, maintenance windows |
| P4 - Info | CVSS < 4.0 | No SLA | Track, address opportunistically |
Example: Log4Shell (CVE-2021-44228)
CVSS: 10.0
EPSS: 0.975 (97.5% exploitation probability)
KEV: Yes (CISA catalog)
Asset: Critical (payment API)
Exposure: Internet-facing
Priority Score = (10 × 0.3) + (97.5 × 0.3) + 50 + (1 × 0.2) + (1 × 0.2) = 82.65
Result: P0 - Critical (24-hour SLA)For complete prioritization framework and automation scripts, see references/prioritization-framework.md.
CI/CD Integration Patterns
Multi-Stage Security Pipeline
Implement progressive security gates across pipeline stages:
Stage 1: Pre-Commit (Developer Workstation)
Tools: Secret scanning (Gitleaks), SAST (Semgrep)
Threshold: Block high-confidence secrets, critical SAST findings
Speed: < 10 secondsStage 2: Pull Request (CI Pipeline)
Tools: SAST, SCA, Secret scanning
Threshold: No Critical/High vulnerabilities, no secrets
Speed: < 5 minutes
Action: Block PR merge until fixedStage 3: Build (CI Pipeline)
Tools: Container scanning (Trivy), SBOM generation
Threshold: No Critical vulnerabilities in production dependencies
Artifacts: SBOM stored, scan results uploaded
Speed: < 2 minutes
Action: Fail build on Critical findingsStage 4: Pre-Deployment (Staging)
Tools: DAST, Integration tests
Threshold: No Critical/High DAST findings
Speed: 10-30 minutes
Action: Gate deployment to productionStage 5: Production (Runtime)
Tools: Continuous scanning, runtime monitoring
Threshold: Alert on new CVEs in deployed images
Action: Alert security team, plan patchingExample: GitHub Actions Multi-Stage Scan
name: Security Scan Pipeline
on: [push, pull_request]
jobs:
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: trufflesecurity/trufflehog@main
with:
path: ./
extra_args: --only-verified
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: semgrep/semgrep-action@v1
with:
config: p/security-audit
container:
runs-on: ubuntu-latest
needs: [secrets, sast]
steps:
- uses: actions/checkout@v4
- run: docker build -t myapp:${{ github.sha }} .
- uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: 1
- name: Generate SBOM
run: |
trivy image --format cyclonedx \
--output sbom.json myapp:${{ github.sha }}
- uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom.jsonFor complete CI/CD patterns (GitLab CI, Jenkins, Azure Pipelines), see references/ci-cd-patterns.md.
Container Scanning with Trivy
Trivy is the recommended default for container scanning: comprehensive, fast, and CI/CD native.
Basic Usage
# Scan container image
trivy image alpine:latest
# Scan with severity filter
trivy image --severity HIGH,CRITICAL alpine:latest
# Fail on findings (CI/CD)
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest
# Generate SBOM
trivy image --format cyclonedx --output sbom.json alpine:latest
# Scan filesystem
trivy fs /path/to/project
# Scan Kubernetes manifests
trivy config deployment.yamlConfiguration (.trivy.yaml)
severity: HIGH,CRITICAL
exit-code: 1
ignore-unfixed: true # Only fail on fixable vulnerabilities
vuln-type: os,library
skip-dirs:
- node_modules
- vendor
ignorefile: .trivyignoreIgnoring False Positives (.trivyignore)
# False positive
CVE-2023-12345
# Accepted risk with justification
CVE-2023-67890 # Risk accepted: Not exploitable in our use case
# Development dependency (not in production)
CVE-2023-11111 # Dev dependency onlyGitHub Actions Integration
- name: Trivy Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: 1
- name: Upload to GitHub Security
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: trivy-results.sarifAlternative: Grype for Accuracy
Grype focuses on minimal false positives and works with Syft for SBOM generation.
Important: Use Grype v0.104.1 or later (credential disclosure CVE-2025-65965 patched in earlier versions).
Basic Usage
# Scan container image
grype alpine:latest
# Scan with severity threshold
grype alpine:latest --fail-on high
# Scan SBOM (faster)
grype sbom:./sbom.json
# Syft + Grype workflow
syft alpine:latest -o json | grype --fail-on criticalWhen to Use Grype
- Projects sensitive to false positives
- SBOM-first workflows (generate with Syft, scan with Grype)
- Need second opinion validation
- Anchore ecosystem users
For complete tool comparisons and selection criteria, see references/tool-selection.md.
Security Gates and Thresholds
Progressive Threshold Strategy
Balance security and development velocity with progressive gates. Configure different thresholds for PR checks (fast, HIGH+CRITICAL), builds (comprehensive), and deployments (strict, CRITICAL only).
Policy-as-Code
Use OPA (Open Policy Agent) for automated policy enforcement. Create policies to deny Critical vulnerabilities, enforce KEV catalog checks, and implement environment-specific rules.
For complete policy patterns, baseline detection, and OPA examples, see references/policy-as-code.md.
Remediation Workflows
Automated Remediation
Set up automated workflows to scan daily, extract fixable vulnerabilities, update dependencies, and create remediation pull requests automatically.
SLA Tracking
Track vulnerability remediation against SLA targets (P0: 24 hours, P1: 7 days, P2: 30 days, P3: 90 days). Monitor overdue vulnerabilities and escalate as needed.
False Positive Management
Maintain suppression files (.trivyignore) with documented justifications, review dates, and approval tracking. Implement workflows for false positive triage and approval.
For complete remediation workflows, SLA trackers, and automation scripts, see references/remediation-workflows.md.
Integration with Related Skills
building-ci-pipelines
- Add security stages to pipeline definitions
- Configure artifacts for SBOM storage
- Implement quality gates with vulnerability thresholds
secret-management
- Integrate secret scanning (Gitleaks, TruffleHog)
- Automate secret rotation on detection
- Use pre-commit hooks for prevention
infrastructure-as-code
- Scan Terraform and Kubernetes manifests with Trivy config
- Detect misconfigurations before deployment
- Enforce policy-as-code with OPA
security-hardening
- Apply remediation guidance from scan results
- Select secure base images
- Implement security best practices
compliance-frameworks
- Generate SBOMs for SOC2, ISO 27001 audits
- Track vulnerability metrics for compliance reporting
- Provide evidence for security controls
Quick Reference
Essential Commands
# Trivy: Scan image with severity filter
trivy image --severity HIGH,CRITICAL myapp:latest
# Trivy: Generate SBOM
trivy image --format cyclonedx --output sbom.json myapp:latest
# Trivy: Scan SBOM
trivy sbom sbom.json
# Grype: Scan image
grype myapp:latest --fail-on high
# Syft + Grype: SBOM workflow
syft myapp:latest -o json | grype
# Gitleaks: Scan for secrets
gitleaks detect --source . --verboseCommon Patterns
# CI/CD: Fail build on Critical
trivy image --exit-code 1 --severity CRITICAL myapp:latest
# Ignore unfixed vulnerabilities
trivy image --ignore-unfixed --severity HIGH,CRITICAL myapp:latest
# Scan only OS packages
trivy image --vuln-type os myapp:latest
# Skip specific directories
trivy fs --skip-dirs node_modules,vendor .Progressive Disclosure
This skill provides foundational vulnerability management patterns. For deeper topics:
- Tool Selection:
references/tool-selection.md- Complete decision frameworks - SBOM Patterns:
references/sbom-guide.md- Generation, storage, consumption - Prioritization:
references/prioritization-framework.md- CVSS/EPSS/KEV automation - CI/CD Integration:
references/ci-cd-patterns.md- GitLab CI, Jenkins, Azure Pipelines - Remediation:
references/remediation-workflows.md- SLA tracking, false positives - Policy-as-Code:
references/policy-as-code.md- OPA examples, security gates
Working Examples:
examples/trivy/- Trivy scanning patternsexamples/grype/- Grype + Syft workflowsexamples/ci-cd/- Complete pipeline configurationsexamples/sbom/- SBOM generation and managementexamples/prioritization/- EPSS and KEV integration scripts
Automation Scripts:
scripts/vulnerability-report.sh- Generate executive reportsscripts/sla-tracker.sh- Track remediation SLAsscripts/false-positive-manager.sh- Manage suppression rules
name: Multi-Stage Security Pipeline
on:
pull_request:
push:
branches: [main, develop]
env:
IMAGE_NAME: myapp
IMAGE_TAG: ${{ github.sha }}
jobs:
# ============================================
# Stage 1: Fast Security Checks (< 5 minutes)
# ============================================
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Gitleaks Secret Scan
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
sast-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep SAST
uses: semgrep/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
p/secrets
dependency-review:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Dependency Review
uses: actions/dependency-review-action@v3
with:
fail-on-severity: high
deny-licenses: GPL-2.0, GPL-3.0
# ============================================
# Stage 2: Container Scanning
# ============================================
container-scan:
runs-on: ubuntu-latest
needs: [secrets-scan, sast-scan]
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build container image
uses: docker/build-push-action@v5
with:
context: .
tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: 1
ignore-unfixed: true
- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: trivy-results.sarif
- name: Generate SBOM (CycloneDX)
run: |
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image \
--format cyclonedx \
${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} \
> sbom-cyclonedx.json
- name: Generate SBOM (SPDX)
run: |
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image \
--format spdx-json \
${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} \
> sbom-spdx.json
- name: Upload SBOMs
uses: actions/upload-artifact@v3
with:
name: sboms-${{ env.IMAGE_TAG }}
path: |
sbom-cyclonedx.json
sbom-spdx.json
retention-days: 90
- name: Scan SBOM for compliance
run: |
docker run --rm \
-v $(pwd):/workspace \
aquasec/trivy:latest sbom \
/workspace/sbom-cyclonedx.json \
--severity HIGH,CRITICAL \
--exit-code 1
# ============================================
# Stage 3: DAST (Staging Only)
# ============================================
dast-scan:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
needs: container-scan
steps:
- uses: actions/checkout@v4
- name: OWASP ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.7.0
with:
target: https://staging.example.com
fail_action: true
cmd_options: '-a'
# ============================================
# Stage 4: Reporting and Notifications
# ============================================
security-report:
runs-on: ubuntu-latest
if: always()
needs: [secrets-scan, sast-scan, container-scan]
steps:
- name: Download SBOM artifacts
uses: actions/download-artifact@v3
with:
name: sboms-${{ env.IMAGE_TAG }}
continue-on-error: true
- name: Generate security summary
run: |
echo "## Security Scan Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Image:** ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}" >> $GITHUB_STEP_SUMMARY
echo "**Scan Date:** $(date)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f sbom-cyclonedx.json ]; then
COMPONENT_COUNT=$(jq '.components | length' sbom-cyclonedx.json)
echo "**Components:** $COMPONENT_COUNT" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Scan Results" >> $GITHUB_STEP_SUMMARY
echo "- Secrets: ${{ needs.secrets-scan.result }}" >> $GITHUB_STEP_SUMMARY
echo "- SAST: ${{ needs.sast-scan.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Container: ${{ needs.container-scan.result }}" >> $GITHUB_STEP_SUMMARY
notify-security-team:
runs-on: ubuntu-latest
if: failure()
needs: [secrets-scan, sast-scan, container-scan]
steps:
- name: Send Slack notification
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "Security scan failed for ${{ github.repository }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Security Scan Failed* :warning:\n\n*Repository:* ${{ github.repository }}\n*Branch:* ${{ github.ref_name }}\n*Commit:* ${{ github.sha }}\n*Author:* ${{ github.actor }}"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "View Run"
},
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
#!/usr/bin/env python3
"""
EPSS (Exploit Prediction Scoring System) Integration
Fetch EPSS scores for vulnerabilities and prioritize based on exploitation probability.
Usage:
python epss-integration.py CVE-2021-44228
python epss-integration.py --scan-results trivy-results.json
"""
import argparse
import json
import requests
import sys
from datetime import datetime
from typing import Dict, List, Optional
class EPSSClient:
"""Client for FIRST.org EPSS API"""
BASE_URL = "https://api.first.org/data/v1/epss"
def __init__(self):
self.session = requests.Session()
def get_score(self, cve_id: str) -> Optional[Dict]:
"""
Fetch EPSS score for a single CVE
Returns:
{
'cve': 'CVE-2021-44228',
'epss': 0.97505,
'percentile': 0.99999,
'date': '2025-12-04'
}
"""
url = f"{self.BASE_URL}?cve={cve_id}"
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
data = response.json()
if data['status'] == 'OK' and data['data']:
result = data['data'][0]
return {
'cve': result['cve'],
'epss': float(result['epss']),
'percentile': float(result['percentile']),
'date': result['date']
}
except Exception as e:
print(f"Error fetching EPSS for {cve_id}: {e}", file=sys.stderr)
return None
def get_scores_bulk(self, cve_ids: List[str]) -> Dict[str, Dict]:
"""
Fetch EPSS scores for multiple CVEs
Returns:
{
'CVE-2021-44228': {'epss': 0.97505, 'percentile': 0.99999},
'CVE-2023-12345': {'epss': 0.001, 'percentile': 0.25},
...
}
"""
# API supports comma-separated CVE list
cve_list = ','.join(cve_ids[:100]) # Limit to 100 per request
url = f"{self.BASE_URL}?cve={cve_list}"
try:
response = self.session.get(url, timeout=30)
response.raise_for_status()
data = response.json()
results = {}
if data['status'] == 'OK':
for item in data['data']:
results[item['cve']] = {
'epss': float(item['epss']),
'percentile': float(item['percentile']),
'date': item['date']
}
return results
except Exception as e:
print(f"Error fetching bulk EPSS: {e}", file=sys.stderr)
return {}
def enrich_scan_results(scan_file: str, output_file: str = None):
"""
Enrich Trivy scan results with EPSS scores
Args:
scan_file: Path to Trivy JSON scan results
output_file: Optional path for enriched output
"""
# Load scan results
with open(scan_file) as f:
scan_data = json.load(f)
# Extract CVEs
cves = set()
for result in scan_data.get('Results', []):
for vuln in result.get('Vulnerabilities', []):
cve_id = vuln.get('VulnerabilityID')
if cve_id and cve_id.startswith('CVE-'):
cves.add(cve_id)
print(f"Found {len(cves)} unique CVEs in scan results")
# Fetch EPSS scores
epss_client = EPSSClient()
epss_scores = epss_client.get_scores_bulk(list(cves))
print(f"Retrieved EPSS scores for {len(epss_scores)} CVEs")
# Enrich vulnerabilities with EPSS
for result in scan_data.get('Results', []):
for vuln in result.get('Vulnerabilities', []):
cve_id = vuln.get('VulnerabilityID')
if cve_id in epss_scores:
vuln['EPSS'] = epss_scores[cve_id]
# Save enriched results
if output_file:
with open(output_file, 'w') as f:
json.dump(scan_data, f, indent=2)
print(f"Enriched results saved to {output_file}")
# Print summary
print("\n=== EPSS Analysis ===")
# High EPSS vulnerabilities (> 10% probability)
high_epss = []
for result in scan_data.get('Results', []):
for vuln in result.get('Vulnerabilities', []):
epss_data = vuln.get('EPSS', {})
epss_score = epss_data.get('epss', 0)
if epss_score >= 0.1:
high_epss.append({
'cve': vuln['VulnerabilityID'],
'epss': epss_score,
'cvss': vuln.get('CVSS', {}).get('nvd', {}).get('V3Score', 0),
'package': vuln.get('PkgName'),
'severity': vuln.get('Severity')
})
# Sort by EPSS descending
high_epss.sort(key=lambda x: x['epss'], reverse=True)
print(f"\nHigh Exploitation Probability (EPSS >= 10%): {len(high_epss)}")
print("\nTop 10 by EPSS:")
print(f"{'CVE':<20} {'EPSS':<8} {'CVSS':<6} {'Severity':<10} {'Package'}")
print("-" * 80)
for vuln in high_epss[:10]:
print(f"{vuln['cve']:<20} {vuln['epss']:.4f} {vuln['cvss']:<6.1f} {vuln['severity']:<10} {vuln['package']}")
# Recommended priority
print("\n=== Recommended Priority ===")
p1_count = sum(1 for v in high_epss if v['epss'] >= 0.5)
p2_count = sum(1 for v in high_epss if 0.1 <= v['epss'] < 0.5)
print(f"P1 (EPSS >= 50%): {p1_count} vulnerabilities - Fix within 7 days")
print(f"P2 (EPSS 10-50%): {p2_count} vulnerabilities - Fix within 30 days")
def analyze_single_cve(cve_id: str):
"""Analyze a single CVE and display EPSS information"""
epss_client = EPSSClient()
result = epss_client.get_score(cve_id)
if result:
print(f"\n=== EPSS Analysis for {cve_id} ===")
print(f"EPSS Score: {result['epss']:.4f} ({result['epss']*100:.2f}%)")
print(f"Percentile: {result['percentile']:.4f}")
print(f"Date: {result['date']}")
# Interpretation
epss = result['epss']
if epss >= 0.9:
priority = "P0/P1"
action = "IMMEDIATE - Very high exploitation probability"
elif epss >= 0.5:
priority = "P1"
action = "High priority - Likely to be exploited"
elif epss >= 0.1:
priority = "P2"
action = "Moderate priority - Monitor closely"
else:
priority = "P3/P4"
action = "Low priority - Unlikely to be exploited"
print(f"\nRecommended Priority: {priority}")
print(f"Action: {action}")
else:
print(f"Could not retrieve EPSS data for {cve_id}")
def main():
parser = argparse.ArgumentParser(description='EPSS Integration Tool')
parser.add_argument('cve', nargs='?', help='Single CVE to analyze (e.g., CVE-2021-44228)')
parser.add_argument('--scan-results', help='Path to Trivy JSON scan results')
parser.add_argument('--output', help='Output file for enriched results')
args = parser.parse_args()
if args.scan_results:
enrich_scan_results(args.scan_results, args.output)
elif args.cve:
analyze_single_cve(args.cve)
else:
parser.print_help()
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
CISA KEV (Known Exploited Vulnerabilities) Checker
Check if CVEs are in the CISA KEV catalog (actively exploited in the wild).
Usage:
python kev-checker.py CVE-2021-44228
python kev-checker.py --scan-results trivy-results.json
python kev-checker.py --update-cache
"""
import argparse
import json
import requests
import sys
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Set
class KEVCatalog:
"""CISA Known Exploited Vulnerabilities Catalog"""
CATALOG_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
CACHE_FILE = Path.home() / ".cache" / "kev-catalog.json"
CACHE_DURATION = timedelta(days=1)
def __init__(self):
self.catalog = None
self.kev_set: Set[str] = set()
def update_cache(self):
"""Download latest KEV catalog"""
print("Downloading latest KEV catalog from CISA...")
try:
response = requests.get(self.CATALOG_URL, timeout=30)
response.raise_for_status()
catalog_data = response.json()
# Create cache directory if needed
self.CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
# Save to cache
with open(self.CACHE_FILE, 'w') as f:
json.dump({
'updated': datetime.now().isoformat(),
'catalog': catalog_data
}, f, indent=2)
print(f"KEV catalog updated: {len(catalog_data.get('vulnerabilities', []))} entries")
self.catalog = catalog_data
self._build_kev_set()
except Exception as e:
print(f"Error updating KEV catalog: {e}", file=sys.stderr)
sys.exit(1)
def load_catalog(self, force_update: bool = False):
"""Load KEV catalog from cache or download"""
# Check if cache exists and is recent
if not force_update and self.CACHE_FILE.exists():
with open(self.CACHE_FILE) as f:
cached = json.load(f)
updated = datetime.fromisoformat(cached['updated'])
# Use cache if less than 1 day old
if datetime.now() - updated < self.CACHE_DURATION:
self.catalog = cached['catalog']
self._build_kev_set()
return
# Cache missing, expired, or force update requested
self.update_cache()
def _build_kev_set(self):
"""Build set of CVE IDs for fast lookup"""
if self.catalog:
self.kev_set = {
vuln['cveID']
for vuln in self.catalog.get('vulnerabilities', [])
}
def is_kev(self, cve_id: str) -> bool:
"""Check if CVE is in KEV catalog"""
if not self.catalog:
self.load_catalog()
return cve_id in self.kev_set
def get_kev_details(self, cve_id: str) -> Optional[Dict]:
"""Get KEV catalog entry details"""
if not self.catalog:
self.load_catalog()
for vuln in self.catalog.get('vulnerabilities', []):
if vuln['cveID'] == cve_id:
return vuln
return None
def get_all_kevs(self) -> List[Dict]:
"""Get all KEV entries"""
if not self.catalog:
self.load_catalog()
return self.catalog.get('vulnerabilities', [])
def check_single_cve(cve_id: str):
"""Check if a single CVE is in KEV catalog"""
kev = KEVCatalog()
kev.load_catalog()
print(f"\n=== KEV Status for {cve_id} ===")
if kev.is_kev(cve_id):
details = kev.get_kev_details(cve_id)
print(f"⚠️ CRITICAL: {cve_id} is in CISA KEV catalog!")
print(f"\nVendor/Project: {details.get('vendorProject')}")
print(f"Product: {details.get('product')}")
print(f"Vulnerability: {details.get('vulnerabilityName')}")
print(f"Date Added: {details.get('dateAdded')}")
print(f"Due Date: {details.get('dueDate')}")
print(f"\nDescription: {details.get('shortDescription')}")
print(f"Required Action: {details.get('requiredAction')}")
print(f"\n🔴 PRIORITY: P0 - Immediate remediation required (24-hour SLA)")
else:
print(f"✓ {cve_id} is NOT in KEV catalog")
print("No evidence of active exploitation (based on CISA data)")
def check_scan_results(scan_file: str, output_file: str = None):
"""Check Trivy scan results against KEV catalog"""
kev = KEVCatalog()
kev.load_catalog()
# Load scan results
with open(scan_file) as f:
scan_data = json.load(f)
# Find KEV vulnerabilities
kev_found = []
all_cves = set()
for result in scan_data.get('Results', []):
for vuln in result.get('Vulnerabilities', []):
cve_id = vuln.get('VulnerabilityID')
if cve_id and cve_id.startswith('CVE-'):
all_cves.add(cve_id)
if kev.is_kev(cve_id):
kev_details = kev.get_kev_details(cve_id)
kev_found.append({
'cve': cve_id,
'package': vuln.get('PkgName'),
'severity': vuln.get('Severity'),
'fixed_version': vuln.get('FixedVersion'),
'kev_added': kev_details.get('dateAdded'),
'kev_due': kev_details.get('dueDate'),
'kev_action': kev_details.get('requiredAction')
})
# Mark in scan results
vuln['KEV'] = kev_details
# Save enriched results
if output_file:
with open(output_file, 'w') as f:
json.dump(scan_data, f, indent=2)
print(f"Enriched results saved to {output_file}")
# Print summary
print(f"\n=== KEV Analysis ===")
print(f"Total unique CVEs scanned: {len(all_cves)}")
print(f"CVEs in CISA KEV catalog: {len(kev_found)}")
if kev_found:
print(f"\n🔴 CRITICAL: {len(kev_found)} actively exploited vulnerabilities found!")
print("\n" + "=" * 100)
print(f"{'CVE':<20} {'Package':<25} {'Severity':<10} {'Fixed':<15} {'KEV Added'}")
print("=" * 100)
for vuln in kev_found:
fixed = vuln['fixed_version'] or 'No fix'
print(f"{vuln['cve']:<20} {vuln['package']:<25} {vuln['severity']:<10} {fixed:<15} {vuln['kev_added']}")
print("\n⚠️ ACTION REQUIRED:")
print(" - Priority: P0 (CRITICAL)")
print(" - SLA: 24 hours")
print(" - These CVEs are actively exploited in the wild")
print(" - Immediate patching required")
print("\nRemediation steps:")
for vuln in kev_found:
if vuln['fixed_version']:
print(f" - Update {vuln['package']} to {vuln['fixed_version']}")
else:
print(f" - Apply mitigations for {vuln['cve']}: {vuln['kev_action']}")
# Exit with error if KEV found
sys.exit(1)
else:
print("\n✓ No KEV catalog vulnerabilities found")
print("Continue with normal vulnerability prioritization")
def list_recent_kevs(days: int = 30):
"""List recently added KEV entries"""
kev = KEVCatalog()
kev.load_catalog()
cutoff_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
recent = [
vuln for vuln in kev.get_all_kevs()
if vuln.get('dateAdded', '') >= cutoff_date
]
# Sort by date added (newest first)
recent.sort(key=lambda x: x.get('dateAdded', ''), reverse=True)
print(f"\n=== KEV Entries Added in Last {days} Days ===")
print(f"Total: {len(recent)} entries\n")
print(f"{'Date Added':<12} {'CVE':<20} {'Vendor/Product':<40} {'Due Date'}")
print("=" * 100)
for vuln in recent[:50]: # Show up to 50
vendor_product = f"{vuln.get('vendorProject')} {vuln.get('product')}"
print(f"{vuln.get('dateAdded'):<12} {vuln.get('cveID'):<20} {vendor_product:<40} {vuln.get('dueDate')}")
def main():
parser = argparse.ArgumentParser(description='CISA KEV Catalog Checker')
parser.add_argument('cve', nargs='?', help='Single CVE to check (e.g., CVE-2021-44228)')
parser.add_argument('--scan-results', help='Path to Trivy JSON scan results')
parser.add_argument('--output', help='Output file for enriched results')
parser.add_argument('--update-cache', action='store_true', help='Force update KEV catalog cache')
parser.add_argument('--list-recent', type=int, metavar='DAYS', help='List KEVs added in last N days')
args = parser.parse_args()
if args.update_cache:
kev = KEVCatalog()
kev.update_cache()
print("KEV catalog cache updated successfully")
elif args.list_recent:
list_recent_kevs(args.list_recent)
elif args.scan_results:
check_scan_results(args.scan_results, args.output)
elif args.cve:
check_single_cve(args.cve)
else:
parser.print_help()
sys.exit(1)
if __name__ == '__main__':
main()
#!/bin/bash
# Trivy Basic Container Scanning Examples
set -e
IMAGE="${1:-alpine:latest}"
echo "=== Trivy Basic Scanning Examples ==="
echo "Image: $IMAGE"
echo ""
# Example 1: Basic scan
echo "1. Basic scan (all vulnerabilities)"
trivy image "$IMAGE"
echo ""
# Example 2: Severity filtering
echo "2. Scan with severity filter (HIGH, CRITICAL only)"
trivy image --severity HIGH,CRITICAL "$IMAGE"
echo ""
# Example 3: Exit code (fail build on findings)
echo "3. Exit code mode (fail on HIGH/CRITICAL)"
trivy image --severity HIGH,CRITICAL --exit-code 1 "$IMAGE" || echo "Build would fail due to vulnerabilities"
echo ""
# Example 4: Ignore unfixed vulnerabilities
echo "4. Ignore unfixed vulnerabilities"
trivy image --ignore-unfixed --severity HIGH,CRITICAL "$IMAGE"
echo ""
# Example 5: Specific vulnerability types
echo "5. Scan only OS packages"
trivy image --vuln-type os "$IMAGE"
echo ""
# Example 6: JSON output
echo "6. JSON output for automation"
trivy image --format json --output scan-results.json "$IMAGE"
echo "Results saved to scan-results.json"
jq '.Results[].Vulnerabilities | length' scan-results.json | head -1 | xargs echo "Total vulnerabilities:"
echo ""
# Example 7: Table output (formatted)
echo "7. Table output (formatted)"
trivy image --format table "$IMAGE"
echo ""
# Example 8: Scan specific directories
echo "8. Skip directories"
trivy image --skip-dirs /usr/share/doc,/var/lib/dpkg "$IMAGE"
echo ""
# Example 9: Quiet mode
echo "9. Quiet mode (minimal output)"
trivy image --quiet --severity CRITICAL "$IMAGE"
echo ""
# Example 10: Template output (custom format)
echo "10. Custom template output"
trivy image --format template --template '{{- range .Results }}{{ range .Vulnerabilities }}{{ .VulnerabilityID }},{{ .Severity }},{{ .PkgName }}
{{ end }}{{ end }}' "$IMAGE" > vulnerabilities.csv
echo "CSV output saved to vulnerabilities.csv"
head -5 vulnerabilities.csv
echo ""
echo "=== Scan complete ==="name: Trivy Container Scanning
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 0 * * *' # Daily scan
env:
IMAGE_NAME: myapp
REGISTRY: ghcr.io
jobs:
trivy-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
packages: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t ${{ env.IMAGE_NAME }}:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE_NAME }}:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: 1
- name: Upload Trivy results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: trivy-results.sarif
- name: Generate SBOM
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image \
--format cyclonedx \
--output /tmp/sbom.json \
${{ env.IMAGE_NAME }}:${{ github.sha }}
docker cp $(docker ps -lq):/tmp/sbom.json sbom.json
- name: Upload SBOM as artifact
uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom.json
retention-days: 90
- name: Scan SBOM
run: |
docker run --rm -v $(pwd):/workspace \
aquasec/trivy:latest sbom \
/workspace/sbom.json \
--severity HIGH,CRITICAL
- name: Create vulnerability report
if: failure()
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image \
--format json \
--output trivy-report.json \
${{ env.IMAGE_NAME }}:${{ github.sha }}
cat > vulnerability-summary.md << EOF
## Vulnerability Scan Results
**Image:** ${{ env.IMAGE_NAME }}:${{ github.sha }}
**Scan Date:** $(date)
### Summary
$(jq -r '
[.Results[].Vulnerabilities[]] |
{
total: length,
critical: ([.[] | select(.Severity == "CRITICAL")] | length),
high: ([.[] | select(.Severity == "HIGH")] | length)
} |
"- Total: \(.total)\n- Critical: \(.critical)\n- High: \(.high)"
' trivy-report.json)
### Action Required
Fix HIGH and CRITICAL vulnerabilities before merging.
EOF
- name: Comment on PR
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const summary = fs.readFileSync('vulnerability-summary.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: summary
});
trivy-config-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Trivy misconfiguration scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: config
scan-ref: .
format: sarif
output: trivy-config-results.sarif
exit-code: 1
- name: Upload config scan results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: trivy-config-results.sarif
#!/bin/bash
# Trivy SBOM Generation and Scanning Examples
set -e
IMAGE="${1:-alpine:latest}"
echo "=== Trivy SBOM Examples ==="
echo "Image: $IMAGE"
echo ""
# Example 1: Generate CycloneDX SBOM
echo "1. Generate CycloneDX SBOM (recommended for security)"
trivy image --format cyclonedx --output sbom-cyclonedx.json "$IMAGE"
echo "CycloneDX SBOM saved to sbom-cyclonedx.json"
jq '.components | length' sbom-cyclonedx.json | xargs echo "Component count:"
echo ""
# Example 2: Generate SPDX SBOM
echo "2. Generate SPDX SBOM (for compliance)"
trivy image --format spdx-json --output sbom-spdx.json "$IMAGE"
echo "SPDX SBOM saved to sbom-spdx.json"
jq '.packages | length' sbom-spdx.json | xargs echo "Package count:"
echo ""
# Example 3: Scan SBOM (faster than re-scanning image)
echo "3. Scan existing SBOM"
trivy sbom sbom-cyclonedx.json --severity HIGH,CRITICAL
echo ""
# Example 4: Generate SBOM with vulnerabilities
echo "4. Generate SBOM with vulnerability data included"
trivy image --format cyclonedx --scanners vuln --output sbom-with-vulns.json "$IMAGE"
echo "SBOM with vulnerabilities saved to sbom-with-vulns.json"
jq '.vulnerabilities | length' sbom-with-vulns.json | xargs echo "Vulnerability count in SBOM:"
echo ""
# Example 5: SBOM for filesystem
echo "5. Generate SBOM for local filesystem"
trivy fs --format cyclonedx --output sbom-fs.json .
echo "Filesystem SBOM saved to sbom-fs.json"
echo ""
# Example 6: Compare SBOMs
echo "6. Extract package lists for comparison"
jq -r '.components[] | "\(.name)@\(.version)"' sbom-cyclonedx.json | sort > packages.txt
echo "Package list saved to packages.txt"
head -10 packages.txt
echo ""
# Clean up
echo "=== SBOM generation complete ==="
echo "Files created:"
ls -lh sbom-*.json packages.txt
skill: "managing-vulnerabilities"
version: "1.0"
domain: "security"
base_outputs:
- path: ".github/dependabot.yml"
must_contain: ["version: 2", "updates:", "package-ecosystem"]
description: "Dependabot configuration for automated dependency scanning and updates"
- path: ".trivy.yaml"
must_contain: ["severity:", "exit-code:", "vuln-type:"]
description: "Trivy scanner configuration with severity thresholds and scan settings"
- path: ".trivyignore"
must_contain: ["# False positive", "# Risk accepted", "CVE-"]
description: "Trivy ignore file for managing false positives and accepted risks with justifications"
- path: "security/VULNERABILITY_MANAGEMENT.md"
must_contain: ["Prioritization", "SLA", "Remediation"]
description: "Vulnerability management process documentation including prioritization framework and SLAs"
- path: "security/sbom/"
must_contain: ["cyclonedx", "spdx"]
description: "SBOM storage directory for CycloneDX and SPDX formats"
conditional_outputs:
maturity:
starter:
- path: ".github/workflows/security-scan.yml"
must_contain: ["trivy-action", "severity: HIGH,CRITICAL", "exit-code: 1"]
description: "Basic security scanning workflow with Trivy for HIGH and CRITICAL vulnerabilities"
- path: "security/false-positives.md"
must_contain: ["CVE-", "Justification", "Review Date"]
description: "False positive tracking document with justifications and review dates"
intermediate:
- path: ".github/workflows/security-multi-stage.yml"
must_contain: ["secrets-scan", "sast-scan", "container-scan", "needs:"]
description: "Multi-stage security pipeline with secrets, SAST, and container scanning"
- path: "security/policies/vulnerability-policy.rego"
must_contain: ["package trivy", "deny[", "severity"]
description: "OPA policy for automated vulnerability enforcement using policy-as-code"
- path: ".gitleaks.toml"
must_contain: ["[allowlist]", "[[rules]]", "description"]
description: "Gitleaks configuration for secret scanning with custom rules and allowlist"
- path: "security/reports/vulnerability-dashboard.json"
must_contain: ["vulnerabilities", "sla_status", "remediation_progress"]
description: "Vulnerability dashboard data for tracking remediation progress against SLAs"
advanced:
- path: ".github/workflows/security-advanced.yml"
must_contain: ["secrets-scan", "sast-scan", "container-scan", "dast-scan", "sbom"]
description: "Advanced security pipeline with SAST, DAST, container scanning, SBOM generation, and EPSS integration"
- path: "security/policies/admission-control.rego"
must_contain: ["package kubernetes.admission", "deny[", "vulnerability"]
description: "Kubernetes admission controller policy for blocking vulnerable container deployments"
- path: "security/automation/remediation-workflow.yml"
must_contain: ["schedule:", "trivy", "pull_request", "automated"]
description: "Automated remediation workflow that creates PRs for fixable vulnerabilities"
- path: "security/reports/executive-summary.json"
must_contain: ["cvss", "epss", "kev", "priority_score", "sla_compliance"]
description: "Executive security report with CVSS, EPSS, KEV data, and SLA compliance metrics"
- path: "security/integrations/epss-checker.py"
must_contain: ["FIRST.org", "EPSS", "probability"]
description: "EPSS integration script for calculating exploitation probability scores"
- path: "security/integrations/kev-checker.py"
must_contain: ["CISA", "KEV", "catalog"]
description: "KEV catalog integration for identifying actively exploited vulnerabilities"
ci_cd:
github_actions:
- path: ".github/workflows/container-scan.yml"
must_contain: ["aquasecurity/trivy-action", "upload-sarif", "github/codeql-action"]
description: "GitHub Actions workflow for container scanning with SARIF upload to Security tab"
- path: ".github/workflows/sbom-generation.yml"
must_contain: ["trivy image", "cyclonedx", "upload-artifact"]
description: "GitHub Actions workflow for SBOM generation and artifact storage"
gitlab_ci:
- path: ".gitlab-ci.yml"
must_contain: ["trivy:latest", "artifacts:", "security_scan"]
description: "GitLab CI pipeline with Trivy scanning and artifact management"
jenkins:
- path: "Jenkinsfile"
must_contain: ["stage('Security Scan')", "trivy", "archiveArtifacts"]
description: "Jenkins pipeline with security scanning stages and SBOM artifact archiving"
azure_pipelines:
- path: "azure-pipelines.yml"
must_contain: ["- task: Docker", "trivy", "PublishBuildArtifacts"]
description: "Azure Pipelines configuration with container scanning and SBOM publishing"
scaffolding:
- path: "security/"
description: "Security directory for vulnerability management artifacts, policies, and reports"
- path: "security/policies/"
description: "Policy-as-code directory for OPA policies and security gates"
- path: "security/reports/"
description: "Vulnerability reports directory for tracking scan results and metrics"
- path: "security/sbom/"
description: "SBOM storage directory for Software Bills of Materials (CycloneDX and SPDX)"
- path: "security/integrations/"
description: "Security integrations directory for EPSS, KEV, and external security tools"
- path: "security/automation/"
description: "Automation scripts directory for vulnerability remediation and reporting"
- path: ".github/workflows/"
description: "GitHub Actions workflows directory for CI/CD security pipelines"
metadata:
primary_blueprints:
- "security"
contributes_to:
- "Vulnerability management"
- "DevSecOps workflows"
- "SBOM generation and compliance"
- "Security scanning automation"
- "Risk-based prioritization"
related_skills:
- "building-ci-pipelines"
- "secret-management"
- "infrastructure-as-code"
- "security-hardening"
- "compliance-frameworks"
key_tools:
- "Trivy"
- "Grype"
- "Syft"
- "Semgrep"
- "Gitleaks"
- "OWASP ZAP"
- "Dependabot"
- "OPA (Open Policy Agent)"
standards:
- "CycloneDX (SBOM)"
- "SPDX (SBOM)"
- "CVSS (Severity)"
- "EPSS (Exploitation Probability)"
- "CISA KEV Catalog"
- "SARIF (Security Analysis)"
CI/CD Security Scanning Patterns
Complete CI/CD integration patterns for GitHub Actions, GitLab CI, Jenkins, and Azure Pipelines.
Table of Contents
1. GitHub Actions 2. GitLab CI 3. Jenkins 4. Azure Pipelines
---
GitHub Actions
Multi-Stage Security Pipeline
name: Security Scan Pipeline
on:
pull_request:
push:
branches: [main]
jobs:
# Stage 1: Fast checks (secrets, SAST)
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Gitleaks Scan
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep Scan
uses: semgrep/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/owasp-top-ten
# Stage 2: Dependency scanning
dependencies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Dependency Review
uses: actions/dependency-review-action@v3
with:
fail-on-severity: high
# Stage 3: Container scanning (after fast checks)
container:
runs-on: ubuntu-latest
needs: [secrets, sast]
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: 1
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: trivy-results.sarif
- name: Generate SBOM
run: |
trivy image --format cyclonedx \
--output sbom.json \
myapp:${{ github.sha }}
- name: Upload SBOM
uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom.json
# Stage 4: DAST (only on main branch)
dast:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
needs: container
steps:
- uses: actions/checkout@v4
- name: OWASP ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.7.0
with:
target: https://staging.example.com
fail_action: trueGitLab CI
stages:
- security-fast
- security-scan
- security-test
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# Fast checks
gitleaks:
stage: security-fast
image: zricethezav/gitleaks:latest
script:
- gitleaks detect --source . --verbose --exit-code 1
allow_failure: false
semgrep:
stage: security-fast
image: returntocorp/semgrep
script:
- semgrep ci --config=p/security-audit
allow_failure: false
# Container scanning
trivy-scan:
stage: security-scan
image: aquasec/trivy:latest
script:
- trivy image --severity HIGH,CRITICAL --exit-code 1 $IMAGE_TAG
artifacts:
reports:
container_scanning: gl-container-scanning-report.json
# SBOM generation
sbom-generate:
stage: security-scan
image: aquasec/trivy:latest
script:
- trivy image --format cyclonedx --output sbom.json $IMAGE_TAG
artifacts:
paths:
- sbom.json
expire_in: 90 days
# DAST
zap-scan:
stage: security-test
image: owasp/zap2docker-stable
script:
- zap-baseline.py -t https://staging.example.com -r zap-report.html
artifacts:
paths:
- zap-report.html
when: always
only:
- mainJenkins
pipeline {
agent any
environment {
IMAGE_NAME = "myapp"
IMAGE_TAG = "${env.GIT_COMMIT}"
}
stages {
stage('Secret Scanning') {
steps {
sh 'gitleaks detect --source . --verbose --exit-code 1'
}
}
stage('SAST') {
steps {
sh 'semgrep ci --config=p/security-audit'
}
}
stage('Build Image') {
steps {
sh "docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ."
}
}
stage('Container Scan') {
steps {
sh """
trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
--format json \
--output trivy-results.json \
${IMAGE_NAME}:${IMAGE_TAG}
"""
}
}
stage('Generate SBOM') {
steps {
sh """
trivy image --format cyclonedx \
--output sbom.json \
${IMAGE_NAME}:${IMAGE_TAG}
"""
archiveArtifacts artifacts: 'sbom.json'
}
}
stage('DAST') {
when {
branch 'main'
}
steps {
sh '''
docker run -v $(pwd):/zap/wrk/:rw \
owasp/zap2docker-stable \
zap-baseline.py -t https://staging.example.com \
-r zap-report.html
'''
}
}
}
post {
always {
publishHTML([
reportDir: '.',
reportFiles: 'zap-report.html',
reportName: 'ZAP Security Report'
])
}
failure {
emailext(
subject: "Security Scan Failed: ${env.JOB_NAME}",
body: "Build ${env.BUILD_NUMBER} failed security scans.",
to: "security-team@example.com"
)
}
}
}Azure Pipelines
trigger:
branches:
include:
- main
- develop
pool:
vmImage: 'ubuntu-latest'
variables:
imageName: 'myapp'
imageTag: $(Build.BuildId)
stages:
- stage: SecurityFast
displayName: 'Fast Security Checks'
jobs:
- job: Secrets
steps:
- task: Bash@3
displayName: 'Gitleaks Scan'
inputs:
targetType: 'inline'
script: |
wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.0/gitleaks_8.18.0_linux_x64.tar.gz
tar -xzf gitleaks_8.18.0_linux_x64.tar.gz
./gitleaks detect --source . --verbose --exit-code 1
- job: SAST
steps:
- task: Bash@3
displayName: 'Semgrep Scan'
inputs:
targetType: 'inline'
script: |
pip install semgrep
semgrep ci --config=p/security-audit
- stage: ContainerScan
displayName: 'Container Security Scan'
dependsOn: SecurityFast
jobs:
- job: BuildAndScan
steps:
- task: Docker@2
displayName: 'Build image'
inputs:
command: 'build'
repository: $(imageName)
tags: $(imageTag)
- task: Bash@3
displayName: 'Trivy Scan'
inputs:
targetType: 'inline'
script: |
wget https://github.com/aquasecurity/trivy/releases/download/v0.48.0/trivy_0.48.0_Linux-64bit.tar.gz
tar -xzf trivy_0.48.0_Linux-64bit.tar.gz
./trivy image --severity HIGH,CRITICAL --exit-code 1 $(imageName):$(imageTag)
- task: Bash@3
displayName: 'Generate SBOM'
inputs:
targetType: 'inline'
script: |
./trivy image --format cyclonedx --output $(Build.ArtifactStagingDirectory)/sbom.json $(imageName):$(imageTag)
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.ArtifactStagingDirectory)/sbom.json'
artifactName: 'sbom'See complete examples in examples/ci-cd/ directory.
Policy-as-Code for Vulnerability Management
Implement security policies using OPA (Open Policy Agent) and automated enforcement.
Table of Contents
1. OPA Vulnerability Policies 2. Using OPA with Trivy 3. Advanced Policies 4. Policy Testing
---
OPA Vulnerability Policies
Basic Vulnerability Policy
package vulnerability
# Deny if any Critical vulnerabilities found
deny[msg] {
input.Vulnerabilities[_].Severity == "CRITICAL"
msg := "Critical vulnerabilities detected - build failed"
}
# Deny if High vulnerabilities in production dependencies
deny[msg] {
vuln := input.Vulnerabilities[_]
vuln.Severity == "HIGH"
not contains(vuln.PkgPath, "node_modules/dev")
msg := sprintf("High vulnerability in production: %v (%v)", [vuln.PkgName, vuln.VulnerabilityID])
}
# Warn if unfixed vulnerabilities
warn[msg] {
vuln := input.Vulnerabilities[_]
vuln.FixedVersion == ""
msg := sprintf("Unfixed vulnerability: %v in %v", [vuln.VulnerabilityID, vuln.PkgName])
}KEV-Aware Policy
package vulnerability
import future.keywords.in
# KEV catalog data (load separately or embed)
kev_catalog := {
"CVE-2021-44228",
"CVE-2021-45046",
# ... more KEV CVEs
}
# Deny if vulnerability is in KEV catalog
deny[msg] {
vuln := input.Vulnerabilities[_]
vuln.VulnerabilityID in kev_catalog
msg := sprintf("KEV vulnerability detected: %v - immediate remediation required", [vuln.VulnerabilityID])
}
# Helper function to check KEV
is_kev(cve_id) {
cve_id in kev_catalog
}CVSS-Based Policy
package vulnerability
# Deny based on CVSS score and package location
deny[msg] {
vuln := input.Vulnerabilities[_]
cvss_score := vuln.CVSS.nvd.V3Score
cvss_score >= 7.0
is_production_dependency(vuln.PkgPath)
msg := sprintf("CVSS %v vulnerability in production: %v", [cvss_score, vuln.VulnerabilityID])
}
# Helper: Check if production dependency
is_production_dependency(path) {
not contains(path, "test")
not contains(path, "dev")
not contains(path, "node_modules/@types")
}Using OPA with Trivy
Scan and Evaluate Policy
#!/bin/bash
# Scan with Trivy and evaluate OPA policy
IMAGE="myapp:latest"
POLICY_DIR="policies"
# Scan image
trivy image --format json --output scan.json "$IMAGE"
# Evaluate policy
opa eval --data "$POLICY_DIR" --input scan.json \
'data.vulnerability.deny' --format pretty
# Exit code based on policy
if opa eval --data "$POLICY_DIR" --input scan.json 'data.vulnerability.deny' | grep -q "true"; then
echo "Policy violations found - build failed"
exit 1
fiCI/CD Integration
# .github/workflows/policy-check.yml
name: Policy Enforcement
on: [push, pull_request]
jobs:
security-policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan with Trivy
run: trivy image --format json --output scan.json myapp:${{ github.sha }}
- name: Install OPA
run: |
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64
chmod +x opa
- name: Evaluate security policy
run: |
./opa eval --data policies/ --input scan.json \
'data.vulnerability.deny' --format pretty
- name: Enforce policy
run: |
VIOLATIONS=$(./opa eval --data policies/ --input scan.json 'data.vulnerability.deny' --format values)
if [ -n "$VIOLATIONS" ]; then
echo "Policy violations:"
echo "$VIOLATIONS"
exit 1
fiAdvanced Policies
Multi-Tier Policy
package vulnerability
# P0: Critical + KEV
p0_violations[msg] {
vuln := input.Vulnerabilities[_]
vuln.Severity == "CRITICAL"
is_kev(vuln.VulnerabilityID)
msg := sprintf("P0: %v (KEV + Critical)", [vuln.VulnerabilityID])
}
# P1: High CVSS or High + EPSS
p1_violations[msg] {
vuln := input.Vulnerabilities[_]
cvss := vuln.CVSS.nvd.V3Score
cvss >= 9.0
msg := sprintf("P1: %v (CVSS %v)", [vuln.VulnerabilityID, cvss])
}
# Aggregate violations
violations := array.concat(p0_violations, p1_violations)
# Deny if any P0 or P1
deny[msg] {
count(violations) > 0
msg := sprintf("Found %v high-priority violations", [count(violations)])
}Environment-Specific Policies
package vulnerability
# Get environment from metadata
environment := input.Metadata.Environment
# Production: Zero tolerance
deny[msg] {
environment == "production"
input.Vulnerabilities[_].Severity == "CRITICAL"
msg := "Production: No Critical vulnerabilities allowed"
}
# Staging: Warn only
warn[msg] {
environment == "staging"
input.Vulnerabilities[_].Severity == "CRITICAL"
msg := "Staging: Critical vulnerabilities present (warning only)"
}
# Development: Allow but track
info[msg] {
environment == "development"
count(input.Vulnerabilities) > 0
msg := sprintf("Development: %v vulnerabilities tracked", [count(input.Vulnerabilities)])
}Policy Testing
OPA Policy Tests
package vulnerability_test
import data.vulnerability
# Test: Deny Critical vulnerabilities
test_deny_critical {
input := {"Vulnerabilities": [{"Severity": "CRITICAL", "VulnerabilityID": "CVE-2023-12345"}]}
count(vulnerability.deny) > 0
}
# Test: Allow Low vulnerabilities
test_allow_low {
input := {"Vulnerabilities": [{"Severity": "LOW", "VulnerabilityID": "CVE-2023-99999"}]}
count(vulnerability.deny) == 0
}
# Test: KEV detection
test_kev_detection {
input := {"Vulnerabilities": [{"VulnerabilityID": "CVE-2021-44228", "Severity": "CRITICAL"}]}
count(vulnerability.deny) > 0
}Run tests:
opa test policies/ -vSee complete policies in examples/opa/ directory.
Vulnerability Prioritization Framework
Risk-based vulnerability prioritization using CVSS, EPSS, KEV, and business context.
Table of Contents
1. Overview 2. CVSS Scoring 3. EPSS Integration 4. KEV Catalog 5. Risk-Based Formula 6. SLA Tiers 7. Automation Scripts 8. Examples
---
Overview
The Problem with CVSS Alone
CVSS Base Score limitations:
- Treats all Critical CVEs equally (not all are exploited)
- Static score, doesn't reflect threat landscape
- No consideration of actual risk to your environment
- Results in "patch everything" approach (unsustainable)
Modern Risk-Based Approach
Combine multiple data sources:
| Metric | Purpose | Source |
|---|---|---|
| CVSS | Vulnerability severity | NVD, vendor advisories |
| EPSS | Exploitation probability | FIRST.org API |
| KEV | Actively exploited | CISA KEV Catalog |
| Asset Criticality | Business impact | Internal CMDB |
| Exposure | Attack surface | Network topology |
---
CVSS Scoring
CVSS v3.1 Overview
Base Score Components:
Attack Vector (AV):
- Network (N): Remotely exploitable
- Adjacent (A): Local network required
- Local (L): Local access required
- Physical (P): Physical access required
Attack Complexity (AC):
- Low (L): No special conditions
- High (H): Specific conditions required
Privileges Required (PR):
- None (N): Unauthenticated
- Low (L): Basic user privileges
- High (H): Administrator privileges
User Interaction (UI):
- None (N): No interaction needed
- Required (R): User action required
Impact (CIA):
- High (H): Total loss
- Low (L): Limited loss
- None (N): No impact
Severity Ranges
| Range | Severity | Typical Response |
|---|---|---|
| 9.0 - 10.0 | Critical | Immediate action |
| 7.0 - 8.9 | High | Urgent, prioritize |
| 4.0 - 6.9 | Medium | Normal planning |
| 0.1 - 3.9 | Low | Backlog |
| 0.0 | None | Informational |
Fetching CVSS Scores
# From NVD API
curl "https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2021-44228" | \
jq '.vulnerabilities[0].cve.metrics.cvssMetricV31[0].cvssData.baseScore'
# From Trivy scan
trivy image myapp:latest --format json | \
jq '.Results[].Vulnerabilities[] | {cve: .VulnerabilityID, cvss: .CVSS.nvd.V3Score}'---
EPSS Integration
What is EPSS?
Exploit Prediction Scoring System (EPSS) predicts the probability a vulnerability will be exploited within 30 days.
Based On:
- Exploit availability (PoC, Metasploit modules)
- Public discussions and mentions
- Vulnerability characteristics
- Historical exploitation patterns
EPSS Score Interpretation
| Score Range | Probability | Action |
|---|---|---|
| 0.9 - 1.0 | Very High (>90%) | Immediate priority |
| 0.5 - 0.9 | High (50-90%) | High priority |
| 0.1 - 0.5 | Moderate (10-50%) | Monitor closely |
| 0.0 - 0.1 | Low (<10%) | Normal priority |
Using EPSS API
Fetch EPSS Score:
# Single CVE
curl "https://api.first.org/data/v1/epss?cve=CVE-2021-44228" | jq '.'
# Response:
{
"status": "OK",
"data": [{
"cve": "CVE-2021-44228",
"epss": "0.97505",
"percentile": "0.99999"
}]
}
# Multiple CVEs
curl "https://api.first.org/data/v1/epss?cve=CVE-2021-44228,CVE-2023-12345"
# All CVEs (large download)
curl "https://api.first.org/data/v1/epss" --output epss-all.csvPython Integration:
import requests
def get_epss_score(cve_id):
"""Fetch EPSS score for CVE"""
url = f"https://api.first.org/data/v1/epss?cve={cve_id}"
response = requests.get(url)
data = response.json()
if data['status'] == 'OK' and data['data']:
return float(data['data'][0]['epss'])
return 0.0
# Example
score = get_epss_score("CVE-2021-44228")
print(f"EPSS Score: {score:.4f} ({score*100:.2f}%)")
# Output: EPSS Score: 0.9751 (97.51%)---
KEV Catalog
CISA Known Exploited Vulnerabilities
CVEs actively exploited in the wild. If in KEV → Immediate action required.
KEV Catalog: https://www.cisa.gov/known-exploited-vulnerabilities-catalog
Using KEV Catalog
Download KEV Data:
# Download JSON
curl -o kev.json https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
# Query specific CVE
jq '.vulnerabilities[] | select(.cveID == "CVE-2021-44228")' kev.jsonCheck if CVE is in KEV:
import requests
def is_in_kev(cve_id):
"""Check if CVE is in CISA KEV catalog"""
url = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
response = requests.get(url)
kev_data = response.json()
for vuln in kev_data['vulnerabilities']:
if vuln['cveID'] == cve_id:
return True, vuln.get('dateAdded'), vuln.get('dueDate')
return False, None, None
# Example
in_kev, added, due = is_in_kev("CVE-2021-44228")
if in_kev:
print(f"P0 CRITICAL: In KEV catalog (added {added}, due {due})")KEV Response Fields:
{
"cveID": "CVE-2021-44228",
"vendorProject": "Apache",
"product": "Log4j",
"vulnerabilityName": "Log4j2 Remote Code Execution",
"dateAdded": "2021-12-10",
"shortDescription": "Apache Log4j2 ...",
"requiredAction": "Apply updates per vendor instructions.",
"dueDate": "2021-12-24"
}---
Risk-Based Formula
Weighted Priority Score
Priority Score = (CVSS × 0.3) + (EPSS × 100 × 0.3) + (KEV × 50) + (Asset × 0.2) + (Exposure × 0.2)
Where:
- CVSS: Base score (0-10)
- EPSS: Probability (0-1), scaled to 0-100
- KEV: 1 if in KEV catalog, 0 otherwise (multiplied by 50)
- Asset: 1.0 (Critical), 0.7 (High), 0.4 (Medium), 0.1 (Low)
- Exposure: 1.0 (Internet-facing), 0.5 (Internal), 0.1 (Isolated)
Maximum Score: 10×0.3 + 100×0.3 + 50 + 1×0.2 + 1×0.2 = 83.4Asset Criticality Matrix
| Asset Type | Criticality | Weight | Examples |
|---|---|---|---|
| Critical | 1.0 | Highest | Payment systems, PII databases, authentication |
| High | 0.7 | High | User-facing apps, APIs, internal services |
| Medium | 0.4 | Medium | Admin tools, internal dashboards |
| Low | 0.1 | Low | Dev/test environments, demos |
Exposure Matrix
| Exposure Type | Weight | Examples |
|---|---|---|
| Internet-facing | 1.0 | Public websites, APIs, CDNs |
| Internal | 0.5 | Corporate network, VPN-only |
| Isolated | 0.1 | Air-gapped, development environments |
---
SLA Tiers
Priority Definitions
| Priority | Criteria | SLA | Action |
|---|---|---|---|
| P0 - Critical | KEV + Internet-facing + Critical asset | 24 hours | Emergency patch, all hands |
| P1 - High | CVSS ≥ 9.0 OR (CVSS ≥ 7.0 AND EPSS ≥ 0.1) | 7 days | Prioritize in current sprint |
| P2 - Medium | CVSS 7.0-8.9 OR EPSS ≥ 0.05 | 30 days | Plan in next sprint |
| P3 - Low | CVSS 4.0-6.9, EPSS < 0.05 | 90 days | Backlog, maintenance window |
| P4 - Info | CVSS < 4.0 | No SLA | Track, address opportunistically |
SLA Escalation
Day 1-7: Normal remediation
Day 8-14: Manager notification
Day 15-21: Director escalation
Day 22+: Executive escalation, risk acceptance required---
Automation Scripts
Python Priority Calculator
import requests
class VulnerabilityPrioritizer:
def __init__(self):
self.kev_cache = None
def get_epss_score(self, cve_id):
"""Fetch EPSS score from API"""
url = f"https://api.first.org/data/v1/epss?cve={cve_id}"
response = requests.get(url)
data = response.json()
if data['status'] == 'OK' and data['data']:
return float(data['data'][0]['epss'])
return 0.0
def is_in_kev(self, cve_id):
"""Check KEV catalog"""
if not self.kev_cache:
url = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
response = requests.get(url)
self.kev_cache = response.json()
for vuln in self.kev_cache['vulnerabilities']:
if vuln['cveID'] == cve_id:
return True
return False
def calculate_priority(self, cve_id, cvss_score, asset_criticality, exposure):
"""
Calculate priority score
Args:
cve_id: CVE identifier
cvss_score: CVSS base score (0-10)
asset_criticality: 1.0 (Critical), 0.7 (High), 0.4 (Medium), 0.1 (Low)
exposure: 1.0 (Internet), 0.5 (Internal), 0.1 (Isolated)
"""
epss_score = self.get_epss_score(cve_id)
kev_status = 1 if self.is_in_kev(cve_id) else 0
priority_score = (
(cvss_score * 0.3) +
(epss_score * 100 * 0.3) +
(kev_status * 50) +
(asset_criticality * 0.2) +
(exposure * 0.2)
)
# Determine priority tier
if kev_status and exposure >= 0.5 and asset_criticality >= 0.7:
tier = "P0"
sla_hours = 24
elif cvss_score >= 9.0 or (cvss_score >= 7.0 and epss_score >= 0.1):
tier = "P1"
sla_hours = 7 * 24
elif cvss_score >= 7.0 or epss_score >= 0.05:
tier = "P2"
sla_hours = 30 * 24
elif cvss_score >= 4.0:
tier = "P3"
sla_hours = 90 * 24
else:
tier = "P4"
sla_hours = None
return {
'cve_id': cve_id,
'cvss': cvss_score,
'epss': epss_score,
'kev': kev_status,
'asset': asset_criticality,
'exposure': exposure,
'priority_score': round(priority_score, 2),
'tier': tier,
'sla_hours': sla_hours
}
# Usage
prioritizer = VulnerabilityPrioritizer()
result = prioritizer.calculate_priority(
cve_id="CVE-2021-44228",
cvss_score=10.0,
asset_criticality=1.0, # Critical
exposure=1.0 # Internet-facing
)
print(f"{result['tier']}: {result['cve_id']}")
print(f" Priority Score: {result['priority_score']}")
print(f" SLA: {result['sla_hours']} hours")
print(f" CVSS: {result['cvss']}, EPSS: {result['epss']:.4f}, KEV: {bool(result['kev'])}")---
Examples
Example 1: Log4Shell (Critical)
CVE: CVE-2021-44228 (Log4Shell)
CVSS: 10.0
EPSS: 0.97505 (97.5%)
KEV: Yes (CISA catalog)
Asset: Critical (payment API)
Exposure: Internet-facing
Calculation:
Priority Score = (10 × 0.3) + (97.5 × 0.3) + (1 × 50) + (1 × 0.2) + (1 × 0.2)
= 3 + 29.25 + 50 + 0.2 + 0.2
= 82.65
Result: P0 - Critical
SLA: 24 hours
Action: Emergency patch immediately, all hands on deck---
Example 2: High CVSS, Low EPSS
CVE: CVE-2023-99999 (Hypothetical)
CVSS: 9.0 (High)
EPSS: 0.001 (0.1%)
KEV: No
Asset: Medium (internal dashboard)
Exposure: Internal only
Calculation:
Priority Score = (9 × 0.3) + (0.1 × 0.3) + (0 × 50) + (0.4 × 0.2) + (0.5 × 0.2)
= 2.7 + 0.03 + 0 + 0.08 + 0.1
= 2.91
Result: P3 - Low
SLA: 90 days
Action: Backlog, address in maintenance window
Reasoning: High CVSS but very low exploitation probability, internal only, medium asset---
Example 3: Medium CVSS, High EPSS
CVE: CVE-2023-88888 (Hypothetical)
CVSS: 7.5 (High)
EPSS: 0.85 (85%)
KEV: No
Asset: High (user-facing API)
Exposure: Internet-facing
Calculation:
Priority Score = (7.5 × 0.3) + (85 × 0.3) + (0 × 50) + (0.7 × 0.2) + (1 × 0.2)
= 2.25 + 25.5 + 0 + 0.14 + 0.2
= 28.09
Result: P1 - High
SLA: 7 days
Action: Prioritize in current sprint
Reasoning: High exploitation probability + Internet-facing = high risk---
Summary
Quick Reference:
P0: KEV + Internet + Critical asset → 24 hours
P1: CVSS ≥ 9.0 OR (CVSS ≥ 7.0 AND EPSS ≥ 0.1) → 7 days
P2: CVSS 7.0-8.9 OR EPSS ≥ 0.05 → 30 days
P3: CVSS 4.0-6.9, EPSS < 0.05 → 90 days
P4: CVSS < 4.0 → No SLAData Sources:
CVSS: NVD API, Trivy scans, vendor advisories
EPSS: https://api.first.org/data/v1/epss
KEV: https://www.cisa.gov/known-exploited-vulnerabilities-catalogAutomation: See examples/prioritization/ for complete scripts.
Vulnerability Remediation Workflows
Patterns for tracking, managing, and remediating vulnerabilities with SLAs and automation.
Table of Contents
1. SLA Tracking 2. False Positive Management 3. Automated Remediation 4. Metrics and Reporting
---
SLA Tracking
Priority-Based SLAs
| Priority | SLA | Escalation Path |
|---|---|---|
| P0 - Critical | 24 hours | Security team → CISO → CEO |
| P1 - High | 7 days | Team lead → Engineering manager → Director |
| P2 - Medium | 30 days | Sprint planning → Normal backlog |
| P3 - Low | 90 days | Maintenance windows → Technical debt |
| P4 - Info | No SLA | Opportunistic fixes |
Automated SLA Tracker
#!/bin/bash
# scripts/sla-tracker.sh
# Track vulnerability remediation SLAs
SCAN_RESULTS="scan-results.json"
KEV_CATALOG="kev.json"
OUTPUT="sla-report.json"
# Download KEV catalog
curl -s -o "$KEV_CATALOG" https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
# Parse vulnerabilities and calculate SLA
jq --slurpfile kev "$KEV_CATALOG" '
.Results[].Vulnerabilities[] |
{
cve: .VulnerabilityID,
severity: .Severity,
found_date: (.PublishedDate | fromdateiso8601),
cvss: .CVSS.nvd.V3Score,
package: .PkgName,
fixed_version: .FixedVersion,
in_kev: ([($kev[0].vulnerabilities[] | select(.cveID == .VulnerabilityID))] | length > 0)
} |
# Calculate priority and SLA
if .in_kev then
.priority = "P0" | .sla_hours = 24
elif .cvss >= 9.0 then
.priority = "P1" | .sla_hours = 168
elif .cvss >= 7.0 then
.priority = "P2" | .sla_hours = 720
elif .cvss >= 4.0 then
.priority = "P3" | .sla_hours = 2160
else
.priority = "P4" | .sla_hours = null
end |
# Calculate deadline
if .sla_hours then
.deadline = (.found_date + .sla_hours * 3600) | .overdue = (now > .deadline)
else
.deadline = null | .overdue = false
end
' "$SCAN_RESULTS" > "$OUTPUT"
# Generate summary
echo "SLA Summary:"
jq -r '
group_by(.priority) |
map({
priority: .[0].priority,
count: length,
overdue: ([.[] | select(.overdue == true)] | length)
}) |
.[]
| "\(.priority): \(.count) total, \(.overdue) overdue"
' "$OUTPUT"False Positive Management
Suppression File (.trivyignore)
# CVE-2023-12345
# Reason: False positive - vulnerability not applicable to our usage
# Verified by: security-team@example.com
# Date: 2025-12-04
# Review date: 2026-03-04
CVE-2023-12345
# CVE-2023-67890
# Reason: Risk accepted - fix requires major refactor, compensating controls in place
# Approved by: CISO
# Compensating controls: WAF rules, network isolation
# Review date: 2025-06-04
CVE-2023-67890
# Development dependencies (not in production)
CVE-2023-11111 # webpack-dev-server (devDependency only)False Positive Workflow
# .github/workflows/manage-false-positives.yml
name: False Positive Management
on:
issues:
types: [labeled]
jobs:
add-suppression:
if: github.event.label.name == 'false-positive'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extract CVE from issue
id: cve
run: |
CVE=$(echo "${{ github.event.issue.title }}" | grep -oP 'CVE-\d{4}-\d+')
echo "cve=$CVE" >> $GITHUB_OUTPUT
- name: Add to .trivyignore
run: |
echo "" >> .trivyignore
echo "# ${{ steps.cve.outputs.cve }}" >> .trivyignore
echo "# Reason: ${{ github.event.issue.body }}" >> .trivyignore
echo "# Added by: ${{ github.event.issue.user.login }}" >> .trivyignore
echo "# Date: $(date +%Y-%m-%d)" >> .trivyignore
echo "${{ steps.cve.outputs.cve }}" >> .trivyignore
- name: Create PR
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b suppress-${{ steps.cve.outputs.cve }}
git add .trivyignore
git commit -m "chore: Suppress ${{ steps.cve.outputs.cve }} (false positive)"
git push origin suppress-${{ steps.cve.outputs.cve }}
gh pr create --title "Suppress ${{ steps.cve.outputs.cve }}" \
--body "Closes #${{ github.event.issue.number }}" \
--label "security,false-positive"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Automated Remediation
Dependency Update Automation
name: Automated Vulnerability Remediation
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight
workflow_dispatch:
jobs:
scan-and-remediate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan for vulnerabilities
id: scan
run: |
trivy image myapp:latest --format json --output scan.json
FIXABLE=$(jq '[.Results[].Vulnerabilities[] | select(.Severity == "HIGH" or .Severity == "CRITICAL") | select(.FixedVersion != "")] | length' scan.json)
echo "fixable_count=$FIXABLE" >> $GITHUB_OUTPUT
- name: Update dependencies (Node.js)
if: steps.scan.outputs.fixable_count > 0
run: |
npm update
npm audit fix
- name: Update dependencies (Python)
if: steps.scan.outputs.fixable_count > 0
run: |
pip install pip-audit
pip-audit --fix
- name: Rebuild and re-scan
if: steps.scan.outputs.fixable_count > 0
run: |
docker build -t myapp:remediation .
trivy image myapp:remediation --format json --output scan-after.json
- name: Create remediation PR
if: steps.scan.outputs.fixable_count > 0
run: |
git config user.name "security-bot"
git config user.email "security-bot@example.com"
git checkout -b security/auto-remediation-$(date +%Y%m%d)
git add package*.json requirements.txt
git commit -m "security: Fix ${{ steps.scan.outputs.fixable_count }} vulnerabilities"
git push origin security/auto-remediation-$(date +%Y%m%d)
gh pr create \
--title "Security: Automated vulnerability remediation" \
--body "Fixes ${{ steps.scan.outputs.fixable_count }} fixable vulnerabilities" \
--label "security,automated"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Metrics and Reporting
Vulnerability Dashboard Metrics
#!/usr/bin/env python3
# scripts/vulnerability-metrics.py
import json
import sys
from datetime import datetime
def analyze_scan_results(scan_file):
with open(scan_file) as f:
scan_data = json.load(f)
metrics = {
'total_vulnerabilities': 0,
'by_severity': {'CRITICAL': 0, 'HIGH': 0, 'MEDIUM': 0, 'LOW': 0},
'fixable': 0,
'unfixable': 0,
'by_priority': {'P0': 0, 'P1': 0, 'P2': 0, 'P3': 0, 'P4': 0},
'scan_date': datetime.now().isoformat()
}
for result in scan_data.get('Results', []):
for vuln in result.get('Vulnerabilities', []):
metrics['total_vulnerabilities'] += 1
severity = vuln.get('Severity', 'UNKNOWN')
metrics['by_severity'][severity] = metrics['by_severity'].get(severity, 0) + 1
if vuln.get('FixedVersion'):
metrics['fixable'] += 1
else:
metrics['unfixable'] += 1
return metrics
if __name__ == '__main__':
metrics = analyze_scan_results(sys.argv[1] if len(sys.argv) > 1 else 'scan.json')
print(json.dumps(metrics, indent=2))Executive Report Generation
#!/bin/bash
# scripts/executive-report.sh
SCAN_FILE="scan.json"
REPORT_FILE="security-report.md"
cat > "$REPORT_FILE" << EOF
# Security Vulnerability Report
**Date:** $(date +%Y-%m-%d)
**Scan Date:** $(jq -r '.CreatedAt' "$SCAN_FILE")
## Executive Summary
$(jq -r '
[.Results[].Vulnerabilities[]] |
{
total: length,
critical: ([.[] | select(.Severity == "CRITICAL")] | length),
high: ([.[] | select(.Severity == "HIGH")] | length),
medium: ([.[] | select(.Severity == "MEDIUM")] | length),
low: ([.[] | select(.Severity == "LOW")] | length)
} |
"- **Total Vulnerabilities:** \(.total)
- **Critical:** \(.critical)
- **High:** \(.high)
- **Medium:** \(.medium)
- **Low:** \(.low)"
' "$SCAN_FILE")
## Priority Breakdown
$(jq -r '
[.Results[].Vulnerabilities[]] |
group_by(
if .CVSS.nvd.V3Score >= 9.0 then "P1"
elif .CVSS.nvd.V3Score >= 7.0 then "P2"
elif .CVSS.nvd.V3Score >= 4.0 then "P3"
else "P4"
end
) |
map({priority: .[0], count: length}) |
.[] |
"- **\(.priority):** \(.count) vulnerabilities"
' "$SCAN_FILE")
## Action Items
### Immediate (P0/P1)
$(jq -r '
[.Results[].Vulnerabilities[] | select(.CVSS.nvd.V3Score >= 9.0)] |
.[] |
"- [ ] Fix \(.VulnerabilityID) in \(.PkgName) (CVSS: \(.CVSS.nvd.V3Score))"
' "$SCAN_FILE" | head -10)
### Upcoming Sprint (P2)
$(jq -r '
[.Results[].Vulnerabilities[] | select(.CVSS.nvd.V3Score >= 7.0 and .CVSS.nvd.V3Score < 9.0)] |
length |
"- \(.) vulnerabilities to address in upcoming sprint"
' "$SCAN_FILE")
---
*Generated automatically by security scanning pipeline*
EOF
echo "Report generated: $REPORT_FILE"See complete scripts in scripts/ directory.
SBOM Generation and Management Guide
Complete patterns for Software Bill of Materials (SBOM) generation, storage, consumption, and compliance.
Table of Contents
1. SBOM Fundamentals 2. CycloneDX vs SPDX 3. Generation Tools 4. Generation Patterns 5. SBOM Storage and Distribution 6. SBOM Consumption 7. Compliance Requirements 8. Best Practices
---
SBOM Fundamentals
What is an SBOM?
A Software Bill of Materials (SBOM) is a formal, structured list of components, libraries, and modules required to build software. Think of it as an "ingredients label" for software.
Core Elements (NTIA Minimum):
- Supplier name
- Component name
- Version of component
- Unique identifier (PURL, CPE)
- Dependency relationships
- Author of SBOM data
- Timestamp
Why SBOMs Matter
Regulatory Drivers:
- Executive Order 14028 (US Federal software procurement)
- NTIA minimum elements for SBOM
- Industry standards (OWASP, Linux Foundation)
- Customer security requirements
Security Benefits:
- Vulnerability tracking: Know what's affected by new CVEs
- Supply chain transparency: Understand dependency risks
- Incident response: Quickly identify affected systems
- License compliance: Track open-source licenses
Operational Benefits:
- Faster vulnerability remediation
- Automated security scanning
- Compliance evidence generation
- Supply chain risk management
---
CycloneDX vs SPDX
Format Comparison
| Aspect | CycloneDX | SPDX |
|---|---|---|
| Focus | Security, vulnerability management | License compliance, legal |
| Maintainer | OWASP | Linux Foundation |
| Status | OWASP standard | ISO/IEC 5962:2021 |
| Primary Use | DevSecOps, security pipelines | Legal teams, compliance |
| Formats | JSON, XML, Protocol Buffers | JSON, XML, YAML, RDF, Tag/Value |
| Vulnerability Data | Native support (BOM-link, VEX) | External enrichment needed |
| License Info | Basic license identifiers | Comprehensive legal metadata |
| Tool Support | Trivy, Syft, cdxgen, Grype | Tern, Microsoft SBOM Tool, Syft |
| Speed | Fast (JSON optimized) | Slower (more comprehensive) |
| Size | Smaller files | Larger files (more metadata) |
When to Use CycloneDX
Best For:
- DevSecOps and security-focused teams
- Vulnerability tracking and management
- Fast CI/CD pipelines (smaller files)
- Automated security scanning
- Container security workflows
- Agile development environments
Example Use Cases:
✓ Container image SBOM for vulnerability scanning
✓ CI/CD pipeline artifact generation
✓ Automated dependency tracking
✓ Security dashboard integration
✓ Rapid SBOM generation (< 1 minute)When to Use SPDX
Best For:
- Legal and compliance teams
- License compliance audits
- Government/defense contracts (ISO standard)
- Comprehensive legal metadata required
- Open-source license tracking
- Long-term archival
Example Use Cases:
✓ License compliance audits
✓ Federal contract requirements
✓ Open-source license tracking
✓ Legal risk assessment
✓ Export compliance documentationRecommendation by Scenario
Scenario 1: DevSecOps Team
Format: CycloneDX JSON
Reason: Security-focused, fast, native vulnerability references
Tools: Trivy, Syft, cdxgenScenario 2: Legal/Compliance Team
Format: SPDX 2.3
Reason: Comprehensive license data, ISO standard
Tools: Microsoft SBOM Tool, SyftScenario 3: Dual Requirements
Format: Both (generate both formats)
Reason: Security AND legal needs
Tools: Syft (supports both), or Trivy + Microsoft SBOM ToolScenario 4: Federal/Government
Format: SPDX (preferred) or CycloneDX (accepted)
Reason: ISO standard alignment, government preference
Tools: Microsoft SBOM Tool, Tern---
Generation Tools
Multi-Format Tools
Trivy (Recommended - All-in-One)
Supports: CycloneDX, SPDX Best For: Container images, fast CI/CD, comprehensive coverage
# CycloneDX JSON (recommended for security)
trivy image --format cyclonedx --output sbom.json alpine:latest
# SPDX JSON (for compliance)
trivy image --format spdx-json --output sbom-spdx.json alpine:latest
# Scan filesystem
trivy fs --format cyclonedx --output sbom.json .
# Include vulnerabilities in SBOM
trivy image --format cyclonedx --scanners vuln --output sbom-with-vulns.json alpine:latestStrengths:
- All-in-one: Scan + SBOM generation
- Fast: Optimized for CI/CD
- Multiple targets: Containers, filesystems, Git repos
- Vulnerability integration: Include vulnerabilities in SBOM
---
Syft (Accuracy-Focused)
Supports: CycloneDX, SPDX, Syft JSON Best For: High accuracy, SBOM-first workflows, Anchore ecosystem
# CycloneDX
syft alpine:latest -o cyclonedx-json=sbom.json
# SPDX
syft alpine:latest -o spdx-json=sbom-spdx.json
# Multiple formats at once
syft alpine:latest -o cyclonedx-json=sbom-cdx.json -o spdx-json=sbom-spdx.json
# Scan directory
syft dir:. -o cyclonedx-json=sbom.json
# Scan with cataloging options
syft packages alpine:latest --scope all-layers -o cyclonedx-jsonStrengths:
- High accuracy: Precise package detection
- Multi-format: CycloneDX, SPDX, custom JSON
- Flexible: Many output formats and options
- Anchore integration: Works with Grype for scanning
---
cdxgen (Multi-Language)
Supports: CycloneDX only Best For: Multi-language projects, Node.js/Python/Java focus
# Install
npm install -g @cyclonedx/cdxgen
# Generate SBOM
cdxgen -o sbom.json .
# Specific project type
cdxgen -t node -o sbom.json .
# With evidence (build info)
cdxgen --evidence -o sbom.json .
# Include dev dependencies
cdxgen --include-dev -o sbom.json .Strengths:
- 20+ languages: Node.js, Python, Java, Go, PHP, .NET, etc.
- OWASP official: Maintained by CycloneDX team
- Evidence tracking: Build and deployment metadata
- Container support: Docker and OCI images
---
Language-Specific Tools
Node.js
# CycloneDX BOM
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
# SPDX
npm install -g spdx-sbom-generator
spdx-sbom-generator -p .Python
# CycloneDX
pip install cyclonedx-bom
cyclonedx-py -o sbom.json
# Syft (recommended)
syft dir:. -o cyclonedx-json=sbom.jsonJava (Maven)
<!-- pom.xml -->
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<version>2.7.9</version>
<executions>
<execution>
<goals>
<goal>makeAggregateBom</goal>
</goals>
</execution>
</executions>
</plugin># Generate SBOM
mvn cyclonedx:makeAggregateBomGo
# Syft
syft packages dir:. -o cyclonedx-json=sbom.json
# cdxgen
cdxgen -t go -o sbom.json .Rust
# cargo-sbom
cargo install cargo-sbom
cargo sbom --output-format cyclonedx_json_1_4 > sbom.json---
Generation Patterns
Pattern 1: CI/CD Automatic Generation
GitHub Actions:
name: Generate SBOM
on:
push:
branches: [main]
release:
types: [published]
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Generate CycloneDX SBOM (security)
run: |
trivy image --format cyclonedx \
--output sbom-cyclonedx.json \
myapp:${{ github.sha }}
- name: Generate SPDX SBOM (compliance)
run: |
syft myapp:${{ github.sha }} \
-o spdx-json=sbom-spdx.json
- name: Upload SBOM artifacts
uses: actions/upload-artifact@v3
with:
name: sboms
path: |
sbom-cyclonedx.json
sbom-spdx.json
retention-days: 90
- name: Attach SBOM to release (if release event)
if: github.event_name == 'release'
run: |
gh release upload ${{ github.event.release.tag_name }} \
sbom-cyclonedx.json sbom-spdx.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}GitLab CI:
sbom-generation:
stage: build
image: aquasec/trivy:latest
script:
- trivy image --format cyclonedx --output sbom.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
artifacts:
paths:
- sbom.json
reports:
cyclonedx: sbom.json
expire_in: 90 days---
Pattern 2: Multi-Stage Docker Build with SBOM
Dockerfile:
# Stage 1: Build application
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
# Generate SBOM during build
RUN npx @cyclonedx/cyclonedx-npm \
--output-file /tmp/sbom.json \
--output-format JSON \
--short-PURLs
# Stage 2: Runtime
FROM node:20-alpine AS runtime
WORKDIR /app
# Copy application
COPY --from=builder /app/node_modules ./node_modules
COPY . .
# Include SBOM in image
COPY --from=builder /tmp/sbom.json /app/sbom.json
# Label with SBOM location
LABEL org.opencontainers.image.sbom=/app/sbom.json
CMD ["node", "index.js"]---
Pattern 3: SBOM as OCI Artifact
Store SBOM alongside container image in registry:
# Install ORAS (OCI Registry As Storage)
brew install oras
# Generate SBOM
trivy image --format cyclonedx --output sbom.json myapp:latest
# Attach SBOM to image as OCI artifact
oras attach myregistry.io/myapp:latest \
--artifact-type application/vnd.cyclonedx+json \
sbom.json:application/vnd.cyclonedx+json
# Discover attached artifacts
oras discover myregistry.io/myapp:latest
# Pull SBOM
oras pull myregistry.io/myapp:latest --artifact-type application/vnd.cyclonedx+json---
Pattern 4: SBOM Enrichment with Vulnerabilities
Include vulnerability data in SBOM:
# Generate SBOM with vulnerabilities (CycloneDX VEX)
trivy image --format cyclonedx \
--scanners vuln \
--output sbom-with-vulns.json \
myapp:latest
# Separate SBOM and vulnerability report
trivy image --format cyclonedx --output sbom.json myapp:latest
trivy image --format json --output vulns.json myapp:latest---
SBOM Storage and Distribution
Storage Options
1. Version Control (Git)
Best For: Tracking SBOM changes over time
# Store in repository
git add sbom.json
git commit -m "chore: Update SBOM for v1.2.0"
git tag v1.2.0
git push --tagsStructure:
project/
├── sbom/
│ ├── sbom-v1.0.0.json
│ ├── sbom-v1.1.0.json
│ └── sbom-latest.json
└── src/---
2. Artifact Repository
Best For: Enterprise artifact management
Artifactory:
# Upload to Artifactory
curl -H "X-JFrog-Art-Api:$API_KEY" \
-T sbom.json \
"https://artifactory.example.com/artifactory/sbom-repo/myapp/1.0.0/sbom.json"Nexus:
# Upload to Nexus
curl -u admin:admin123 \
--upload-file sbom.json \
"https://nexus.example.com/repository/sbom-repo/myapp/1.0.0/sbom.json"---
3. SBOM Management Platforms
Dependency Track:
# Upload SBOM
curl -X PUT \
"https://dtrack.example.com/api/v1/bom" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d @sbom.jsonKubernetes ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-sbom
namespace: production
data:
sbom.json: |
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
...
}---
4. Cloud Storage
AWS S3:
# Upload to S3
aws s3 cp sbom.json s3://company-sboms/myapp/v1.0.0/sbom.json
# Make public (if needed for customers)
aws s3api put-object-acl \
--bucket company-sboms \
--key myapp/v1.0.0/sbom.json \
--acl public-readGoogle Cloud Storage:
# Upload to GCS
gsutil cp sbom.json gs://company-sboms/myapp/v1.0.0/sbom.json---
Distribution Methods
1. Include in Software Package
# Include SBOM in release tarball
tar -czf myapp-v1.0.0.tar.gz myapp/ sbom.json
# Include in installer
dpkg-deb --build --root-owner-group myapp_1.0.0_amd64
# (SBOM in /usr/share/doc/myapp/sbom.json)---
2. Publish with Release
GitHub Releases:
# Create release with SBOM
gh release create v1.0.0 \
--title "Release v1.0.0" \
--notes "See CHANGELOG.md" \
myapp-v1.0.0.tar.gz \
sbom-cyclonedx.json \
sbom-spdx.json---
3. API Endpoint
Serve SBOM via API for automated consumption:
// Express.js example
app.get('/sbom/:version', (req, res) => {
const sbomPath = `./sboms/sbom-${req.params.version}.json`;
res.sendFile(sbomPath);
});
// https://api.example.com/sbom/1.0.0---
SBOM Consumption
Scanning SBOM Instead of Image
Faster CI/CD: Scan SBOM (seconds) vs. re-scan image (minutes)
# Generate SBOM once (build stage)
trivy image --format cyclonedx --output sbom.json myapp:latest
# Scan SBOM in subsequent stages (fast)
trivy sbom sbom.json --severity HIGH,CRITICAL
# With Grype
grype sbom:sbom.json --fail-on highGitHub Actions Pattern:
build:
runs-on: ubuntu-latest
steps:
- run: docker build -t myapp:${{ github.sha }} .
- run: trivy image --format cyclonedx --output sbom.json myapp:${{ github.sha }}
- uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom.json
scan-pr:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/download-artifact@v3
with:
name: sbom
- run: trivy sbom sbom.json --severity HIGH,CRITICAL --exit-code 1---
SBOM Analysis and Queries
Query SBOM with jq:
# List all components
jq '.components[] | {name: .name, version: .version}' sbom.json
# Find specific dependency
jq '.components[] | select(.name == "lodash")' sbom.json
# Count dependencies
jq '.components | length' sbom.json
# List licenses
jq '.components[].licenses[].license.id' sbom.json | sort -u
# Find vulnerabilities (if included)
jq '.vulnerabilities[] | {id: .id, severity: .ratings[0].severity}' sbom.jsonPython Script Example:
import json
with open('sbom.json') as f:
sbom = json.load(f)
# Extract components
components = sbom.get('components', [])
# Group by license
licenses = {}
for comp in components:
for lic in comp.get('licenses', []):
license_id = lic['license']['id']
licenses.setdefault(license_id, []).append(comp['name'])
# Report
for license, packages in licenses.items():
print(f"{license}: {len(packages)} packages")---
SBOM Diffing
Compare SBOMs between versions:
# Generate SBOMs for two versions
trivy image --format cyclonedx --output sbom-old.json myapp:v1.0.0
trivy image --format cyclonedx --output sbom-new.json myapp:v1.1.0
# Extract component lists
jq -r '.components[] | "\(.name)@\(.version)"' sbom-old.json | sort > old-deps.txt
jq -r '.components[] | "\(.name)@\(.version)"' sbom-new.json | sort > new-deps.txt
# Show differences
diff old-deps.txt new-deps.txt
# Added dependencies
comm -13 old-deps.txt new-deps.txt
# Removed dependencies
comm -23 old-deps.txt new-deps.txt---
Compliance Requirements
NTIA Minimum Elements
Required fields for SBOM compliance:
1. Supplier Name: Who provides the component 2. Component Name: Name of the software component 3. Version: Version identifier 4. Unique Identifier: PURL, CPE, or similar 5. Dependency Relationships: Component graph 6. Author of SBOM: Who created the SBOM 7. Timestamp: When SBOM was created
Validation:
# Verify NTIA compliance
# Check for required fields in CycloneDX
jq '.metadata.timestamp, .metadata.authors, .components[0] | {name, version, purl}' sbom.json---
Executive Order 14028 (Federal)
Requirements:
- SBOM for all software sold to federal government
- Machine-readable format (CycloneDX or SPDX)
- NTIA minimum elements included
- Regular updates with vulnerability data
Recommended Format: SPDX (ISO standard preferred by government)
---
Industry Standards
OWASP CycloneDX:
- Current version: 1.5
- Security-focused SBOM standard
- Used by: Trivy, Syft, cdxgen, Dependency Track
SPDX:
- Current version: 2.3
- ISO/IEC 5962:2021
- Used by: Linux Foundation, Microsoft SBOM Tool
---
Best Practices
1. Generate SBOM on Every Build
# Always generate SBOM
- name: Generate SBOM
run: trivy image --format cyclonedx --output sbom.json $IMAGE
if: always() # Generate even if tests fail2. Store with Artifacts
# Upload SBOM with build artifacts
- uses: actions/upload-artifact@v3
with:
name: release-artifacts
path: |
myapp-binary
sbom.json
sbom-spdx.json3. Version SBOMs
# Include version in filename
trivy image --format cyclonedx --output sbom-v${VERSION}.json myapp:${VERSION}
# Tag SBOMs in Git
git tag sbom-v1.0.04. Provide Multiple Formats
# Generate both CycloneDX (security) and SPDX (compliance)
trivy image --format cyclonedx --output sbom-cyclonedx.json myapp:latest
trivy image --format spdx-json --output sbom-spdx.json myapp:latest5. Automate Distribution
# Automatically attach to releases
- name: Upload to release
if: github.event_name == 'release'
run: gh release upload ${{ github.ref_name }} sbom*.json6. Sign SBOMs
# Sign with cosign
cosign sign-blob --key cosign.key sbom.json > sbom.json.sig
# Verify
cosign verify-blob --key cosign.pub --signature sbom.json.sig sbom.json7. Include in Documentation
# README.md
## Security
Software Bill of Materials (SBOM) available:
- CycloneDX: [sbom-cyclonedx.json](./sbom-cyclonedx.json)
- SPDX: [sbom-spdx.json](./sbom-spdx.json)
Generated with: Trivy v0.48.0
Last updated: 2025-12-048. Continuous Monitoring
# Upload to Dependency Track for continuous monitoring
curl -X PUT "https://dtrack.example.com/api/v1/bom" \
-H "X-Api-Key: $API_KEY" \
-d @sbom.json
# Get notifications on new vulnerabilities---
Troubleshooting
Common Issues
1. Large SBOM Files
# Problem: SBOM is 50MB+
# Solution: Exclude unnecessary components
# Exclude dev dependencies
trivy image --skip-dirs node_modules --format cyclonedx myapp:latest
# Syft: scope production only
syft myapp:latest --scope squashed -o cyclonedx-json2. Missing Dependencies
# Problem: Not all dependencies detected
# Solution: Use multiple tools and merge
# Generate with multiple tools
trivy image --format cyclonedx --output sbom-trivy.json myapp:latest
syft myapp:latest -o cyclonedx-json=sbom-syft.json
# Manual merge or use cdx-merge tool3. Incompatible Format Versions
# Problem: Tool doesn't support CycloneDX 1.5
# Solution: Specify older version
# Syft with specific version
syft myapp:latest --output cyclonedx-1.4-json=sbom.json---
Summary
Quick Decision Guide:
Security/DevSecOps → CycloneDX + Trivy/Syft
Legal/Compliance → SPDX + Microsoft SBOM Tool / Syft
Both requirements → Generate both formats
Federal/Government → SPDX (ISO standard)
Fast CI/CD → CycloneDX JSONEssential Commands:
# Generate CycloneDX (security)
trivy image --format cyclonedx --output sbom.json myapp:latest
# Generate SPDX (compliance)
syft myapp:latest -o spdx-json=sbom-spdx.json
# Scan SBOM (fast)
trivy sbom sbom.json --severity HIGH,CRITICALVulnerability Scanning Tool Selection Guide
Complete decision frameworks for selecting appropriate security scanning tools across all layers.
Table of Contents
1. Container Scanning Tools 2. SAST Tools 3. DAST Tools 4. SCA Tools 5. Secret Scanning Tools 6. Decision Flowcharts 7. Tool Comparison Matrices
---
Container Scanning Tools
Trivy (Primary Recommendation)
When to Use:
- Default choice for most container scanning needs
- Need comprehensive coverage (OS, languages, secrets, misconfig)
- Fast CI/CD pipelines (< 1 minute scans)
- SBOM generation required
- Kubernetes environments (manifest scanning)
- Teams needing all-in-one solution
Strengths:
- Comprehensive: OS packages, language libraries, secrets, misconfigurations, licenses
- Fast: Parallel scanning, optimized database
- Zero setup: Single binary, works out-of-the-box
- SBOM native: Generate and consume CycloneDX/SPDX
- Multi-target: Container images, filesystems, Git repos, K8s manifests, Terraform
- Active development: 100+ releases/year, responsive community
Limitations:
- Can have false positives (mitigated with .trivyignore)
- Database updates required (automatic but adds startup time)
Installation:
# macOS
brew install trivy
# Linux
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Docker
docker pull aquasec/trivy:latest
# Binary download
wget https://github.com/aquasecurity/trivy/releases/download/v0.48.0/trivy_0.48.0_Linux-64bit.tar.gzExample Usage:
# Basic scan
trivy image alpine:latest
# CI/CD with severity filter
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:latest
# Generate SBOM
trivy image --format cyclonedx --output sbom.json myapp:latest
# Scan filesystem
trivy fs --scanners vuln,secret,misconfig /path/to/project
# Scan Kubernetes
trivy config deployment.yamlBest Practices:
# .trivy.yaml
severity: HIGH,CRITICAL
exit-code: 1
ignore-unfixed: true
vuln-type: os,library
skip-dirs:
- node_modules
- vendor
- test
ignorefile: .trivyignore---
Grype (Alternative - Accuracy Focus)
When to Use:
- False positive sensitivity critical
- SBOM-first workflows (pair with Syft)
- Need second opinion validation
- Anchore ecosystem users
- Accuracy more important than comprehensiveness
Strengths:
- Minimal false positives: Precise package version matching
- Syft integration: Best-in-class SBOM generation
- Lightweight: No database required
- Fast: Comparable to Trivy
- Clear output: Easy to parse and understand
Limitations:
- Requires Syft for SBOM generation (two tools vs. Trivy's one)
- Less comprehensive than Trivy (no misconfig, limited secret scanning)
- Credential disclosure CVE (CVE-2025-65965) - Use v0.104.1+
Installation:
# macOS
brew install grype
# Linux
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# Docker
docker pull anchore/grype:latestExample Usage:
# Basic scan
grype alpine:latest
# With severity threshold
grype alpine:latest --fail-on high
# Scan SBOM
grype sbom:./sbom.json
# Exclude paths
grype dir:. --exclude './node_modules/**'
# Syft + Grype workflow
syft alpine:latest -o json | grype --fail-on criticalBest Practices:
# .grype.yaml
fail-on-severity: high
output: table
exclude:
- CVE-2023-12345 # False positive---
Snyk Container (Commercial)
When to Use:
- Budget for commercial tooling
- Developer productivity priority
- Need automated remediation PRs
- Want unified platform (container + SCA + SAST + IaC)
- Require priority intelligence (exploit maturity)
Strengths:
- Developer-first UX: Clear fix guidance, base image recommendations
- Automated remediation: PRs for dependency updates
- Priority Intelligence: Risk-based prioritization with exploit data
- IDE integration: Real-time feedback in VS Code, IntelliJ
- Comprehensive: Container, code, dependencies, IaC in one platform
Limitations:
- Commercial license required (free tier limited)
- Requires internet connectivity for scanning
- Less customizable than open-source tools
Example Usage:
# Install
npm install -g snyk
# Authenticate
snyk auth
# Scan container
snyk container test alpine:latest
# Monitor in dashboard
snyk container monitor alpine:latest --project-name=myapp
# Automated fixes
snyk container test --file=Dockerfile --exclude-base-image-vulns---
SAST Tools
Semgrep (Primary Recommendation)
When to Use:
- Need fast, customizable static analysis
- Multi-language projects
- Custom security rules required
- CI/CD integration (< 5 minute scans)
- Open-source preference
Strengths:
- Fast: Semantic pattern matching, not full compilation
- Multi-language: 30+ languages supported
- Customizable: Write custom rules in YAML
- Low false positives: Context-aware matching
- CI/CD native: GitHub Actions, GitLab CI, pre-commit hooks
Limitations:
- Requires rule customization for best results
- Less comprehensive than commercial tools (fewer rules out-of-box)
Installation:
# macOS
brew install semgrep
# pip
pip install semgrep
# Docker
docker pull returntocorp/semgrepExample Usage:
# Scan with OWASP ruleset
semgrep --config=p/security-audit .
# Multiple rulesets
semgrep --config=p/owasp-top-ten --config=p/secrets .
# CI/CD mode (fail on findings)
semgrep ci
# Custom rules
semgrep --config=rules/custom-security.yaml .---
Snyk Code (Commercial)
When to Use:
- Developer-first experience priority
- IDE integration required
- Need clear fix guidance
- Want AI-powered analysis
Strengths:
- IDE plugins: Real-time feedback in editor
- Developer guidance: Clear explanations and fix suggestions
- AI-powered: ML-based vulnerability detection
- Fast: Incremental scanning in IDE
---
SonarQube (Enterprise)
When to Use:
- Enterprise code quality + security
- Need centralized dashboard
- Compliance requirements (audit trails)
- Long-term trend analysis
Strengths:
- Comprehensive: Code quality + security
- Quality gates: Enforce thresholds
- Historical analysis: Track technical debt over time
- Enterprise features: LDAP, SSO, audit logs
---
DAST Tools
OWASP ZAP (Primary Recommendation)
When to Use:
- Need open-source full-featured DAST
- Manual + automated testing required
- API security testing
- Budget constraints
Strengths:
- Full-featured: Active scan, passive scan, fuzzing, API testing
- Extensible: Plugins and scripts
- Active community: Well-documented, many resources
- Free: No licensing costs
Installation:
# Docker
docker pull owasp/zap2docker-stable
# macOS
brew install --cask owasp-zap
# Download
# https://www.zaproxy.org/download/Example Usage:
# Baseline scan (non-intrusive)
docker run -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-stable \
zap-baseline.py -t https://example.com -r report.html
# Full scan (intrusive)
docker run -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-stable \
zap-full-scan.py -t https://example.com -r report.html
# API scan
docker run -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-stable \
zap-api-scan.py -t https://api.example.com/openapi.json -f openapi---
StackHawk (Commercial - CI/CD Native)
When to Use:
- Modern CI/CD native DAST required
- Need developer-friendly output
- API-first applications
- Budget for commercial tool
Strengths:
- CI/CD native: Built for automated pipelines
- Developer-focused: Clear, actionable results
- API testing: GraphQL, REST, gRPC support
- Fast: Optimized for CI/CD speed
---
SCA Tools
Dependabot (GitHub Native)
When to Use:
- Using GitHub
- Need automated dependency updates
- Free option required
- Simple dependency scanning sufficient
Strengths:
- Native GitHub integration: Zero setup
- Automated PRs: Dependency updates with changelogs
- Free: Included with GitHub
- Security alerts: Integrated with GitHub Security
Configuration:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10---
Renovate (Advanced Automation)
When to Use:
- Need advanced customization
- Multi-platform (GitHub, GitLab, Bitbucket, Azure DevOps)
- Complex dependency update strategies
- Monorepo support required
Strengths:
- Highly customizable: Granular control over updates
- Multi-platform: Works across Git platforms
- Smart grouping: Group related updates
- Flexible scheduling: Time-based, on-demand, manual approval
Configuration:
{
"extends": ["config:base"],
"schedule": ["after 10pm every weekday"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
]
}---
Snyk Open Source (Commercial)
When to Use:
- Comprehensive commercial solution required
- Need largest vulnerability database
- Want automated fix PRs with testing
- Unified platform preference
Strengths:
- Largest database: Most comprehensive vulnerability data
- Fix PRs: Automated updates with CI/CD integration
- Priority Intelligence: Risk-based prioritization
- Unified platform: SCA + SAST + Container + IaC
---
Secret Scanning Tools
Gitleaks (Primary Recommendation)
When to Use:
- Need fast, open-source secret scanning
- CI/CD integration required
- Custom rule configuration needed
- Pre-commit hooks
Strengths:
- Fast: Optimized scanning engine
- Configurable: Custom rules via TOML
- CI/CD ready: Exit codes, JSON output
- Pre-commit support: Prevent secrets before commit
Installation:
# macOS
brew install gitleaks
# Docker
docker pull zricethezav/gitleaks:latest
# Binary
wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.0/gitleaks_8.18.0_linux_x64.tar.gzExample Usage:
# Scan current directory
gitleaks detect --source . --verbose
# Scan specific commit range
gitleaks detect --log-opts="--since=2023-01-01"
# Pre-commit hook
gitleaks protect --staged --verbose
# CI/CD mode (exit code on findings)
gitleaks detect --source . --exit-code 1Configuration:
# .gitleaks.toml
title = "Gitleaks Config"
[extend]
useDefault = true
[[rules]]
description = "Custom AWS Key"
regex = '''AKIA[0-9A-Z]{16}'''
tags = ["aws", "key"]---
TruffleHog (Entropy Detection)
When to Use:
- Need entropy-based detection
- Want to find secrets without known patterns
- Historical repository scanning
- High-entropy secret detection
Strengths:
- Entropy detection: Find secrets without regex
- Git history: Scan entire repository history
- Verified secrets: Attempts to verify found secrets
- Active scanning: Real-time monitoring
---
GitGuardian (Commercial)
When to Use:
- Need real-time protection
- Want incident response workflow
- Require compliance reporting
- Budget for commercial solution
Strengths:
- Real-time monitoring: Instant alerts on secret commits
- Incident response: Guided remediation workflows
- Compliance: Audit trails and reporting
- Developer education: Training and awareness
---
Decision Flowcharts
Container Scanning Decision Tree
START: Need to scan container image
Q1: Budget and requirements?
├─ Open-source + comprehensive → Q2
├─ Open-source + accuracy focus → Grype + Syft
└─ Commercial + developer UX → Snyk Container
Q2: Open-source comprehensive choice
├─ Need SBOM + scan in one tool? → YES: Trivy
├─ Need minimal false positives? → NO: Grype + Syft
└─ Default choice → TrivySAST Decision Tree
START: Need static code analysis
Q1: Budget?
├─ Open-source → Q2
└─ Commercial → Q3
Q2: Open-source SAST
├─ Need fast + customizable? → Semgrep
├─ Using GitLab? → GitLab SAST
└─ Default → Semgrep
Q3: Commercial SAST
├─ Developer UX priority? → Snyk Code
├─ Enterprise quality gates? → SonarQube
└─ Default → Snyk CodeDAST Decision Tree
START: Need dynamic application testing
Q1: Budget?
├─ Open-source → OWASP ZAP
└─ Commercial → Q2
Q2: Commercial DAST needs
├─ CI/CD native required? → StackHawk
├─ Manual + automated? → Burp Suite
└─ Default → StackHawk---
Tool Comparison Matrices
Container Scanning Comparison
| Feature | Trivy | Grype | Snyk Container |
|---|---|---|---|
| License | Apache 2.0 | Apache 2.0 | Commercial |
| Speed | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Accuracy | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| OS Packages | Yes | Yes | Yes |
| Language Packages | Yes | Yes | Yes |
| Secrets | Yes | Limited | Yes |
| Misconfig | Yes | No | Yes |
| SBOM Generation | CycloneDX, SPDX | Via Syft | Yes |
| SBOM Scanning | Yes | Yes | Yes |
| CI/CD Integration | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| False Positives | Moderate | Low | Low |
| Setup Complexity | Low | Low | Low |
| Best For | Most projects | Accuracy focus | Enterprise |
SAST Comparison
| Feature | Semgrep | Snyk Code | SonarQube |
|---|---|---|---|
| License | LGPL 2.1 | Commercial | Commercial |
| Speed | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Languages | 30+ | 10+ | 25+ |
| Custom Rules | Yes (YAML) | No | Limited |
| IDE Integration | Yes | Yes | Yes |
| CI/CD Ready | Yes | Yes | Yes |
| False Positives | Low | Low | Moderate |
| Fix Guidance | Basic | Excellent | Good |
| Best For | Fast + customizable | Developer UX | Enterprise quality |
Secret Scanning Comparison
| Feature | Gitleaks | TruffleHog | GitGuardian |
|---|---|---|---|
| License | MIT | AGPL-3.0 | Commercial |
| Detection | Regex | Regex + Entropy | Regex + ML |
| Speed | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Git History | Yes | Yes | Yes |
| Pre-commit | Yes | Yes | Yes |
| Verification | No | Yes | Yes |
| Real-time | No | No | Yes |
| Incident Response | No | No | Yes |
| Best For | CI/CD | Entropy detection | Enterprise |
---
Recommended Combinations
Startup/Small Team
Container: Trivy (comprehensive, free)
SAST: Semgrep (fast, customizable)
DAST: OWASP ZAP (full-featured, free)
SCA: Dependabot (GitHub native, free)
Secrets: Gitleaks (fast, configurable)
Cost: $0
Setup: Low complexityMid-Size Company
Container: Trivy + Snyk Container (comprehensive + UX)
SAST: Semgrep + Snyk Code (coverage + developer UX)
DAST: StackHawk (CI/CD native)
SCA: Renovate (advanced automation)
Secrets: GitGuardian (real-time protection)
Cost: Moderate
Setup: Moderate complexityEnterprise
Container: Snyk Container (unified platform)
SAST: SonarQube (quality gates + compliance)
DAST: Burp Suite Enterprise
SCA: Snyk Open Source (comprehensive)
Secrets: GitGuardian Enterprise (incident response)
Cost: High
Setup: High complexity
Benefits: Unified dashboards, compliance, support---
Selection Checklist
Use this checklist to select appropriate tools:
Requirements:
- [ ] Budget: Open-source, commercial, enterprise?
- [ ] Languages: Which languages need scanning?
- [ ] CI/CD platform: GitHub Actions, GitLab CI, Jenkins, other?
- [ ] Team size: How many developers?
- [ ] Security maturity: Beginning, intermediate, advanced?
- [ ] Compliance: SOC2, ISO 27001, PCI-DSS requirements?
Evaluation Criteria:
- [ ] Speed: CI/CD pipeline impact (target < 5 minutes)
- [ ] Accuracy: False positive tolerance?
- [ ] Coverage: OS, languages, secrets, misconfig?
- [ ] Integration: Existing tools and workflows?
- [ ] Maintainability: Active development, community support?
- [ ] Scalability: Can handle codebase growth?
Decision:
- Container scanning: ________________
- SAST: ________________
- DAST: ________________
- SCA: ________________
- Secret scanning: ________________
#!/bin/bash
# Generate executive vulnerability report from Trivy scan results
set -e
SCAN_FILE="${1:-scan.json}"
OUTPUT_FILE="${2:-security-report.md}"
if [ ! -f "$SCAN_FILE" ]; then
echo "Error: Scan file not found: $SCAN_FILE"
echo "Usage: $0 <scan-file.json> [output-file.md]"
exit 1
fi
echo "Generating security report from $SCAN_FILE..."
# Generate markdown report
cat > "$OUTPUT_FILE" << EOF
# Security Vulnerability Report
**Generated:** $(date '+%Y-%m-%d %H:%M:%S')
**Scan File:** $SCAN_FILE
---
## Executive Summary
$(jq -r '
[.Results[].Vulnerabilities[]] |
{
total: length,
critical: ([.[] | select(.Severity == "CRITICAL")] | length),
high: ([.[] | select(.Severity == "HIGH")] | length),
medium: ([.[] | select(.Severity == "MEDIUM")] | length),
low: ([.[] | select(.Severity == "LOW")] | length),
fixable: ([.[] | select(.FixedVersion != "")] | length)
} |
"### Vulnerability Breakdown
| Severity | Count |
|----------|-------|
| **Critical** | \(.critical) |
| **High** | \(.high) |
| **Medium** | \(.medium) |
| **Low** | \(.low) |
| **Total** | \(.total) |
**Fixable Vulnerabilities:** \(.fixable) / \(.total)
**Fix Rate:** \((.fixable / .total * 100) | floor)%"
' "$SCAN_FILE")
---
## Priority Classification
$(jq -r '
[.Results[].Vulnerabilities[]] |
group_by(
if .CVSS.nvd.V3Score >= 9.0 then "P1"
elif .CVSS.nvd.V3Score >= 7.0 then "P2"
elif .CVSS.nvd.V3Score >= 4.0 then "P3"
else "P4"
end
) |
map({
priority: (.[0] | if .CVSS.nvd.V3Score >= 9.0 then "P1" elif .CVSS.nvd.V3Score >= 7.0 then "P2" elif .CVSS.nvd.V3Score >= 4.0 then "P3" else "P4" end),
count: length,
sla: (if .[0].CVSS.nvd.V3Score >= 9.0 then "7 days" elif .[0].CVSS.nvd.V3Score >= 7.0 then "30 days" elif .[0].CVSS.nvd.V3Score >= 4.0 then "90 days" else "No SLA" end)
}) |
"| Priority | Count | SLA |
|----------|-------|-----|" +
([.[] | "| **\(.priority)** | \(.count) | \(.sla) |"] | join("\n"))
' "$SCAN_FILE")
---
## Critical Vulnerabilities (P0/P1)
$(jq -r '
[.Results[].Vulnerabilities[] | select(.CVSS.nvd.V3Score >= 9.0)] |
if length > 0 then
"| CVE | CVSS | Package | Fixed Version |
|-----|------|---------|---------------|" +
([.[] | "| \(.VulnerabilityID) | \(.CVSS.nvd.V3Score) | \(.PkgName) | \(.FixedVersion // "No fix available") |"] | join("\n"))
else
"✅ No critical vulnerabilities found."
end
' "$SCAN_FILE")
---
## Action Items
### Immediate Action (P0/P1) - 7 Day SLA
$(jq -r '
[.Results[].Vulnerabilities[] | select(.CVSS.nvd.V3Score >= 9.0)] |
if length > 0 then
[.[] | "- [ ] **\(.VulnerabilityID)** in \(.PkgName)" + (if .FixedVersion != "" then " → Upgrade to \(.FixedVersion)" else " (No fix available - implement mitigations)" end)] | join("\n")
else
"None"
end
' "$SCAN_FILE")
### Upcoming Sprint (P2) - 30 Day SLA
$(jq -r '
[.Results[].Vulnerabilities[] | select(.CVSS.nvd.V3Score >= 7.0 and .CVSS.nvd.V3Score < 9.0)] |
if length > 0 then
"**\(length) vulnerabilities** to address in upcoming sprint.\n\nTop 5:\n" +
([.[0:5][] | "- \(.VulnerabilityID) in \(.PkgName) (CVSS: \(.CVSS.nvd.V3Score))"] | join("\n"))
else
"None"
end
' "$SCAN_FILE")
---
## Package Breakdown
### Most Vulnerable Packages
$(jq -r '
[.Results[].Vulnerabilities[]] |
group_by(.PkgName) |
map({
package: .[0].PkgName,
count: length,
max_cvss: ([.[].CVSS.nvd.V3Score] | max)
}) |
sort_by(-.count) |
.[0:10] |
"| Package | Vulnerabilities | Max CVSS |
|---------|----------------|----------|" +
([.[] | "| \(.package) | \(.count) | \(.max_cvss) |"] | join("\n"))
' "$SCAN_FILE")
---
## Recommendations
### Short-term (0-30 days)
1. **Fix P1 vulnerabilities** - \$(jq '[.Results[].Vulnerabilities[] | select(.CVSS.nvd.V3Score >= 9.0)] | length' "$SCAN_FILE") critical issues require immediate attention
2. **Update vulnerable packages** - Focus on packages with multiple vulnerabilities
3. **Implement security gates** - Fail builds on new CRITICAL findings
### Medium-term (30-90 days)
1. **Address P2 vulnerabilities** - \$(jq '[.Results[].Vulnerabilities[] | select(.CVSS.nvd.V3Score >= 7.0 and .CVSS.nvd.V3Score < 9.0)] | length' "$SCAN_FILE") high-severity issues
2. **Review unfixed vulnerabilities** - Implement compensating controls
3. **Automated remediation** - Set up Dependabot or Renovate
### Long-term (90+ days)
1. **Reduce attack surface** - Remove unused dependencies
2. **Continuous monitoring** - Daily scans and alerts
3. **Security training** - Developer security awareness
---
## Appendix: Scan Metadata
$(jq -r '
"**Scan Tool:** Trivy
**Scan Date:** \(.CreatedAt)
**Scanner Version:** \(.SchemaVersion)
**Artifact:** \(.ArtifactName)
**Artifact Type:** \(.ArtifactType)"
' "$SCAN_FILE")
---
*Report generated automatically by vulnerability-report.sh*
EOF
echo "Report generated: $OUTPUT_FILE"
echo ""
echo "Summary:"
jq -r '[.Results[].Vulnerabilities[]] | {critical: ([.[] | select(.Severity == "CRITICAL")] | length), high: ([.[] | select(.Severity == "HIGH")] | length), total: length} | " Critical: \(.critical)\n High: \(.high)\n Total: \(.total)"' "$SCAN_FILE"
Related skills
FAQ
How do you prioritize which vulnerabilities to fix first?
Combine CVSS severity, EPSS exploitation probability, CISA KEV status, asset criticality, and exposure into a priority score mapped to SLA tiers.
CycloneDX or SPDX for SBOMs?
CycloneDX is recommended for DevSecOps and vulnerability tracking; SPDX suits legal and license-compliance needs.