mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-10 21:24:56 +03:00
173 lines
6.8 KiB
Python
173 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
"""DevSecOps security scanning pipeline agent.
|
|
|
|
Orchestrates Semgrep (SAST), Trivy (container/SCA), and Gitleaks (secrets)
|
|
scans via subprocess, aggregates findings, and enforces severity gates.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import datetime
|
|
|
|
|
|
SEVERITY_ORDER = {"CRITICAL": 4, "HIGH": 3, "MEDIUM": 2, "LOW": 1, "INFO": 0}
|
|
|
|
|
|
def run_semgrep(target_dir, config="auto"):
|
|
"""Run Semgrep SAST scan and return findings."""
|
|
cmd = ["semgrep", "scan", "--config", config, "--json", "--quiet", target_dir]
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
|
data = json.loads(result.stdout) if result.stdout.strip() else {}
|
|
findings = []
|
|
for r in data.get("results", []):
|
|
findings.append({
|
|
"tool": "semgrep",
|
|
"rule_id": r.get("check_id", ""),
|
|
"severity": r.get("extra", {}).get("severity", "WARNING").upper(),
|
|
"message": r.get("extra", {}).get("message", ""),
|
|
"file": r.get("path", ""),
|
|
"line": r.get("start", {}).get("line", 0),
|
|
})
|
|
return findings
|
|
except FileNotFoundError:
|
|
return [{"tool": "semgrep", "error": "semgrep not installed"}]
|
|
except subprocess.TimeoutExpired:
|
|
return [{"tool": "semgrep", "error": "scan timed out"}]
|
|
except json.JSONDecodeError:
|
|
return [{"tool": "semgrep", "error": "invalid JSON output"}]
|
|
|
|
|
|
def run_trivy(image_or_path, scan_type="image"):
|
|
"""Run Trivy vulnerability scan on image or filesystem."""
|
|
cmd = ["trivy", scan_type, "--format", "json", "--quiet", image_or_path]
|
|
if scan_type == "fs":
|
|
cmd = ["trivy", "fs", "--format", "json", "--quiet", "--scanners", "vuln,secret", image_or_path]
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
|
data = json.loads(result.stdout) if result.stdout.strip() else {}
|
|
findings = []
|
|
for res in data.get("Results", []):
|
|
for vuln in res.get("Vulnerabilities", []):
|
|
findings.append({
|
|
"tool": "trivy",
|
|
"rule_id": vuln.get("VulnerabilityID", ""),
|
|
"severity": vuln.get("Severity", "UNKNOWN").upper(),
|
|
"message": vuln.get("Title", ""),
|
|
"file": res.get("Target", ""),
|
|
"line": 0,
|
|
"fixed_version": vuln.get("FixedVersion", ""),
|
|
})
|
|
return findings
|
|
except FileNotFoundError:
|
|
return [{"tool": "trivy", "error": "trivy not installed"}]
|
|
except (subprocess.TimeoutExpired, json.JSONDecodeError) as e:
|
|
return [{"tool": "trivy", "error": str(e)}]
|
|
|
|
|
|
def run_gitleaks(repo_path):
|
|
"""Run Gitleaks secret detection scan."""
|
|
cmd = ["gitleaks", "detect", "--source", repo_path, "--report-format", "json",
|
|
"--report-path", "/dev/stdout", "--no-banner"]
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
data = json.loads(result.stdout) if result.stdout.strip().startswith("[") else []
|
|
findings = []
|
|
for leak in data:
|
|
findings.append({
|
|
"tool": "gitleaks",
|
|
"rule_id": leak.get("RuleID", ""),
|
|
"severity": "HIGH",
|
|
"message": leak.get("Description", ""),
|
|
"file": leak.get("File", ""),
|
|
"line": leak.get("StartLine", 0),
|
|
})
|
|
return findings
|
|
except FileNotFoundError:
|
|
return [{"tool": "gitleaks", "error": "gitleaks not installed"}]
|
|
except (subprocess.TimeoutExpired, json.JSONDecodeError) as e:
|
|
return [{"tool": "gitleaks", "error": str(e)}]
|
|
|
|
|
|
def enforce_gate(findings, fail_on="HIGH"):
|
|
"""Enforce security gate based on severity threshold."""
|
|
threshold = SEVERITY_ORDER.get(fail_on.upper(), 3)
|
|
blockers = [
|
|
f for f in findings
|
|
if not f.get("error") and SEVERITY_ORDER.get(f.get("severity", ""), 0) >= threshold
|
|
]
|
|
return {
|
|
"gate_threshold": fail_on,
|
|
"blocking_findings": len(blockers),
|
|
"passed": len(blockers) == 0,
|
|
}
|
|
|
|
|
|
def aggregate_report(all_findings):
|
|
"""Aggregate findings from all scanners."""
|
|
by_severity = {}
|
|
by_tool = {}
|
|
for f in all_findings:
|
|
if f.get("error"):
|
|
continue
|
|
sev = f.get("severity", "UNKNOWN")
|
|
tool = f.get("tool", "unknown")
|
|
by_severity[sev] = by_severity.get(sev, 0) + 1
|
|
by_tool[tool] = by_tool.get(tool, 0) + 1
|
|
return {
|
|
"total_findings": sum(1 for f in all_findings if not f.get("error")),
|
|
"by_severity": by_severity,
|
|
"by_tool": by_tool,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="DevSecOps security scanning pipeline")
|
|
parser.add_argument("target", nargs="?", help="Directory or container image to scan")
|
|
parser.add_argument("--semgrep", action="store_true", help="Run Semgrep SAST")
|
|
parser.add_argument("--trivy", action="store_true", help="Run Trivy vulnerability scan")
|
|
parser.add_argument("--trivy-type", default="fs", choices=["image", "fs"], help="Trivy scan type")
|
|
parser.add_argument("--gitleaks", action="store_true", help="Run Gitleaks secret detection")
|
|
parser.add_argument("--fail-on", default="HIGH", help="Fail gate on severity (default: HIGH)")
|
|
parser.add_argument("--output", "-o", help="Output JSON report path")
|
|
args = parser.parse_args()
|
|
|
|
print("[*] DevSecOps Security Scanning Pipeline")
|
|
report = {"timestamp": datetime.datetime.utcnow().isoformat() + "Z", "findings": []}
|
|
|
|
if not args.target:
|
|
print("[DEMO] Usage: python agent.py /path/to/code --semgrep --trivy --gitleaks")
|
|
print(" Tools: semgrep (SAST), trivy (SCA/container), gitleaks (secrets)")
|
|
print(json.dumps({"demo": True, "tools": ["semgrep", "trivy", "gitleaks"]}, indent=2))
|
|
sys.exit(0)
|
|
|
|
if args.semgrep:
|
|
report["findings"].extend(run_semgrep(args.target))
|
|
if args.trivy:
|
|
report["findings"].extend(run_trivy(args.target, args.trivy_type))
|
|
if args.gitleaks:
|
|
report["findings"].extend(run_gitleaks(args.target))
|
|
|
|
summary = aggregate_report(report["findings"])
|
|
gate = enforce_gate(report["findings"], args.fail_on)
|
|
report["summary"] = summary
|
|
report["gate"] = gate
|
|
|
|
print(f"[*] Total findings: {summary['total_findings']}")
|
|
print(f"[*] By severity: {summary['by_severity']}")
|
|
print(f"[*] Gate ({args.fail_on}): {'PASSED' if gate['passed'] else 'FAILED'}")
|
|
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
|
|
print(json.dumps({"gate_passed": gate["passed"], "total": summary["total_findings"]}, indent=2))
|
|
sys.exit(0 if gate["passed"] else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|