Claude 24f816b6a3
Consolidate 22 sibling repos into layered organism structure
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):

- brain/        LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
                dispatch, A51 channels, and the OSINT cluster
- knowledge/    LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
                MITRE ATT&CK data
- reference/    defensive threat-reference (C3, shhbruh doc) + AdaYaml parser

License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.

Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.

https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
2026-06-10 06:53:01 +00:00

169 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""Agent for subdomain enumeration using subfinder and httpx.
Runs ProjectDiscovery subfinder for passive subdomain discovery,
validates live hosts with httpx, resolves DNS, and generates
an attack surface report.
"""
import subprocess
import json
import sys
from datetime import datetime
from pathlib import Path
from collections import defaultdict
class SubdomainEnumerationAgent:
"""Enumerates subdomains using subfinder and validates with httpx."""
def __init__(self, domain, output_dir="./recon"):
self.domain = domain
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.subdomains = []
self.live_hosts = []
def run_subfinder(self, all_sources=False, recursive=False):
"""Run subfinder for passive subdomain enumeration."""
out_file = self.output_dir / f"{self.domain}_subdomains.txt"
cmd = ["subfinder", "-d", self.domain, "-o", str(out_file), "-silent"]
if all_sources:
cmd.append("-all")
if recursive:
cmd.append("-recursive")
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if out_file.exists():
self.subdomains = [
line.strip() for line in out_file.read_text().splitlines()
if line.strip()
]
return {"count": len(self.subdomains),
"output_file": str(out_file),
"stderr": result.stderr[:200] if result.stderr else ""}
except FileNotFoundError:
return {"error": "subfinder not installed. Install: go install -v "
"github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest"}
except subprocess.TimeoutExpired:
return {"error": "subfinder timed out after 300s"}
def run_subfinder_json(self):
"""Run subfinder with JSON output for source tracking."""
cmd = ["subfinder", "-d", self.domain, "-oJ", "-silent"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
entries = []
for line in result.stdout.strip().splitlines():
try:
entry = json.loads(line)
entries.append(entry)
host = entry.get("host", "")
if host and host not in self.subdomains:
self.subdomains.append(host)
except json.JSONDecodeError:
continue
return entries
except (FileNotFoundError, subprocess.TimeoutExpired):
return []
def validate_with_httpx(self, ports="80,443"):
"""Validate discovered subdomains with httpx probe."""
if not self.subdomains:
return []
input_file = self.output_dir / f"{self.domain}_to_probe.txt"
input_file.write_text("\n".join(self.subdomains))
out_file = self.output_dir / f"{self.domain}_live.json"
cmd = ["httpx", "-l", str(input_file), "-ports", ports,
"-status-code", "-title", "-json", "-o", str(out_file), "-silent"]
try:
subprocess.run(cmd, capture_output=True, text=True, timeout=600)
if out_file.exists():
for line in out_file.read_text().splitlines():
try:
self.live_hosts.append(json.loads(line))
except json.JSONDecodeError:
continue
return self.live_hosts
except (FileNotFoundError, subprocess.TimeoutExpired):
return []
def resolve_dns(self):
"""Resolve subdomains to IP addresses using dnsx."""
if not self.subdomains:
return []
input_file = self.output_dir / f"{self.domain}_to_resolve.txt"
input_file.write_text("\n".join(self.subdomains))
cmd = ["dnsx", "-l", str(input_file), "-a", "-resp", "-json", "-silent"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
records = []
for line in result.stdout.strip().splitlines():
try:
records.append(json.loads(line))
except json.JSONDecodeError:
continue
return records
except (FileNotFoundError, subprocess.TimeoutExpired):
return []
def detect_takeover_candidates(self):
"""Identify subdomains potentially vulnerable to takeover."""
candidates = []
cloud_cnames = ["amazonaws.com", "azurewebsites.net", "cloudfront.net",
"herokuapp.com", "github.io", "s3.amazonaws.com",
"blob.core.windows.net", "cloudapp.azure.com"]
dns_records = self.resolve_dns()
for record in dns_records:
cname = record.get("cname", "")
if any(cloud in cname for cloud in cloud_cnames):
candidates.append({
"subdomain": record.get("host", ""),
"cname": cname,
"risk": "Potential subdomain takeover"
})
return candidates
def generate_report(self):
"""Generate attack surface enumeration report."""
status_dist = defaultdict(int)
for host in self.live_hosts:
code = host.get("status_code", 0)
status_dist[str(code)] += 1
report = {
"domain": self.domain,
"report_date": datetime.utcnow().isoformat(),
"total_subdomains": len(self.subdomains),
"live_hosts": len(self.live_hosts),
"status_distribution": dict(status_dist),
"subdomains": self.subdomains[:100],
"live_host_details": self.live_hosts[:50],
"takeover_candidates": self.detect_takeover_candidates(),
}
report_path = self.output_dir / f"{self.domain}_report.json"
with open(report_path, "w") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
return report
def main():
if len(sys.argv) < 2:
print("Usage: agent.py <domain> [output_dir]")
sys.exit(1)
domain = sys.argv[1]
output_dir = sys.argv[2] if len(sys.argv) > 2 else "./recon"
agent = SubdomainEnumerationAgent(domain, output_dir)
agent.run_subfinder(all_sources=True)
agent.validate_with_httpx()
agent.generate_report()
if __name__ == "__main__":
main()