mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 14:14:56 +03:00
c47eed6a64
- 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
181 lines
6.7 KiB
Python
181 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Vulnerability dashboard builder using DefectDojo API.
|
|
|
|
Queries DefectDojo REST API v2 for findings, products, and engagements
|
|
to build vulnerability management dashboards and metrics.
|
|
"""
|
|
|
|
import json
|
|
import datetime
|
|
import os
|
|
import collections
|
|
|
|
try:
|
|
import requests
|
|
HAS_REQUESTS = True
|
|
except ImportError:
|
|
HAS_REQUESTS = False
|
|
|
|
|
|
class DefectDojoClient:
|
|
"""Client for DefectDojo REST API v2."""
|
|
|
|
def __init__(self, url=None, api_key=None):
|
|
self.url = (url or os.environ.get("DEFECTDOJO_URL", "http://localhost:8080")).rstrip("/")
|
|
self.api_key = api_key or os.environ.get("DEFECTDOJO_API_KEY", "")
|
|
self.headers = {
|
|
"Authorization": "Token " + self.api_key,
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
def _get(self, endpoint, params=None):
|
|
if not HAS_REQUESTS or not self.api_key:
|
|
return {"error": "requests not available or no API key"}
|
|
try:
|
|
resp = requests.get(
|
|
self.url + "/api/v2/" + endpoint,
|
|
headers=self.headers, params=params, timeout=15
|
|
)
|
|
if resp.status_code == 200:
|
|
return resp.json()
|
|
return {"error": "HTTP {}".format(resp.status_code)}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
def get_findings(self, severity=None, active=True, limit=100):
|
|
params = {"active": active, "limit": limit}
|
|
if severity:
|
|
params["severity"] = severity
|
|
return self._get("findings/", params)
|
|
|
|
def get_products(self, limit=100):
|
|
return self._get("products/", {"limit": limit})
|
|
|
|
def get_engagements(self, product_id=None, limit=100):
|
|
params = {"limit": limit}
|
|
if product_id:
|
|
params["product"] = product_id
|
|
return self._get("engagements/", params)
|
|
|
|
def get_finding_count_by_severity(self):
|
|
result = {}
|
|
for sev in ["Critical", "High", "Medium", "Low", "Info"]:
|
|
data = self._get("findings/", {"severity": sev, "active": True, "limit": 1})
|
|
if isinstance(data, dict) and "count" in data:
|
|
result[sev] = data["count"]
|
|
return result
|
|
|
|
def import_scan(self, product_id, engagement_id, scan_type, file_path):
|
|
if not HAS_REQUESTS or not self.api_key:
|
|
return {"error": "requests not available or no API key"}
|
|
try:
|
|
with open(file_path, "rb") as f:
|
|
resp = requests.post(
|
|
self.url + "/api/v2/import-scan/",
|
|
headers={"Authorization": "Token " + self.api_key},
|
|
data={
|
|
"product": product_id,
|
|
"engagement": engagement_id,
|
|
"scan_type": scan_type,
|
|
"active": True,
|
|
"verified": False,
|
|
},
|
|
files={"file": f},
|
|
timeout=60,
|
|
)
|
|
if resp.status_code in (200, 201):
|
|
return resp.json()
|
|
return {"error": "HTTP {}".format(resp.status_code)}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
def build_dashboard_data(findings):
|
|
"""Build dashboard metrics from findings list."""
|
|
if not isinstance(findings, dict) or "results" not in findings:
|
|
return {"error": "Invalid findings data"}
|
|
|
|
results = findings["results"]
|
|
severity_counts = collections.Counter()
|
|
product_counts = collections.Counter()
|
|
age_sum = 0
|
|
overdue_count = 0
|
|
now = datetime.datetime.now(datetime.timezone.utc)
|
|
|
|
for f in results:
|
|
severity_counts[f.get("severity", "Unknown")] += 1
|
|
product_counts[f.get("test", {}).get("engagement", {}).get("product", {}).get("name", "Unknown")] += 1
|
|
if f.get("date"):
|
|
try:
|
|
created = datetime.datetime.fromisoformat(f["date"])
|
|
if created.tzinfo is None:
|
|
created = created.replace(tzinfo=datetime.timezone.utc)
|
|
age = (now - created).days
|
|
age_sum += age
|
|
sla = {"Critical": 7, "High": 30, "Medium": 90, "Low": 180}.get(f.get("severity", ""), 999)
|
|
if age > sla:
|
|
overdue_count += 1
|
|
except ValueError:
|
|
pass
|
|
|
|
total = len(results)
|
|
return {
|
|
"total_active_findings": total,
|
|
"by_severity": dict(severity_counts),
|
|
"by_product": dict(product_counts.most_common(10)),
|
|
"avg_age_days": round(age_sum / max(total, 1), 1),
|
|
"overdue_count": overdue_count,
|
|
"sla_compliance_pct": round((total - overdue_count) / max(total, 1) * 100, 1),
|
|
}
|
|
|
|
|
|
SUPPORTED_SCAN_TYPES = [
|
|
"Nessus Scan", "Qualys Scan", "Burp REST API",
|
|
"ZAP Scan", "Trivy Scan", "Snyk Scan",
|
|
"Semgrep JSON Report", "SARIF", "Generic Findings Import",
|
|
"Anchore Grype", "Nuclei Scan", "Checkov Scan",
|
|
]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("=" * 60)
|
|
print("Vulnerability Dashboard with DefectDojo")
|
|
print("REST API v2 queries, severity metrics, SLA tracking")
|
|
print("=" * 60)
|
|
print(" requests available: {}".format(HAS_REQUESTS))
|
|
|
|
client = DefectDojoClient()
|
|
|
|
print("\n--- Supported Scan Types ---")
|
|
for st in SUPPORTED_SCAN_TYPES:
|
|
print(" - {}".format(st))
|
|
|
|
print("\n--- API Endpoints ---")
|
|
endpoints = [
|
|
("GET", "/api/v2/findings/", "List findings"),
|
|
("GET", "/api/v2/products/", "List products"),
|
|
("GET", "/api/v2/engagements/", "List engagements"),
|
|
("POST", "/api/v2/import-scan/", "Import scan results"),
|
|
("POST", "/api/v2/reimport-scan/", "Re-import scan results"),
|
|
]
|
|
for method, path, desc in endpoints:
|
|
print(" {} {:30s} {}".format(method, path, desc))
|
|
|
|
demo_findings = {
|
|
"count": 5,
|
|
"results": [
|
|
{"severity": "Critical", "title": "SQL Injection", "date": "2025-01-10", "test": {"engagement": {"product": {"name": "WebApp"}}}},
|
|
{"severity": "High", "title": "XSS", "date": "2025-01-15", "test": {"engagement": {"product": {"name": "WebApp"}}}},
|
|
{"severity": "Medium", "title": "Missing Headers", "date": "2024-12-01", "test": {"engagement": {"product": {"name": "API"}}}},
|
|
{"severity": "Low", "title": "Cookie flag", "date": "2025-02-01", "test": {"engagement": {"product": {"name": "API"}}}},
|
|
{"severity": "Critical", "title": "RCE", "date": "2025-02-20", "test": {"engagement": {"product": {"name": "WebApp"}}}},
|
|
],
|
|
}
|
|
|
|
dashboard = build_dashboard_data(demo_findings)
|
|
print("\n--- Dashboard ---")
|
|
for k, v in dashboard.items():
|
|
print(" {}: {}".format(k, v))
|
|
|
|
print("\n" + json.dumps({"findings_analyzed": demo_findings["count"]}, indent=2))
|