Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

160 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""Trivy Container Security Agent - scans images for vulnerabilities, misconfigs, and secrets."""
import json
import argparse
import logging
import subprocess
from collections import defaultdict
from datetime import datetime
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def run_trivy_scan(image, scanners="vuln,secret", severity="CRITICAL,HIGH,MEDIUM"):
"""Run Trivy image scan and return JSON results."""
cmd = [
"trivy", "image", "--format", "json", "--scanners", scanners,
"--severity", severity, "--quiet", image,
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode != 0 and not result.stdout:
logger.error("Trivy scan failed: %s", result.stderr)
return {}
return json.loads(result.stdout) if result.stdout else {}
def run_trivy_misconfig(target_path):
"""Run Trivy misconfiguration scan on Dockerfile/K8s manifests."""
cmd = [
"trivy", "config", "--format", "json", "--severity", "CRITICAL,HIGH,MEDIUM",
"--quiet", target_path,
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return json.loads(result.stdout) if result.stdout else {}
def generate_sbom(image, sbom_format="cyclonedx"):
"""Generate SBOM from container image."""
cmd = ["trivy", "image", "--format", sbom_format, "--quiet", image]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
return result.stdout if result.returncode == 0 else ""
def analyze_vulnerabilities(scan_results):
"""Analyze vulnerability scan results and produce summary."""
by_severity = defaultdict(int)
by_pkg_type = defaultdict(int)
fixable = 0
total = 0
cve_list = []
for target in scan_results.get("Results", []):
pkg_type = target.get("Type", "unknown")
for vuln in target.get("Vulnerabilities", []):
severity = vuln.get("Severity", "UNKNOWN")
by_severity[severity] += 1
by_pkg_type[pkg_type] += 1
total += 1
if vuln.get("FixedVersion"):
fixable += 1
if severity in ("CRITICAL", "HIGH"):
cve_list.append({
"cve_id": vuln.get("VulnerabilityID", ""),
"package": vuln.get("PkgName", ""),
"installed": vuln.get("InstalledVersion", ""),
"fixed": vuln.get("FixedVersion", ""),
"severity": severity,
"cvss_score": vuln.get("CVSS", {}).get("nvd", {}).get("V3Score", 0),
"title": vuln.get("Title", "")[:100],
})
return {
"total_vulnerabilities": total,
"by_severity": dict(by_severity),
"by_package_type": dict(by_pkg_type),
"fixable_count": fixable,
"fix_rate": round(fixable / max(total, 1) * 100, 1),
"critical_high_cves": sorted(cve_list, key=lambda x: x.get("cvss_score", 0), reverse=True)[:20],
}
def analyze_secrets(scan_results):
"""Analyze secret detection results."""
secrets = []
for target in scan_results.get("Results", []):
for secret in target.get("Secrets", []):
secrets.append({
"rule_id": secret.get("RuleID", ""),
"category": secret.get("Category", ""),
"title": secret.get("Title", ""),
"severity": secret.get("Severity", ""),
"target_file": target.get("Target", ""),
})
return {"total_secrets": len(secrets), "secrets": secrets[:15]}
def analyze_misconfigs(misconfig_results):
"""Analyze misconfiguration scan results."""
findings = []
for target in misconfig_results.get("Results", []):
for mc in target.get("Misconfigurations", []):
findings.append({
"avd_id": mc.get("AVDID", ""),
"title": mc.get("Title", ""),
"severity": mc.get("Severity", ""),
"target": target.get("Target", ""),
"resolution": mc.get("Resolution", "")[:150],
})
by_sev = defaultdict(int)
for f in findings:
by_sev[f["severity"]] += 1
return {"total_misconfigs": len(findings), "by_severity": dict(by_sev), "findings": findings[:15]}
def generate_report(image, vuln_analysis, secret_analysis, misconfig_analysis):
critical = vuln_analysis["by_severity"].get("CRITICAL", 0)
high = vuln_analysis["by_severity"].get("HIGH", 0)
return {
"timestamp": datetime.utcnow().isoformat(),
"image": image,
"vulnerability_summary": vuln_analysis,
"secret_summary": secret_analysis,
"misconfiguration_summary": misconfig_analysis,
"gate_result": "FAIL" if critical > 0 else "WARN" if high > 0 else "PASS",
}
def main():
parser = argparse.ArgumentParser(description="Trivy Container Security Scanning Agent")
parser.add_argument("--image", required=True, help="Container image to scan (e.g., nginx:latest)")
parser.add_argument("--config-path", help="Path to Dockerfile/K8s manifests for misconfig scan")
parser.add_argument("--severity", default="CRITICAL,HIGH,MEDIUM", help="Severity filter")
parser.add_argument("--sbom", action="store_true", help="Generate CycloneDX SBOM")
parser.add_argument("--output", default="trivy_scan_report.json")
args = parser.parse_args()
scan_results = run_trivy_scan(args.image, severity=args.severity)
vuln_analysis = analyze_vulnerabilities(scan_results)
secret_analysis = analyze_secrets(scan_results)
misconfig_analysis = {}
if args.config_path:
misconfig_results = run_trivy_misconfig(args.config_path)
misconfig_analysis = analyze_misconfigs(misconfig_results)
if args.sbom:
sbom_data = generate_sbom(args.image)
if sbom_data:
with open(args.output.replace(".json", "_sbom.json"), "w") as f:
f.write(sbom_data)
report = generate_report(args.image, vuln_analysis, secret_analysis, misconfig_analysis)
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
logger.info("Trivy: %s - %d vulns (%d critical), %d secrets, gate: %s",
args.image, vuln_analysis["total_vulnerabilities"],
vuln_analysis["by_severity"].get("CRITICAL", 0),
secret_analysis["total_secrets"], report["gate_result"])
print(json.dumps(report, indent=2, default=str))
if __name__ == "__main__":
main()