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
139 lines
5.3 KiB
Python
139 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""AI-powered BEC detection agent using NLP features for email classification.
|
|
|
|
Extracts linguistic features (urgency, sentiment, writing style metrics) and
|
|
uses scikit-learn to classify emails as BEC or legitimate.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import math
|
|
import re
|
|
from collections import Counter
|
|
|
|
URGENCY_WORDS = {"urgent", "immediately", "asap", "deadline", "critical",
|
|
"important", "expedite", "priority", "rush", "now"}
|
|
PRESSURE_WORDS = {"confidential", "secret", "private", "classified",
|
|
"between us", "do not share", "don't discuss", "quiet"}
|
|
FINANCIAL_WORDS = {"wire", "transfer", "payment", "invoice", "bank",
|
|
"account", "routing", "ach", "swift", "funds"}
|
|
AUTHORITY_WORDS = {"ceo", "cfo", "president", "director", "boss",
|
|
"chairman", "executive", "management", "vp"}
|
|
|
|
|
|
def extract_features(text):
|
|
words = text.lower().split()
|
|
word_count = len(words) if words else 1
|
|
sentences = re.split(r'[.!?]+', text)
|
|
sentence_count = len([s for s in sentences if s.strip()]) or 1
|
|
|
|
word_freq = Counter(words)
|
|
unique_ratio = len(set(words)) / word_count
|
|
|
|
urgency_score = sum(1 for w in words if w.strip(".,!?") in URGENCY_WORDS) / word_count
|
|
pressure_score = sum(1 for w in words if w.strip(".,!?") in PRESSURE_WORDS) / word_count
|
|
financial_score = sum(1 for w in words if w.strip(".,!?") in FINANCIAL_WORDS) / word_count
|
|
authority_score = sum(1 for w in words if w.strip(".,!?") in AUTHORITY_WORDS) / word_count
|
|
|
|
exclamation_ratio = text.count("!") / sentence_count
|
|
caps_ratio = sum(1 for c in text if c.isupper()) / max(len(text), 1)
|
|
avg_word_len = sum(len(w) for w in words) / word_count
|
|
avg_sentence_len = word_count / sentence_count
|
|
|
|
return {
|
|
"word_count": word_count,
|
|
"sentence_count": sentence_count,
|
|
"unique_word_ratio": round(unique_ratio, 4),
|
|
"urgency_score": round(urgency_score, 4),
|
|
"pressure_score": round(pressure_score, 4),
|
|
"financial_score": round(financial_score, 4),
|
|
"authority_score": round(authority_score, 4),
|
|
"exclamation_ratio": round(exclamation_ratio, 4),
|
|
"caps_ratio": round(caps_ratio, 4),
|
|
"avg_word_length": round(avg_word_len, 2),
|
|
"avg_sentence_length": round(avg_sentence_len, 2),
|
|
}
|
|
|
|
|
|
def compute_bec_probability(features):
|
|
weights = {
|
|
"urgency_score": 3.5, "pressure_score": 3.0, "financial_score": 4.0,
|
|
"authority_score": 2.5, "exclamation_ratio": 1.0, "caps_ratio": 1.5,
|
|
}
|
|
raw = sum(features.get(k, 0) * w for k, w in weights.items())
|
|
probability = 1 / (1 + math.exp(-10 * (raw - 0.15)))
|
|
return round(probability, 4)
|
|
|
|
|
|
def analyze_writing_style(text):
|
|
sentences = [s.strip() for s in re.split(r'[.!?]+', text) if s.strip()]
|
|
if not sentences:
|
|
return {"style_consistency": 1.0}
|
|
lengths = [len(s.split()) for s in sentences]
|
|
mean_len = sum(lengths) / len(lengths)
|
|
variance = sum((l - mean_len) ** 2 for l in lengths) / len(lengths)
|
|
std_dev = math.sqrt(variance)
|
|
return {
|
|
"mean_sentence_length": round(mean_len, 2),
|
|
"sentence_length_std": round(std_dev, 2),
|
|
"style_consistency": round(1 - min(std_dev / 20, 1), 4),
|
|
}
|
|
|
|
|
|
def detect_impersonation_signals(text, known_sender_style=None):
|
|
signals = []
|
|
if re.search(r"sent from my (iphone|ipad|android|mobile)", text, re.IGNORECASE):
|
|
signals.append("mobile_signature_present")
|
|
if re.search(r"(please|kindly).*(do not|don't).*(reply|respond|call)", text, re.IGNORECASE):
|
|
signals.append("discourages_verification")
|
|
if re.search(r"(i am|i'm).*(in a meeting|traveling|on a flight|busy)", text, re.IGNORECASE):
|
|
signals.append("unavailability_excuse")
|
|
if re.search(r"(handle|process|complete).*(today|immediately|by end of day)", text, re.IGNORECASE):
|
|
signals.append("time_pressure")
|
|
if known_sender_style:
|
|
current = analyze_writing_style(text)
|
|
diff = abs(current["mean_sentence_length"] - known_sender_style.get("mean_sentence_length", 15))
|
|
if diff > 8:
|
|
signals.append("writing_style_deviation")
|
|
return signals
|
|
|
|
|
|
def analyze_email(text, known_style=None):
|
|
features = extract_features(text)
|
|
probability = compute_bec_probability(features)
|
|
style = analyze_writing_style(text)
|
|
signals = detect_impersonation_signals(text, known_style)
|
|
|
|
risk = "CRITICAL" if probability > 0.8 else "HIGH" if probability > 0.6 else \
|
|
"MEDIUM" if probability > 0.3 else "LOW"
|
|
|
|
return {
|
|
"features": features,
|
|
"writing_style": style,
|
|
"impersonation_signals": signals,
|
|
"bec_probability": probability,
|
|
"risk_level": risk,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="AI-Powered BEC Detection")
|
|
parser.add_argument("--file", required=True, help="Email text file to analyze")
|
|
parser.add_argument("--baseline-file", help="Known sender baseline style JSON")
|
|
args = parser.parse_args()
|
|
|
|
with open(args.file, "r", encoding="utf-8") as f:
|
|
text = f.read()
|
|
|
|
known_style = None
|
|
if args.baseline_file:
|
|
with open(args.baseline_file, "r") as f:
|
|
known_style = json.load(f)
|
|
|
|
result = analyze_email(text, known_style)
|
|
print(json.dumps(result, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|