mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-24 21:40:58 +03:00
Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills: - scripts/agent.py: 80-150 line Python agents using real libraries (impacket, boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.) - references/api-reference.md: real API documentation with method signatures - LICENSE: MIT license for all skill folders
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""BEC detection agent - analyzes email headers and content for Business Email Compromise indicators.
|
||||
|
||||
Parses email headers for spoofing signals, checks DMARC/SPF/DKIM alignment,
|
||||
detects urgency language patterns, and flags financial request anomalies.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import email
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from email import policy
|
||||
from pathlib import Path
|
||||
|
||||
BEC_URGENCY_PATTERNS = [
|
||||
r"\b(urgent|immediately|asap|right away|time.?sensitive)\b",
|
||||
r"\b(confidential|do not share|keep this between us|don't tell)\b",
|
||||
r"\b(wire transfer|bank transfer|payment|invoice|routing number)\b",
|
||||
r"\b(gift card|bitcoin|crypto|western union|moneygram)\b",
|
||||
r"\b(ceo|cfo|president|director) (asked|requested|needs|wants)\b",
|
||||
r"\b(change.*(bank|account|payment)|new.*(bank|account|routing))\b",
|
||||
r"\b(act now|deadline today|end of day|before close)\b",
|
||||
]
|
||||
|
||||
EXECUTIVE_TITLES = ["ceo", "cfo", "coo", "cto", "president", "chairman",
|
||||
"managing director", "vice president", "vp", "director"]
|
||||
|
||||
|
||||
def parse_email_file(filepath):
|
||||
with open(filepath, "r", encoding="utf-8", errors="replace") as f:
|
||||
return email.message_from_file(f, policy=policy.default)
|
||||
|
||||
|
||||
def check_spf_dkim_dmarc(msg):
|
||||
results = {"spf": "none", "dkim": "none", "dmarc": "none"}
|
||||
auth_results = msg.get("Authentication-Results", "")
|
||||
if "spf=pass" in auth_results.lower():
|
||||
results["spf"] = "pass"
|
||||
elif "spf=fail" in auth_results.lower():
|
||||
results["spf"] = "fail"
|
||||
if "dkim=pass" in auth_results.lower():
|
||||
results["dkim"] = "pass"
|
||||
elif "dkim=fail" in auth_results.lower():
|
||||
results["dkim"] = "fail"
|
||||
if "dmarc=pass" in auth_results.lower():
|
||||
results["dmarc"] = "pass"
|
||||
elif "dmarc=fail" in auth_results.lower():
|
||||
results["dmarc"] = "fail"
|
||||
return results
|
||||
|
||||
|
||||
def check_display_name_spoofing(msg, vip_names):
|
||||
from_header = msg.get("From", "")
|
||||
match = re.match(r'"?([^"<]+)"?\s*<([^>]+)>', from_header)
|
||||
if not match:
|
||||
return None
|
||||
display_name = match.group(1).strip().lower()
|
||||
email_addr = match.group(2).strip().lower()
|
||||
for vip in vip_names:
|
||||
if vip.lower() in display_name:
|
||||
domain = email_addr.split("@")[-1] if "@" in email_addr else ""
|
||||
return {"display_name": display_name, "email": email_addr,
|
||||
"matched_vip": vip, "domain": domain,
|
||||
"indicator": "Display name matches VIP but email may be external"}
|
||||
return None
|
||||
|
||||
|
||||
def check_reply_to_mismatch(msg):
|
||||
from_addr = msg.get("From", "")
|
||||
reply_to = msg.get("Reply-To", "")
|
||||
if not reply_to:
|
||||
return None
|
||||
from_match = re.search(r'<([^>]+)>', from_addr) or re.search(r'(\S+@\S+)', from_addr)
|
||||
reply_match = re.search(r'<([^>]+)>', reply_to) or re.search(r'(\S+@\S+)', reply_to)
|
||||
if from_match and reply_match:
|
||||
from_email = from_match.group(1).lower()
|
||||
reply_email = reply_match.group(1).lower()
|
||||
from_domain = from_email.split("@")[-1]
|
||||
reply_domain = reply_email.split("@")[-1]
|
||||
if from_domain != reply_domain:
|
||||
return {"from": from_email, "reply_to": reply_email,
|
||||
"indicator": "Reply-To domain differs from From domain"}
|
||||
return None
|
||||
|
||||
|
||||
def detect_urgency_language(body):
|
||||
matches = []
|
||||
for pattern in BEC_URGENCY_PATTERNS:
|
||||
found = re.findall(pattern, body, re.IGNORECASE)
|
||||
if found:
|
||||
matches.extend(found)
|
||||
return matches
|
||||
|
||||
|
||||
def calculate_bec_score(auth, spoofing, reply_mismatch, urgency_matches):
|
||||
score = 0
|
||||
if auth.get("spf") == "fail":
|
||||
score += 25
|
||||
if auth.get("dkim") == "fail":
|
||||
score += 20
|
||||
if auth.get("dmarc") == "fail":
|
||||
score += 30
|
||||
if spoofing:
|
||||
score += 35
|
||||
if reply_mismatch:
|
||||
score += 25
|
||||
score += min(len(urgency_matches) * 10, 40)
|
||||
return min(score, 100)
|
||||
|
||||
|
||||
def analyze_email(filepath, vip_names):
|
||||
msg = parse_email_file(filepath)
|
||||
body = msg.get_body(preferencelist=("plain", "html"))
|
||||
body_text = body.get_content() if body else ""
|
||||
|
||||
auth = check_spf_dkim_dmarc(msg)
|
||||
spoofing = check_display_name_spoofing(msg, vip_names)
|
||||
reply_mismatch = check_reply_to_mismatch(msg)
|
||||
urgency = detect_urgency_language(body_text)
|
||||
score = calculate_bec_score(auth, spoofing, reply_mismatch, urgency)
|
||||
|
||||
risk = "CRITICAL" if score >= 70 else "HIGH" if score >= 50 else "MEDIUM" if score >= 30 else "LOW"
|
||||
|
||||
return {
|
||||
"file": str(filepath),
|
||||
"from": msg.get("From", ""),
|
||||
"to": msg.get("To", ""),
|
||||
"subject": msg.get("Subject", ""),
|
||||
"date": msg.get("Date", ""),
|
||||
"authentication": auth,
|
||||
"display_name_spoofing": spoofing,
|
||||
"reply_to_mismatch": reply_mismatch,
|
||||
"urgency_indicators": urgency,
|
||||
"bec_score": score,
|
||||
"risk_level": risk,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="BEC Email Analyzer")
|
||||
parser.add_argument("--email-file", required=True, help="Path to .eml file")
|
||||
parser.add_argument("--vip-names", nargs="+", default=[], help="VIP display names to check")
|
||||
parser.add_argument("--scan-dir", help="Scan all .eml files in directory")
|
||||
args = parser.parse_args()
|
||||
|
||||
results = []
|
||||
if args.scan_dir:
|
||||
for eml in Path(args.scan_dir).glob("*.eml"):
|
||||
results.append(analyze_email(str(eml), args.vip_names))
|
||||
else:
|
||||
results.append(analyze_email(args.email_file, args.vip_names))
|
||||
|
||||
print(json.dumps(results, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user