Files
Anthropic-Cybersecurity-Skills/skills/triaging-security-incident/scripts/agent.py
T
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

263 lines
10 KiB
Python

#!/usr/bin/env python3
"""Agent for triaging security incidents using NIST SP 800-61 and SANS PICERL frameworks."""
import requests
import json
import argparse
from datetime import datetime, timezone
NIST_CATEGORIES = {
"unauthorized_access": "Unauthorized Access",
"dos": "Denial of Service",
"malicious_code": "Malicious Code",
"improper_usage": "Improper Usage",
"reconnaissance": "Reconnaissance",
"web_attack": "Web Application Attack",
}
SEVERITY_MATRIX = {
"P1": {"label": "Critical", "ack_sla": "15 min", "contain_sla": "1 hour",
"criteria": "Crown jewel compromise, active exfiltration, ransomware spreading"},
"P2": {"label": "High", "ack_sla": "30 min", "contain_sla": "4 hours",
"criteria": "Production compromise, confirmed malware, privileged account takeover"},
"P3": {"label": "Medium", "ack_sla": "2 hours", "contain_sla": "24 hours",
"criteria": "Non-production compromise, failed exploitation, single endpoint malware"},
"P4": {"label": "Low", "ack_sla": "8 hours", "contain_sla": "72 hours",
"criteria": "Reconnaissance, policy violation, benign true positive"},
}
def classify_incident(alert_data):
"""Classify incident type based on alert indicators."""
print("[*] Classifying incident type...")
alert_name = alert_data.get("alert_name", "").lower()
process = alert_data.get("process", "").lower()
event_code = alert_data.get("event_code", "")
if any(kw in alert_name for kw in ["malware", "ransomware", "trojan", "cryptominer"]):
category = "malicious_code"
elif any(kw in alert_name for kw in ["brute force", "credential", "password spray"]):
category = "unauthorized_access"
elif any(kw in alert_name for kw in ["dos", "flood", "resource exhaustion"]):
category = "dos"
elif any(kw in alert_name for kw in ["sql injection", "xss", "ssrf", "xxe"]):
category = "web_attack"
elif any(kw in alert_name for kw in ["scan", "enum", "recon", "discovery"]):
category = "reconnaissance"
elif "powershell" in process and "encoded" in alert_name.lower():
category = "malicious_code"
else:
category = "unauthorized_access"
classification = NIST_CATEGORIES.get(category, "Unknown")
print(f" [+] Classification: {classification} ({category})")
return category, classification
def assess_severity(alert_data, asset_criticality="medium"):
"""Calculate incident severity based on threat and asset context."""
print("\n[*] Assessing severity...")
threat_score = 0
alert_severity = alert_data.get("severity", "").lower()
if alert_severity in ("critical", "high"):
threat_score += 3
elif alert_severity == "medium":
threat_score += 2
else:
threat_score += 1
confidence = alert_data.get("confidence", 50)
if confidence >= 80:
threat_score += 2
elif confidence >= 50:
threat_score += 1
asset_scores = {"critical": 3, "high": 2, "medium": 1, "low": 0}
asset_score = asset_scores.get(asset_criticality, 1)
total = threat_score + asset_score
if total >= 7:
priority = "P1"
elif total >= 5:
priority = "P2"
elif total >= 3:
priority = "P3"
else:
priority = "P4"
sev_info = SEVERITY_MATRIX[priority]
print(f" [+] Priority: {priority} - {sev_info['label']}")
print(f" [+] ACK SLA: {sev_info['ack_sla']} | Containment SLA: {sev_info['contain_sla']}")
return priority, sev_info
def check_virustotal(api_key, indicator, indicator_type="ip"):
"""Check an indicator against VirusTotal."""
print(f"\n[*] Checking VirusTotal for {indicator_type}: {indicator}...")
base_urls = {
"ip": f"https://www.virustotal.com/api/v3/ip_addresses/{indicator}",
"hash": f"https://www.virustotal.com/api/v3/files/{indicator}",
"domain": f"https://www.virustotal.com/api/v3/domains/{indicator}",
}
url = base_urls.get(indicator_type)
if not url:
return {}
try:
headers = {"x-apikey": api_key}
resp = requests.get(url, headers=headers, timeout=15)
if resp.status_code == 200:
data = resp.json().get("data", {}).get("attributes", {})
if indicator_type in ("ip", "domain"):
malicious = data.get("last_analysis_stats", {}).get("malicious", 0)
total = sum(data.get("last_analysis_stats", {}).values())
print(f" [+] VT Result: {malicious}/{total} vendors flagged as malicious")
return {"malicious": malicious, "total": total}
elif indicator_type == "hash":
malicious = data.get("last_analysis_stats", {}).get("malicious", 0)
name = data.get("meaningful_name", "Unknown")
print(f" [+] VT Result: {name} - {malicious} detections")
return {"name": name, "malicious": malicious}
elif resp.status_code == 404:
print(f" [-] Not found in VirusTotal")
else:
print(f" [-] VT API error: {resp.status_code}")
except requests.RequestException as e:
print(f" [-] VT request failed: {e}")
return {}
def build_mitre_mapping(category, process_info=""):
"""Map incident to MITRE ATT&CK techniques."""
mappings = {
"malicious_code": [
{"technique": "T1059.001", "name": "PowerShell"},
{"technique": "T1204.002", "name": "User Execution: Malicious File"},
],
"unauthorized_access": [
{"technique": "T1110", "name": "Brute Force"},
{"technique": "T1078", "name": "Valid Accounts"},
],
"reconnaissance": [
{"technique": "T1046", "name": "Network Service Discovery"},
{"technique": "T1595", "name": "Active Scanning"},
],
"web_attack": [
{"technique": "T1190", "name": "Exploit Public-Facing Application"},
],
}
techniques = mappings.get(category, [])
if techniques:
print(f"\n[*] MITRE ATT&CK mapping:")
for t in techniques:
print(f" - {t['technique']}: {t['name']}")
return techniques
def generate_triage_record(alert_data, classification, priority, sev_info,
ti_results, mitre, output_path):
"""Generate a structured incident triage report."""
triage_time = datetime.now(timezone.utc)
alert_time = alert_data.get("timestamp", triage_time.isoformat())
record = {
"ticket_id": f"INC-{triage_time.strftime('%Y')}-{hash(str(alert_data)) % 10000:04d}",
"triage_analyst": alert_data.get("analyst", "automated"),
"triage_time": triage_time.isoformat(),
"alert_time": alert_time,
"classification": {
"type": classification[1],
"category": classification[0],
"priority": priority,
"severity_label": sev_info["label"],
"confidence": alert_data.get("confidence", "Unknown"),
},
"affected_scope": {
"assets": alert_data.get("affected_hosts", []),
"users": alert_data.get("affected_users", []),
"business_unit": alert_data.get("business_unit", "Unknown"),
},
"evidence": {
"alert_source": alert_data.get("source", "Unknown"),
"alert_name": alert_data.get("alert_name", "Unknown"),
"raw_indicators": alert_data.get("indicators", {}),
},
"enrichment": {
"threat_intel": ti_results,
"mitre_attack": mitre,
},
"recommended_actions": [],
"sla": {
"acknowledge_by": sev_info["ack_sla"],
"contain_by": sev_info["contain_sla"],
},
}
if priority in ("P1", "P2"):
record["recommended_actions"] = [
"Isolate affected endpoint via EDR",
"Disable compromised user account",
"Preserve volatile evidence (memory dump)",
"Escalate to Tier 2 IR team",
]
else:
record["recommended_actions"] = [
"Monitor for additional indicators",
"Review related alerts for the past 7 days",
"Document findings and close if benign",
]
with open(output_path, "w") as f:
json.dump(record, f, indent=2)
print(f"\n[*] Triage record saved to {output_path}")
print(f"[*] Ticket: {record['ticket_id']} | Priority: {priority} ({sev_info['label']})")
return record
def main():
parser = argparse.ArgumentParser(description="Security Incident Triage Agent")
parser.add_argument("--alert-name", required=True, help="Name of the triggering alert")
parser.add_argument("--source", default="SIEM", help="Alert source system")
parser.add_argument("--severity", default="high", help="Alert severity")
parser.add_argument("--confidence", type=int, default=75, help="Alert confidence (0-100)")
parser.add_argument("--host", help="Affected hostname")
parser.add_argument("--src-ip", help="Source IP address")
parser.add_argument("--user", help="Affected username")
parser.add_argument("--process", default="", help="Suspicious process name")
parser.add_argument("--asset-criticality", default="medium",
choices=["critical", "high", "medium", "low"])
parser.add_argument("--vt-key", help="VirusTotal API key for threat intel")
parser.add_argument("--indicator", help="IOC to check (IP, hash, or domain)")
parser.add_argument("--indicator-type", default="ip", choices=["ip", "hash", "domain"])
parser.add_argument("-o", "--output", default="triage_record.json")
args = parser.parse_args()
alert_data = {
"alert_name": args.alert_name,
"source": args.source,
"severity": args.severity,
"confidence": args.confidence,
"affected_hosts": [args.host] if args.host else [],
"affected_users": [args.user] if args.user else [],
"process": args.process,
"indicators": {"src_ip": args.src_ip},
"timestamp": datetime.now(timezone.utc).isoformat(),
}
print("[*] Security Incident Triage\n")
classification = classify_incident(alert_data)
priority, sev_info = assess_severity(alert_data, args.asset_criticality)
mitre = build_mitre_mapping(classification[0], args.process)
ti_results = {}
if args.vt_key and args.indicator:
ti_results = check_virustotal(args.vt_key, args.indicator, args.indicator_type)
generate_triage_record(alert_data, classification, priority, sev_info,
ti_results, mitre, args.output)
if __name__ == "__main__":
main()