mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-16 04:35:18 +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
198 lines
7.2 KiB
Python
198 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
"""SOC Metrics and KPI Tracking Agent - Collects and reports SOC performance metrics."""
|
|
|
|
import json
|
|
import os
|
|
import time
|
|
import logging
|
|
import argparse
|
|
from datetime import datetime
|
|
|
|
import requests
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SPLUNK_BASE = os.environ.get("SPLUNK_URL", "https://localhost:8089")
|
|
HEADERS = {"Content-Type": "application/json"}
|
|
|
|
|
|
def authenticate_splunk(base_url, username, password):
|
|
"""Authenticate to Splunk and return session key."""
|
|
resp = requests.post(
|
|
f"{base_url}/services/auth/login",
|
|
data={"username": username, "password": password},
|
|
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
|
|
timeout=30,
|
|
)
|
|
resp.raise_for_status()
|
|
session_key = resp.json()["sessionKey"]
|
|
logger.info("Authenticated to Splunk successfully")
|
|
return {"Authorization": f"Splunk {session_key}"}
|
|
|
|
|
|
def run_splunk_search(base_url, headers, query, earliest="-30d", latest="now"):
|
|
"""Execute a Splunk search and return results."""
|
|
search_body = {
|
|
"search": f"search {query}",
|
|
"earliest_time": earliest,
|
|
"latest_time": latest,
|
|
"output_mode": "json",
|
|
}
|
|
resp = requests.post(
|
|
f"{base_url}/services/search/jobs",
|
|
headers=headers,
|
|
data=search_body,
|
|
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
|
|
timeout=30,
|
|
)
|
|
resp.raise_for_status()
|
|
sid = resp.json()["sid"]
|
|
|
|
for _ in range(120):
|
|
status = requests.get(
|
|
f"{base_url}/services/search/jobs/{sid}",
|
|
headers=headers,
|
|
params={"output_mode": "json"},
|
|
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
|
|
timeout=30,
|
|
).json()
|
|
if status["entry"][0]["content"]["isDone"]:
|
|
break
|
|
time.sleep(2)
|
|
|
|
results = requests.get(
|
|
f"{base_url}/services/search/jobs/{sid}/results",
|
|
headers=headers,
|
|
params={"output_mode": "json", "count": 0},
|
|
verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
|
|
timeout=30,
|
|
).json()
|
|
return results.get("results", [])
|
|
|
|
|
|
def collect_mttd_metrics(base_url, headers):
|
|
"""Collect Mean Time to Detect metrics from Splunk ES notable events."""
|
|
query = (
|
|
'index=notable earliest=-30d status_label="Resolved*" '
|
|
"| eval mttd_seconds = _time - orig_time "
|
|
"| where mttd_seconds > 0 AND mttd_seconds < 86400 "
|
|
"| stats avg(mttd_seconds) AS avg_mttd, median(mttd_seconds) AS med_mttd, "
|
|
"perc90(mttd_seconds) AS p90_mttd by urgency "
|
|
"| eval avg_mttd_min = round(avg_mttd / 60, 1)"
|
|
)
|
|
results = run_splunk_search(base_url, headers, query)
|
|
logger.info("MTTD metrics collected: %d urgency levels", len(results))
|
|
return results
|
|
|
|
|
|
def collect_mttr_metrics(base_url, headers):
|
|
"""Collect Mean Time to Respond metrics."""
|
|
query = (
|
|
'index=notable earliest=-30d status_label="Resolved*" '
|
|
"| eval mttr_seconds = status_end - _time "
|
|
"| where mttr_seconds > 0 AND mttr_seconds < 604800 "
|
|
"| stats avg(mttr_seconds) AS avg_mttr, median(mttr_seconds) AS med_mttr by urgency "
|
|
"| eval avg_mttr_hours = round(avg_mttr / 3600, 1)"
|
|
)
|
|
return run_splunk_search(base_url, headers, query)
|
|
|
|
|
|
def collect_alert_quality(base_url, headers):
|
|
"""Collect alert disposition and quality metrics."""
|
|
query = (
|
|
"index=notable earliest=-30d "
|
|
"| stats count AS total, "
|
|
'sum(eval(if(status_label="Resolved - True Positive", 1, 0))) AS tp, '
|
|
'sum(eval(if(status_label="Resolved - False Positive", 1, 0))) AS fp '
|
|
"| eval tp_rate = round(tp / total * 100, 1) "
|
|
"| eval fp_rate = round(fp / total * 100, 1) "
|
|
"| eval signal_noise = round(tp / (fp + 0.01), 2)"
|
|
)
|
|
return run_splunk_search(base_url, headers, query)
|
|
|
|
|
|
def collect_analyst_productivity(base_url, headers):
|
|
"""Collect per-analyst productivity metrics."""
|
|
query = (
|
|
'index=notable earliest=-30d status_label="Resolved*" '
|
|
"| stats count AS alerts_resolved, "
|
|
"avg(eval((status_end - status_transition_time) / 60)) AS avg_triage_min "
|
|
"by owner "
|
|
"| eval alerts_per_day = round(alerts_resolved / 30, 1) "
|
|
"| sort - alerts_resolved"
|
|
)
|
|
return run_splunk_search(base_url, headers, query)
|
|
|
|
|
|
def generate_report(mttd, mttr, quality, productivity):
|
|
"""Generate formatted SOC performance report."""
|
|
report_date = datetime.utcnow().strftime("%B %Y")
|
|
lines = [
|
|
f"SOC PERFORMANCE REPORT - {report_date}",
|
|
"=" * 50,
|
|
"",
|
|
"KEY METRICS (MTTD):",
|
|
]
|
|
for row in mttd:
|
|
lines.append(
|
|
f" {row.get('urgency', 'N/A'):15s} Avg: {row.get('avg_mttd_min', 'N/A')} min"
|
|
)
|
|
|
|
lines.append("\nKEY METRICS (MTTR):")
|
|
for row in mttr:
|
|
lines.append(
|
|
f" {row.get('urgency', 'N/A'):15s} Avg: {row.get('avg_mttr_hours', 'N/A')} hrs"
|
|
)
|
|
|
|
lines.append("\nALERT QUALITY:")
|
|
for row in quality:
|
|
lines.append(f" Total Alerts: {row.get('total', 'N/A')}")
|
|
lines.append(f" True Positive Rate: {row.get('tp_rate', 'N/A')}%")
|
|
lines.append(f" False Positive Rate: {row.get('fp_rate', 'N/A')}%")
|
|
lines.append(f" Signal-to-Noise: {row.get('signal_noise', 'N/A')}")
|
|
|
|
lines.append("\nANALYST PRODUCTIVITY:")
|
|
for row in productivity:
|
|
lines.append(
|
|
f" {row.get('owner', 'N/A'):20s} {row.get('alerts_per_day', 'N/A')} alerts/day "
|
|
f"Avg triage: {row.get('avg_triage_min', 'N/A')} min"
|
|
)
|
|
|
|
report = "\n".join(lines)
|
|
print(report)
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="SOC Metrics and KPI Tracking Agent")
|
|
parser.add_argument("--splunk-url", default=SPLUNK_BASE, help="Splunk base URL")
|
|
parser.add_argument("--username", default="admin", help="Splunk username")
|
|
parser.add_argument("--password", required=True, help="Splunk password")
|
|
parser.add_argument("--output", default="soc_metrics_report.json", help="Output JSON file")
|
|
args = parser.parse_args()
|
|
|
|
headers = authenticate_splunk(args.splunk_url, args.username, args.password)
|
|
|
|
mttd = collect_mttd_metrics(args.splunk_url, headers)
|
|
mttr = collect_mttr_metrics(args.splunk_url, headers)
|
|
quality = collect_alert_quality(args.splunk_url, headers)
|
|
productivity = collect_analyst_productivity(args.splunk_url, headers)
|
|
|
|
generate_report(mttd, mttr, quality, productivity)
|
|
|
|
output = {
|
|
"generated_at": datetime.utcnow().isoformat(),
|
|
"mttd_metrics": mttd,
|
|
"mttr_metrics": mttr,
|
|
"alert_quality": quality,
|
|
"analyst_productivity": productivity,
|
|
}
|
|
with open(args.output, "w") as f:
|
|
json.dump(output, f, indent=2)
|
|
logger.info("Report saved to %s", args.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|