npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
NIST CSF 2.0
MITRE ATLAS
MITRE D3FEND
When to Use
- You have collected raw OSINT data from multiple tools and sources but need to identify connections, contradictions, and patterns across them.
- You need to build a unified intelligence profile for a target entity (person, organization, or infrastructure) from fragmented data.
- Traditional manual correlation is too slow or error-prone for the volume of data collected.
- You want confidence-scored assessments of identity linkage across platforms rather than simple keyword matching.
Prerequisites
- Python 3.10+ with
requests,json, andcsvlibraries - Sherlock installed (
pip install sherlock-project) - theHarvester installed (
pip install theHarvester) - SpiderFoot 4.0+ running on localhost:5001
- Access to an LLM API (OpenAI, Anthropic, or local model via Ollama)
- Optional: Maltego CE for graph visualization of correlation results
- Optional: API keys for Shodan, VirusTotal, HaveIBeenPwned, Hunter.io
Workflow
Legal & Ethical Requirements
- Obtain documented written authorization before any investigation
- Establish lawful basis for data processing (law enforcement, corporate policy, etc.)
- Define PII retention limits and data handling procedures
- Comply with local privacy regulations (GDPR, CCPA, etc.)
Phase 1 — Multi-Source OSINT Collection
-
Create the working directory for all OSINT outputs:
mkdir -p /tmp/osint -
Enumerate usernames across platforms with Sherlock:
sherlock "targetusername" --output /tmp/osint/sherlock-results.txt --csv -
Harvest emails, subdomains, and hosts with theHarvester:
theHarvester -d targetdomain.com -b all -f /tmp/osint/harvester-results.json -
Run a SpiderFoot passive scan via REST API:
curl -s http://localhost:5001/api/scan/start \ -d "scanname=target-recon&scantarget=targetdomain.com&usecase=passive" \ | jq '.scanid' -
Export SpiderFoot results when scan completes:
SCAN_ID="<scanid_from_step_3>" curl -s "http://localhost:5001/api/scan/${SCAN_ID}/results?type=all" \ -o /tmp/osint/spiderfoot-results.json -
Query breach databases for email exposure (example with HIBP API):
curl -s -H "hibp-api-key: ${HIBP_KEY}" \ -H "User-Agent: OSINT-Correlation-Skill" \ "https://haveibeenpwned.com/api/v3/breachedaccount/target@example.com" \ -o /tmp/osint/breach-results.json
Phase 2 — Data Normalization
-
Normalize all collected data into a common schema. Create a unified JSON structure that tags each finding with its source, timestamp, and data type:
cat > /tmp/osint/normalize.py << 'EOF' import json, csv, sys, os from datetime import datetime findings = [] # Normalize Sherlock CSV results sherlock_path = "/tmp/osint/sherlock-results.txt" if os.path.exists(sherlock_path): with open(sherlock_path) as f: for row in csv.DictReader(f): findings.append({ "source": "sherlock", "type": "social_profile", "platform": row.get("name", ""), "url": row.get("url_user", ""), "username": row.get("username", ""), "status": row.get("status", ""), "collected_at": datetime.utcnow().isoformat() }) # Normalize theHarvester JSON results harvester_path = "/tmp/osint/harvester-results.json" if os.path.exists(harvester_path): with open(harvester_path) as f: data = json.load(f) for email in data.get("emails", []): findings.append({ "source": "theHarvester", "type": "email", "value": email, "collected_at": datetime.utcnow().isoformat() }) for host in data.get("hosts", []): findings.append({ "source": "theHarvester", "type": "hostname", "value": host, "collected_at": datetime.utcnow().isoformat() }) # Normalize SpiderFoot results sf_path = "/tmp/osint/spiderfoot-results.json" if os.path.exists(sf_path): with open(sf_path) as f: for item in json.load(f): findings.append({ "source": "spiderfoot", "type": item.get("type", "unknown"), "value": item.get("data", ""), "module": item.get("module", ""), "collected_at": datetime.utcnow().isoformat() }) with open("/tmp/osint/normalized-findings.json", "w") as f: json.dump(findings, f, indent=2) print(f"Normalized {len(findings)} findings from {len(set(f['source'] for f in findings))} sources") EOF python3 /tmp/osint/normalize.py
Phase 3 — AI-Driven Correlation
-
Send normalized findings to an LLM for cross-source correlation analysis:
cat > /tmp/osint/correlate.py << 'PYEOF' import json, os from openai import OpenAI # or anthropic, ollama, etc. client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) with open("/tmp/osint/normalized-findings.json") as f: findings = json.load(f) correlation_prompt = f"""You are an OSINT analyst. Analyze these findings collected from multiple sources and produce a correlation report. For each identity or entity you detect: 1. List all linked accounts/profiles with the evidence connecting them. 2. Assign a confidence score (0.0-1.0) for each linkage based on: - Exact username match across platforms (high) - Similar usernames with shared metadata (medium) - Same email in breach data and registration (high) - Co-occurring infrastructure (IP, domain) (medium) - Temporal correlation of account creation dates (low-medium) 3. Identify contradictions or potential false positives. 4. Flag high-risk exposures (breached credentials, PII leaks, infrastructure overlaps). 5. Produce a structured JSON report. Raw findings: {json.dumps(findings[:500], indent=2)} """ response = client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": "You are an expert OSINT analyst specializing in identity correlation and link analysis."}, {"role": "user", "content": correlation_prompt} ], temperature=0.1, response_format={"type": "json_object"} ) report = json.loads(response.choices[0].message.content) with open("/tmp/osint/correlation-report.json", "w") as f: json.dump(report, f, indent=2) print(json.dumps(report, indent=2)) PYEOF python3 /tmp/osint/correlate.py -
Perform entity resolution — deduplicate and merge related identities:
cat > /tmp/osint/resolve.py << 'PYEOF' import json with open("/tmp/osint/correlation-report.json") as f: report = json.load(f) # Extract entities and build a link graph entities = report.get("entities", []) print(f"Identified {len(entities)} distinct entities") for entity in entities: name = entity.get("identifier", "unknown") confidence = entity.get("confidence", 0) links = entity.get("linked_accounts", []) risk = entity.get("risk_level", "unknown") print(f" [{confidence:.0%}] {name} — {len(links)} linked accounts — risk: {risk}") PYEOF python3 /tmp/osint/resolve.py
Phase 4 — Reporting and Visualization
-
Generate a final intelligence profile in Markdown:
cat > /tmp/osint/report.py << 'PYEOF' import json from datetime import datetime with open("/tmp/osint/correlation-report.json") as f: report = json.load(f) md = f"# OSINT Correlation Report\n\n" md += f"**Generated:** {datetime.utcnow().isoformat()}Z\n\n" md += "## Entity Profiles\n\n" for entity in report.get("entities", []): eid = entity.get("identifier", "Unknown") conf = entity.get("confidence", 0) md += f"### {eid} (Confidence: {conf:.0%})\n\n" md += "| Source | Platform | Evidence |\n|--------|----------|----------|\n" for link in entity.get("linked_accounts", []): md += f"| {link.get('source','')} | {link.get('platform','')} | {link.get('evidence','')} |\n" md += f"\n**Risk Level:** {entity.get('risk_level', 'N/A')}\n\n" for flag in entity.get("flags", []): md += f"- ⚠️ {flag}\n" md += "\n" with open("/tmp/osint/intelligence-profile.md", "w") as f: f.write(md) print("Report written to /tmp/osint/intelligence-profile.md") PYEOF python3 /tmp/osint/report.py -
Optional — Import correlation graph into Maltego for visualization:
# Export entities as Maltego-compatible CSV for manual import cat > /tmp/osint/maltego_export.py << 'PYEOF' import json, csv with open("/tmp/osint/correlation-report.json") as f: report = json.load(f) with open("/tmp/osint/maltego-import.csv", "w", newline="") as f: writer = csv.writer(f) writer.writerow(["Entity Type", "Value", "Linked To", "Link Label", "Confidence"]) for entity in report.get("entities", []): for link in entity.get("linked_accounts", []): writer.writerow([ link.get("type", "Alias"), link.get("value", ""), entity.get("identifier", ""), link.get("evidence", ""), link.get("confidence", "") ]) print("Maltego CSV exported to /tmp/osint/maltego-import.csv") PYEOF python3 /tmp/osint/maltego_export.py
Key Concepts
| Concept | Description |
|---|---|
| Cross-Source Correlation | Matching identifiers (usernames, emails, IPs) across independent OSINT sources to establish entity linkage |
| Confidence Scoring | Assigning probabilistic confidence (0.0–1.0) to each linkage based on evidence strength and corroboration |
| Entity Resolution | Deduplicating and merging records that refer to the same real-world entity across fragmented datasets |
| False Positive Detection | Using AI reasoning to identify coincidental matches versus genuine identity links |
| Multi-Vector Intelligence | Combining findings from social media, DNS, breach data, and infrastructure into a single threat picture |
| Link Analysis | Graph-based examination of relationships between entities, accounts, and infrastructure |
Tools & Systems
| Tool | Role in Workflow |
|---|---|
| Sherlock | Username enumeration across 400+ social platforms |
| theHarvester | Email, subdomain, and host discovery from public sources |
| SpiderFoot | Automated OSINT collection across 200+ modules |
| Maltego | Graph-based visualization of entity relationships |
| LLM API (GPT-4, Claude, Ollama) | Cross-source reasoning, pattern detection, and confidence scoring |
| HaveIBeenPwned | Breach exposure and credential leak detection |
Common Scenarios
- Threat Actor Attribution: Correlate a suspicious username found in a phishing campaign with social media profiles, domain registrations, and breach data to build an attribution profile.
- Attack Surface Mapping: Link discovered subdomains, emails, and employee social accounts to understand an organization's full external exposure.
- Insider Threat Investigation: Cross-reference an employee's known accounts with dark web marketplace activity and breach databases.
- Brand Impersonation Detection: Identify accounts across platforms mimicking a target brand by correlating registration patterns, naming conventions, and temporal signals.
Output Format
The final output is a structured JSON correlation report and a Markdown intelligence profile containing:
{
"meta": {
"target": "targetdomain.com",
"sources_used": ["sherlock", "theHarvester", "spiderfoot", "hibp"],
"total_findings": 247,
"generated_at": "2025-01-15T14:30:00Z"
},
"entities": [
{
"identifier": "john.target",
"confidence": 0.92,
"linked_accounts": [
{
"source": "sherlock",
"platform": "GitHub",
"value": "john.target",
"evidence": "Exact username match, bio references targetdomain.com",
"confidence": 0.95
}
],
"risk_level": "high",
"flags": [
"Credentials exposed in 2 breaches (2022, 2023)",
"Admin email for targetdomain.com found in public WHOIS"
]
}
],
"contradictions": [],
"recommendations": []
}Verification
- Confirm that each linked account has been independently verified against at least two sources before assigning confidence > 0.8.
- Cross-check AI-generated correlations manually for a random sample (10–20%) to validate accuracy.
- Verify that no false positives from common usernames (e.g., "admin", "test") inflated entity profiles.
- Ensure breach data timestamps are current and from reputable aggregators.
- Validate that the final report does not include stale or retracted OSINT data.
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md4.9 KB
API Reference: Performing AI-Driven OSINT Correlation
CLI Usage
# Correlate Sherlock + theHarvester results
python agent.py --target "targetdomain.com" \
--sherlock sherlock-results.csv \
--harvester harvester-results.json \
-o correlation_report.json
# Full multi-source correlation
python agent.py --target "john.doe" \
--sherlock sherlock.csv \
--harvester harvester.json \
--spiderfoot spiderfoot.json \
--breach breach-results.json \
-o report.json \
--markdown intelligence-profile.md
# Normalize only (no correlation)
python agent.py --sherlock sherlock.csv --harvester harvester.json \
--normalize-only -o normalized.json
# Load pre-normalized generic findings
python agent.py --generic normalized_findings.json -o report.jsonSupported Data Sources
| Source | Flag | Input Format | Data Extracted |
|---|---|---|---|
| Sherlock | --sherlock |
CSV or text | Usernames, social profile URLs, platforms |
| theHarvester | --harvester |
JSON | Emails, hostnames, IP addresses |
| SpiderFoot | --spiderfoot |
JSON | Mixed OSINT findings (200+ module types) |
| Breach/HIBP | --breach |
JSON | Breach names, dates, data classes |
| Generic | --generic |
JSON array | Any pre-normalized findings |
Input File Formats
Sherlock CSV Format
username,name,url_user,exists,http_status
johndoe,GitHub,https://github.com/johndoe,Claimed,200
johndoe,Twitter,https://twitter.com/johndoe,Claimed,200theHarvester JSON Format
{
"emails": ["john@targetdomain.com", "admin@targetdomain.com"],
"hosts": ["mail.targetdomain.com", "vpn.targetdomain.com"],
"ips": ["203.0.113.10", "203.0.113.11"]
}SpiderFoot JSON Format
[
{"type": "EMAILADDR", "data": "john@targetdomain.com", "module": "sfp_hunter"},
{"type": "IP_ADDRESS", "data": "203.0.113.10", "module": "sfp_dnsresolve"},
{"type": "SOCIAL_MEDIA", "data": "https://github.com/johndoe", "module": "sfp_github"}
]Breach/HIBP JSON Format
[
{
"Name": "ExampleBreach",
"BreachDate": "2023-06-15",
"DataClasses": ["Email addresses", "Passwords", "Usernames"]
}
]Correlation Confidence Scoring
| Factor | Weight | Description |
|---|---|---|
| Exact email match | 0.95 | Same email found across multiple sources |
| Breach email match | 0.90 | Email found in breach database |
| Exact username match | 0.85 | Same username across multiple platforms |
| Same IP infrastructure | 0.70 | Shared IP address or hosting |
| Domain match | 0.60 | Shared domain registration or hosting |
| Similar username | 0.45 | Partial username overlap with shared metadata |
| Temporal co-registration | 0.40 | Accounts created within similar timeframe |
Cross-source corroboration increases confidence: +0.15 per additional source, capped at 0.95.
Report Output Schema
{
"meta": {
"target": "targetdomain.com",
"generated_at": "2026-03-19T12:00:00+00:00",
"sources_used": ["sherlock", "theHarvester", "spiderfoot", "breach_database"],
"total_findings": 247,
"total_entities": 12
},
"identifiers": {
"usernames": ["johndoe", "jdoe"],
"emails": ["john@targetdomain.com"],
"domains": ["targetdomain.com"],
"ip_addresses": ["203.0.113.10"],
"urls": ["https://github.com/johndoe"]
},
"entities": [
{
"identifier": "johndoe",
"identifier_type": "user",
"confidence": 0.92,
"sources": ["sherlock", "theHarvester", "breach_database"],
"source_count": 3,
"linked_accounts": [
{"source": "sherlock", "platform": "GitHub", "url": "https://github.com/johndoe"}
],
"flags": ["Exposed in 2 breach(es)"],
"risk_level": "high"
}
],
"risk_summary": {
"high_risk": 2,
"medium_risk": 5,
"low_risk": 5
}
}Markdown Report Output
The --markdown flag generates an intelligence profile in Markdown containing:
- Target metadata and source summary
- Risk summary table
- Entity profiles with linked accounts, confidence scores, and risk flags
OSINT Tool Commands (Data Collection)
# Sherlock: enumerate username across platforms
sherlock "targetuser" --output sherlock.csv --csv
# theHarvester: harvest emails and subdomains
theHarvester -d targetdomain.com -b all -f harvester.json
# SpiderFoot: passive scan via REST API
curl -s http://localhost:5001/api/scan/start \
-d "scanname=recon&scantarget=targetdomain.com&usecase=passive"
# HIBP: check email breach exposure
curl -s -H "hibp-api-key: ${HIBP_KEY}" -H "User-Agent: OSINT-Agent" \
"https://haveibeenpwned.com/api/v3/breachedaccount/target@example.com" \
-o breach.jsonReferences
- Sherlock Project: https://github.com/sherlock-project/sherlock
- theHarvester: https://github.com/laramies/theHarvester
- SpiderFoot: https://github.com/smicallef/spiderfoot
- HIBP API: https://haveibeenpwned.com/API/v3
- Maltego: https://www.maltego.com/
- LOLBAS for graph visualization: https://lolbas-project.github.io/
Scripts 1
agent.py15.3 KB
#!/usr/bin/env python3
"""Agent for performing AI-driven OSINT correlation.
Collects and normalizes OSINT data from multiple sources (Sherlock,
theHarvester, SpiderFoot, breach databases), performs cross-source
entity resolution and correlation, and generates unified intelligence
profiles with confidence scoring.
"""
import argparse
import csv
import json
import os
import re
import sys
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
try:
import requests
except ImportError:
requests = None
# Confidence scoring weights for different correlation types
CORRELATION_WEIGHTS = {
"exact_username_match": 0.85,
"exact_email_match": 0.95,
"domain_match": 0.60,
"similar_username": 0.45,
"same_ip_infrastructure": 0.70,
"breach_email_match": 0.90,
"co_registration_temporal": 0.40,
}
def load_sherlock_results(filepath):
"""Load and normalize Sherlock username enumeration results."""
findings = []
if not os.path.isfile(filepath):
return findings
# Sherlock outputs CSV with columns: username, name, url_user, exists, http_status
try:
with open(filepath, "r", errors="replace") as f:
reader = csv.DictReader(f)
for row in reader:
status = row.get("exists", row.get("status", "")).strip().lower()
if status in ("claimed", "true", "yes"):
findings.append({
"source": "sherlock",
"type": "social_profile",
"platform": row.get("name", row.get("platform", "")),
"url": row.get("url_user", row.get("url", "")),
"username": row.get("username", ""),
"collected_at": datetime.now(timezone.utc).isoformat(),
})
except (csv.Error, KeyError):
# Try line-by-line format (Sherlock text output)
with open(filepath, "r", errors="replace") as f:
for line in f:
line = line.strip()
if line.startswith("[+]") or line.startswith("http"):
url_match = re.search(r'(https?://\S+)', line)
if url_match:
url = url_match.group(1)
platform = url.split("/")[2].replace("www.", "").split(".")[0]
findings.append({
"source": "sherlock",
"type": "social_profile",
"platform": platform,
"url": url,
"collected_at": datetime.now(timezone.utc).isoformat(),
})
return findings
def load_harvester_results(filepath):
"""Load and normalize theHarvester results."""
findings = []
if not os.path.isfile(filepath):
return findings
try:
with open(filepath, "r") as f:
data = json.load(f)
except (json.JSONDecodeError, ValueError):
return findings
for email in data.get("emails", []):
findings.append({
"source": "theHarvester",
"type": "email",
"value": email,
"collected_at": datetime.now(timezone.utc).isoformat(),
})
for host in data.get("hosts", []):
findings.append({
"source": "theHarvester",
"type": "hostname",
"value": host,
"collected_at": datetime.now(timezone.utc).isoformat(),
})
for ip in data.get("ips", []):
findings.append({
"source": "theHarvester",
"type": "ip_address",
"value": ip,
"collected_at": datetime.now(timezone.utc).isoformat(),
})
return findings
def load_spiderfoot_results(filepath):
"""Load and normalize SpiderFoot scan results."""
findings = []
if not os.path.isfile(filepath):
return findings
try:
with open(filepath, "r") as f:
data = json.load(f)
except (json.JSONDecodeError, ValueError):
return findings
items = data if isinstance(data, list) else data.get("results", [])
for item in items:
findings.append({
"source": "spiderfoot",
"type": item.get("type", "unknown"),
"value": item.get("data", item.get("value", "")),
"module": item.get("module", ""),
"collected_at": datetime.now(timezone.utc).isoformat(),
})
return findings
def load_breach_results(filepath):
"""Load and normalize breach/HIBP results."""
findings = []
if not os.path.isfile(filepath):
return findings
try:
with open(filepath, "r") as f:
data = json.load(f)
except (json.JSONDecodeError, ValueError):
return findings
breaches = data if isinstance(data, list) else [data]
for breach in breaches:
findings.append({
"source": "breach_database",
"type": "breach_exposure",
"breach_name": breach.get("Name", breach.get("name", "")),
"breach_date": breach.get("BreachDate", breach.get("date", "")),
"data_classes": breach.get("DataClasses", breach.get("data_types", [])),
"collected_at": datetime.now(timezone.utc).isoformat(),
})
return findings
def normalize_all_sources(source_files):
"""Load and combine findings from all OSINT sources."""
all_findings = []
for source_type, filepath in source_files.items():
if not filepath or not os.path.isfile(filepath):
continue
if source_type == "sherlock":
all_findings.extend(load_sherlock_results(filepath))
elif source_type == "harvester":
all_findings.extend(load_harvester_results(filepath))
elif source_type == "spiderfoot":
all_findings.extend(load_spiderfoot_results(filepath))
elif source_type == "breach":
all_findings.extend(load_breach_results(filepath))
elif source_type == "generic":
try:
with open(filepath, "r") as f:
data = json.load(f)
if isinstance(data, list):
all_findings.extend(data)
elif isinstance(data, dict) and "findings" in data:
all_findings.extend(data["findings"])
except (json.JSONDecodeError, ValueError):
pass
return all_findings
def extract_identifiers(findings):
"""Extract unique identifiers (usernames, emails, IPs, domains) from findings."""
identifiers = {
"usernames": set(),
"emails": set(),
"domains": set(),
"ip_addresses": set(),
"urls": set(),
}
for f in findings:
ftype = f.get("type", "")
value = f.get("value", "")
username = f.get("username", "")
url = f.get("url", "")
if username:
identifiers["usernames"].add(username.lower())
if url:
identifiers["urls"].add(url)
if ftype == "email" and value:
identifiers["emails"].add(value.lower())
domain = value.split("@")[-1] if "@" in value else ""
if domain:
identifiers["domains"].add(domain.lower())
elif ftype == "hostname" and value:
identifiers["domains"].add(value.lower())
elif ftype == "ip_address" and value:
identifiers["ip_addresses"].add(value)
elif ftype == "social_profile":
platform_user = f.get("username", "")
if platform_user:
identifiers["usernames"].add(platform_user.lower())
return {k: sorted(v) for k, v in identifiers.items()}
def correlate_findings(findings):
"""Perform cross-source correlation to identify linked entities."""
entities = []
source_map = defaultdict(list)
# Group findings by identifiers
for f in findings:
username = f.get("username", "").lower()
email = f.get("value", "").lower() if f.get("type") == "email" else ""
url = f.get("url", "")
if username:
source_map[f"user:{username}"].append(f)
if email:
source_map[f"email:{email}"].append(f)
# Also link by email username part
email_user = email.split("@")[0] if "@" in email else ""
if email_user:
source_map[f"user:{email_user}"].append(f)
# Build entities from correlated groups
processed = set()
for key, group_findings in source_map.items():
if key in processed or len(group_findings) < 1:
continue
processed.add(key)
sources_seen = set(f.get("source", "") for f in group_findings)
platforms = [f.get("platform", "") for f in group_findings if f.get("platform")]
urls = [f.get("url", "") for f in group_findings if f.get("url")]
# Calculate confidence based on cross-source corroboration
confidence = 0.5
if len(sources_seen) > 1:
confidence = min(0.95, 0.5 + 0.15 * len(sources_seen))
if len(platforms) > 3:
confidence = min(0.98, confidence + 0.1)
identifier = key.split(":", 1)[1] if ":" in key else key
entity = {
"identifier": identifier,
"identifier_type": key.split(":")[0] if ":" in key else "unknown",
"confidence": round(confidence, 2),
"sources": sorted(sources_seen),
"source_count": len(sources_seen),
"linked_accounts": [],
"flags": [],
}
for f in group_findings:
link = {
"source": f.get("source", ""),
"platform": f.get("platform", ""),
"url": f.get("url", ""),
"type": f.get("type", ""),
"value": f.get("value", f.get("username", "")),
}
entity["linked_accounts"].append(link)
# Risk assessment
breach_findings = [f for f in group_findings if f.get("type") == "breach_exposure"]
if breach_findings:
entity["flags"].append(
f"Exposed in {len(breach_findings)} breach(es)"
)
entity["risk_level"] = "high"
elif len(sources_seen) >= 3:
entity["risk_level"] = "medium"
else:
entity["risk_level"] = "low"
entities.append(entity)
# Sort by confidence descending
entities.sort(key=lambda e: e["confidence"], reverse=True)
return entities
def generate_report(findings, entities, target="unknown"):
"""Generate structured OSINT correlation report."""
sources_used = sorted(set(f.get("source", "") for f in findings))
identifier_summary = extract_identifiers(findings)
report = {
"meta": {
"target": target,
"generated_at": datetime.now(timezone.utc).isoformat(),
"sources_used": sources_used,
"total_findings": len(findings),
"total_entities": len(entities),
},
"identifiers": identifier_summary,
"entities": entities,
"risk_summary": {
"high_risk": sum(1 for e in entities if e.get("risk_level") == "high"),
"medium_risk": sum(1 for e in entities if e.get("risk_level") == "medium"),
"low_risk": sum(1 for e in entities if e.get("risk_level") == "low"),
},
}
return report
def generate_markdown_report(report, output_path):
"""Generate a Markdown intelligence profile from the report."""
md = "# OSINT Correlation Report\n\n"
meta = report.get("meta", {})
md += f"**Target:** {meta.get('target', 'N/A')}\n"
md += f"**Generated:** {meta.get('generated_at', '')}\n"
md += f"**Sources:** {', '.join(meta.get('sources_used', []))}\n"
md += f"**Total Findings:** {meta.get('total_findings', 0)}\n"
md += f"**Entities Identified:** {meta.get('total_entities', 0)}\n\n"
risk = report.get("risk_summary", {})
md += "## Risk Summary\n\n"
md += f"| Risk Level | Count |\n|-----------|-------|\n"
md += f"| High | {risk.get('high_risk', 0)} |\n"
md += f"| Medium | {risk.get('medium_risk', 0)} |\n"
md += f"| Low | {risk.get('low_risk', 0)} |\n\n"
md += "## Entity Profiles\n\n"
for entity in report.get("entities", [])[:50]:
eid = entity.get("identifier", "Unknown")
conf = entity.get("confidence", 0)
risk_level = entity.get("risk_level", "N/A")
md += f"### {eid} (Confidence: {conf:.0%}, Risk: {risk_level})\n\n"
md += "| Source | Platform | Value |\n|--------|----------|-------|\n"
for link in entity.get("linked_accounts", []):
md += (f"| {link.get('source', '')} | {link.get('platform', '')} "
f"| {link.get('value', '')} |\n")
for flag in entity.get("flags", []):
md += f"\n- WARNING: {flag}\n"
md += "\n"
with open(output_path, "w") as f:
f.write(md)
print(f"[*] Markdown report saved to {output_path}")
def main():
parser = argparse.ArgumentParser(
description="AI-Driven OSINT Correlation Agent"
)
parser.add_argument("--target", default="unknown",
help="Target identifier (domain, username, etc.)")
parser.add_argument("--sherlock", help="Sherlock results file (CSV or text)")
parser.add_argument("--harvester", help="theHarvester results file (JSON)")
parser.add_argument("--spiderfoot", help="SpiderFoot results file (JSON)")
parser.add_argument("--breach", help="Breach/HIBP results file (JSON)")
parser.add_argument("--generic", help="Generic normalized findings JSON")
parser.add_argument("--normalize-only", action="store_true",
help="Only normalize data, skip correlation")
parser.add_argument("--markdown", help="Output Markdown report path")
parser.add_argument("--output", "-o", help="Output JSON report path")
args = parser.parse_args()
print("[*] AI-Driven OSINT Correlation Agent")
source_files = {
"sherlock": args.sherlock,
"harvester": args.harvester,
"spiderfoot": args.spiderfoot,
"breach": args.breach,
"generic": args.generic,
}
active_sources = {k: v for k, v in source_files.items() if v}
if not active_sources:
parser.print_help()
print("\n[!] Provide at least one data source file.")
return
print(f"[*] Loading data from {len(active_sources)} source(s): "
f"{', '.join(active_sources.keys())}")
findings = normalize_all_sources(source_files)
print(f"[*] Normalized {len(findings)} findings")
if args.normalize_only:
output = json.dumps(findings, indent=2)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"[*] Normalized findings saved to {args.output}")
else:
print(output)
return
print("[*] Performing cross-source correlation...")
entities = correlate_findings(findings)
print(f"[*] Identified {len(entities)} entities")
report = generate_report(findings, entities, target=args.target)
output = json.dumps(report, indent=2)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"[*] JSON report saved to {args.output}")
else:
print(output)
if args.markdown:
generate_markdown_report(report, args.markdown)
if __name__ == "__main__":
main()