Spaces:
Sleeping
Sleeping
Upload 18 files
Browse files- Version_4/New Text Document.txt +105 -0
- Version_4/__pycache__/config.cpython-313.pyc +0 -0
- Version_4/app.py +88 -0
- Version_4/build_dataset.py +111 -0
- Version_4/checkpoints/phishing_gnn_master.pt +3 -0
- Version_4/config.py +26 -0
- Version_4/models/__init__.py +0 -0
- Version_4/models/__pycache__/__init__.cpython-313.pyc +0 -0
- Version_4/models/__pycache__/gnn.cpython-313.pyc +0 -0
- Version_4/models/gnn.py +72 -0
- Version_4/network_telemetry.json +0 -0
- Version_4/pipeline/__init__.py +0 -0
- Version_4/pipeline/__pycache__/__init__.cpython-313.pyc +0 -0
- Version_4/pipeline/__pycache__/graph_engine.cpython-313.pyc +0 -0
- Version_4/pipeline/graph_engine.py +47 -0
- Version_4/requirements.txt +5 -0
- Version_4/simulate_warfare.py +70 -0
- Version_4/train.py +71 -0
Version_4/New Text Document.txt
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import csv
|
| 2 |
+
import json
|
| 3 |
+
import socket
|
| 4 |
+
import random
|
| 5 |
+
from urllib.parse import urlparse
|
| 6 |
+
|
| 7 |
+
# ==========================================
|
| 8 |
+
# DEFENDER V5 - BALANCED CSV TO JSON PIPELINE
|
| 9 |
+
# ==========================================
|
| 10 |
+
|
| 11 |
+
def extract_domain(raw_url):
|
| 12 |
+
"""Safely extracts the domain name from a raw URL string."""
|
| 13 |
+
url = raw_url.strip()
|
| 14 |
+
if not url.startswith('http'):
|
| 15 |
+
url = 'http://' + url
|
| 16 |
+
try:
|
| 17 |
+
parsed = urlparse(url)
|
| 18 |
+
domain = parsed.netloc.split(':')[0]
|
| 19 |
+
return domain if domain else None
|
| 20 |
+
except:
|
| 21 |
+
return None
|
| 22 |
+
|
| 23 |
+
def resolve_domain(domain):
|
| 24 |
+
"""Attempts to resolve domain to an active IP address."""
|
| 25 |
+
try:
|
| 26 |
+
return socket.gethostbyname(domain)
|
| 27 |
+
except socket.gaierror:
|
| 28 |
+
return None
|
| 29 |
+
|
| 30 |
+
def build_dataset_from_csv():
|
| 31 |
+
csv_filename = "malicious_urls.csv" # <-- Change this if your file name is different
|
| 32 |
+
output_file = "network_telemetry.json"
|
| 33 |
+
|
| 34 |
+
print(f"[*] Scanning {csv_filename} to extract 2500 'good' and 2500 'bad' URLs...")
|
| 35 |
+
|
| 36 |
+
telemetry_data = []
|
| 37 |
+
bad_count = 0
|
| 38 |
+
good_count = 0
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
with open(csv_filename, mode='r', encoding='utf-8') as file:
|
| 42 |
+
reader = csv.DictReader(file)
|
| 43 |
+
|
| 44 |
+
for row in reader:
|
| 45 |
+
# Stop parsing immediately if both limits are reached
|
| 46 |
+
if bad_count >= 2500 and good_count >= 2500:
|
| 47 |
+
break
|
| 48 |
+
|
| 49 |
+
raw_url = row.get('URL', '')
|
| 50 |
+
label_str = row.get('Label', '').strip().lower()
|
| 51 |
+
|
| 52 |
+
if not raw_url:
|
| 53 |
+
continue
|
| 54 |
+
|
| 55 |
+
# Check label and verify if we still need more samples for that category
|
| 56 |
+
if label_str == 'bad' and bad_count < 2500:
|
| 57 |
+
is_malicious = 1.0
|
| 58 |
+
elif label_str == 'good' and good_count < 2500:
|
| 59 |
+
is_malicious = 0.0
|
| 60 |
+
else:
|
| 61 |
+
continue # Skip row if label doesn't match or target is already full
|
| 62 |
+
|
| 63 |
+
domain = extract_domain(raw_url)
|
| 64 |
+
if not domain:
|
| 65 |
+
continue
|
| 66 |
+
|
| 67 |
+
# DNS Lookup (ensures high quality topological edges for the Graph)
|
| 68 |
+
ip = resolve_domain(domain)
|
| 69 |
+
|
| 70 |
+
if ip:
|
| 71 |
+
telemetry_data.append({
|
| 72 |
+
"ip": ip,
|
| 73 |
+
"domain": domain,
|
| 74 |
+
"asn": random.randint(40000, 60000) if is_malicious == 1.0 else 15169, # Placeholder ASNs
|
| 75 |
+
"is_malicious": is_malicious
|
| 76 |
+
})
|
| 77 |
+
|
| 78 |
+
if is_malicious == 1.0:
|
| 79 |
+
bad_count += 1
|
| 80 |
+
else:
|
| 81 |
+
good_count += 1
|
| 82 |
+
|
| 83 |
+
# Progress counter (DNS resolution takes a moment)
|
| 84 |
+
total_processed = bad_count + good_count
|
| 85 |
+
if total_processed % 100 == 0:
|
| 86 |
+
print(f" -> Processed {total_processed} active rows... (Good: {good_count}/2500 | Bad: {bad_count}/2500)")
|
| 87 |
+
|
| 88 |
+
print(f"\n[+] Extraction Complete!")
|
| 89 |
+
print(f" - Legitimate (Good) URLs mapped: {good_count}")
|
| 90 |
+
print(f" - Malicious (Bad) URLs mapped: {bad_count}")
|
| 91 |
+
|
| 92 |
+
# Shuffle the list so the model doesn't process all bad URLs first, then all good URLs
|
| 93 |
+
random.shuffle(telemetry_data)
|
| 94 |
+
|
| 95 |
+
with open(output_file, "w") as f:
|
| 96 |
+
json.dump(telemetry_data, f, indent=4)
|
| 97 |
+
|
| 98 |
+
print(f"[+] Balanced dataset successfully saved to: {output_file}")
|
| 99 |
+
print("[*] Ready to train! Run: python train.py")
|
| 100 |
+
|
| 101 |
+
except FileNotFoundError:
|
| 102 |
+
print(f"[-] Error: Could not find '{csv_filename}' in this directory.")
|
| 103 |
+
|
| 104 |
+
if __name__ == "__main__":
|
| 105 |
+
build_dataset_from_csv()
|
Version_4/__pycache__/config.cpython-313.pyc
ADDED
|
Binary file (916 Bytes). View file
|
|
|
Version_4/app.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from pydantic import BaseModel
|
| 3 |
+
import torch
|
| 4 |
+
import os
|
| 5 |
+
import socket
|
| 6 |
+
from urllib.parse import urlparse
|
| 7 |
+
from models.gnn import PhishingGNN_Model
|
| 8 |
+
from pipeline.graph_engine import TopologicalGraphEngine
|
| 9 |
+
from config import *
|
| 10 |
+
import uvicorn
|
| 11 |
+
|
| 12 |
+
app = FastAPI(title="Defender V5 Sovereign Threat Analysis API")
|
| 13 |
+
|
| 14 |
+
# Define node dimension configurations
|
| 15 |
+
in_channels_dict = {
|
| 16 |
+
'ip': 16,
|
| 17 |
+
'domain': 32,
|
| 18 |
+
'asn': 8,
|
| 19 |
+
'cert': 16
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
# Load model directly into state memory matching training dimension maps
|
| 23 |
+
model = PhishingGNN_Model(
|
| 24 |
+
metadata=GRAPH_METADATA,
|
| 25 |
+
in_channels_dict=in_channels_dict,
|
| 26 |
+
hidden_channels=HIDDEN_CHANNELS,
|
| 27 |
+
num_heads=NUM_HEADS,
|
| 28 |
+
num_layers=NUM_LAYERS,
|
| 29 |
+
dropout_rate=0.0
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
if os.path.exists(MODEL_SAVE_PATH):
|
| 33 |
+
model.load_state_dict(torch.load(MODEL_SAVE_PATH, map_location='cpu', weights_only=True))
|
| 34 |
+
model.eval()
|
| 35 |
+
|
| 36 |
+
# Payload model expects a URL
|
| 37 |
+
class URLPayload(BaseModel):
|
| 38 |
+
url: str
|
| 39 |
+
|
| 40 |
+
@app.post("/analyze")
|
| 41 |
+
async def analyze_url(payload: URLPayload):
|
| 42 |
+
try:
|
| 43 |
+
# 1. Parse the URL to extract the domain
|
| 44 |
+
parsed_url = urlparse(payload.url)
|
| 45 |
+
domain = parsed_url.netloc or parsed_url.path.split('/')[0]
|
| 46 |
+
|
| 47 |
+
if ':' in domain:
|
| 48 |
+
domain = domain.split(':')[0]
|
| 49 |
+
|
| 50 |
+
if not domain:
|
| 51 |
+
raise HTTPException(status_code=400, detail="Invalid URL format.")
|
| 52 |
+
|
| 53 |
+
# 2. Resolve the domain to an IP address
|
| 54 |
+
try:
|
| 55 |
+
ip = socket.gethostbyname(domain)
|
| 56 |
+
except socket.gaierror:
|
| 57 |
+
raise HTTPException(status_code=400, detail=f"DNS Resolution failed for domain: {domain}")
|
| 58 |
+
|
| 59 |
+
# 3. Build telemetry dictionary
|
| 60 |
+
telemetry_log = {
|
| 61 |
+
"ip": ip,
|
| 62 |
+
"domain": domain,
|
| 63 |
+
"asn": None
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
# 4. Pass through Graph Engine
|
| 67 |
+
engine = TopologicalGraphEngine()
|
| 68 |
+
x_dict, edge_index_dict = engine.extract_and_build([telemetry_log])
|
| 69 |
+
|
| 70 |
+
with torch.no_grad():
|
| 71 |
+
raw_scores = model(x_dict, edge_index_dict)
|
| 72 |
+
threat_probability = float(raw_scores.max().item())
|
| 73 |
+
|
| 74 |
+
return {
|
| 75 |
+
"input_url": payload.url,
|
| 76 |
+
"resolved_domain": domain,
|
| 77 |
+
"resolved_ip": ip,
|
| 78 |
+
"structural_anomaly_score": round(threat_probability, 5),
|
| 79 |
+
"remediation_verdict": "ISOLATE_ROUTING" if threat_probability > 0.70 else "ALLOW"
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
except HTTPException:
|
| 83 |
+
raise
|
| 84 |
+
except Exception as e:
|
| 85 |
+
raise HTTPException(status_code=500, detail=f"Graph Engine Exception: {str(e)}")
|
| 86 |
+
|
| 87 |
+
if __name__ == "__main__":
|
| 88 |
+
uvicorn.run(app, host="127.0.0.1", port=8005)
|
Version_4/build_dataset.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import csv
|
| 2 |
+
import json
|
| 3 |
+
import socket
|
| 4 |
+
import random
|
| 5 |
+
from urllib.parse import urlparse
|
| 6 |
+
|
| 7 |
+
# ==========================================
|
| 8 |
+
# DEFENDER V5 - CSV TO JSON PIPELINE (BUGFIXED)
|
| 9 |
+
# ==========================================
|
| 10 |
+
|
| 11 |
+
def extract_domain(raw_url):
|
| 12 |
+
"""Safely extracts the domain name from a raw URL string."""
|
| 13 |
+
url = raw_url.strip()
|
| 14 |
+
if not url.startswith('http'):
|
| 15 |
+
url = 'http://' + url
|
| 16 |
+
try:
|
| 17 |
+
parsed = urlparse(url)
|
| 18 |
+
domain = parsed.netloc.split(':')[0]
|
| 19 |
+
return domain if domain else None
|
| 20 |
+
except:
|
| 21 |
+
return None
|
| 22 |
+
|
| 23 |
+
def resolve_domain(domain):
|
| 24 |
+
"""Attempts to resolve domain to an active IP address. Fails gracefully if offline or corrupted."""
|
| 25 |
+
try:
|
| 26 |
+
return socket.gethostbyname(domain)
|
| 27 |
+
except (socket.gaierror, UnicodeEncodeError):
|
| 28 |
+
# BUGFIX: Added UnicodeEncodeError to prevent crashes from hidden junk characters like \x87
|
| 29 |
+
return None
|
| 30 |
+
|
| 31 |
+
def build_dataset_from_csv():
|
| 32 |
+
csv_filename = "malicious_urls.csv"
|
| 33 |
+
output_file = "network_telemetry.json"
|
| 34 |
+
|
| 35 |
+
print(f"[*] Scanning {csv_filename} to extract 2500 'good' and 2500 'bad' URLs...")
|
| 36 |
+
|
| 37 |
+
telemetry_data = []
|
| 38 |
+
bad_count = 0
|
| 39 |
+
good_count = 0
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
with open(csv_filename, mode='r', encoding='utf-8', errors='ignore') as file:
|
| 43 |
+
# Added errors='ignore' above to strip out any raw structural file encoding issues
|
| 44 |
+
reader = csv.DictReader(file)
|
| 45 |
+
|
| 46 |
+
for row in reader:
|
| 47 |
+
# Stop parsing immediately if both limits are reached
|
| 48 |
+
if bad_count >= 2500 and good_count >= 2500:
|
| 49 |
+
break
|
| 50 |
+
|
| 51 |
+
raw_url = row.get('URL', '')
|
| 52 |
+
label_str = row.get('Label', '').strip().lower()
|
| 53 |
+
|
| 54 |
+
if not raw_url:
|
| 55 |
+
continue
|
| 56 |
+
|
| 57 |
+
# Check label and verify if we still need more samples for that category
|
| 58 |
+
if label_str == 'bad' and bad_count < 2500:
|
| 59 |
+
is_malicious = 1.0
|
| 60 |
+
elif label_str == 'good' and good_count < 2500:
|
| 61 |
+
is_malicious = 0.0
|
| 62 |
+
else:
|
| 63 |
+
continue # Skip row if label doesn't match or target category is already full
|
| 64 |
+
|
| 65 |
+
domain = extract_domain(raw_url)
|
| 66 |
+
if not domain:
|
| 67 |
+
continue
|
| 68 |
+
|
| 69 |
+
# DNS Lookup
|
| 70 |
+
ip = resolve_domain(domain)
|
| 71 |
+
|
| 72 |
+
if ip:
|
| 73 |
+
telemetry_data.append({
|
| 74 |
+
"ip": ip,
|
| 75 |
+
"domain": domain,
|
| 76 |
+
"asn": random.randint(40000, 60000) if is_malicious == 1.0 else 15169,
|
| 77 |
+
"is_malicious": is_malicious
|
| 78 |
+
})
|
| 79 |
+
|
| 80 |
+
if is_malicious == 1.0:
|
| 81 |
+
bad_count += 1
|
| 82 |
+
else:
|
| 83 |
+
good_count += 1
|
| 84 |
+
|
| 85 |
+
# Progress counter
|
| 86 |
+
total_processed = bad_count + good_count
|
| 87 |
+
if total_processed % 100 == 0 or (bad_count == 2500 and total_processed % 10 == 0):
|
| 88 |
+
print(f" -> Processed {total_processed} active rows... (Good: {good_count}/2500 | Bad: {bad_count}/2500)")
|
| 89 |
+
|
| 90 |
+
print(f"\n[+] Extraction Complete!")
|
| 91 |
+
print(f" - Legitimate (Good) URLs mapped: {good_count}")
|
| 92 |
+
print(f" - Malicious (Bad) URLs mapped: {bad_count}")
|
| 93 |
+
|
| 94 |
+
if len(telemetry_data) == 0:
|
| 95 |
+
print("[-] Error: No active domains were resolved. Check internet connection or CSV headers.")
|
| 96 |
+
return
|
| 97 |
+
|
| 98 |
+
# Shuffle the list so the model mixes good and bad data
|
| 99 |
+
random.shuffle(telemetry_data)
|
| 100 |
+
|
| 101 |
+
with open(output_file, "w") as f:
|
| 102 |
+
json.dump(telemetry_data, f, indent=4)
|
| 103 |
+
|
| 104 |
+
print(f"[+] Balanced dataset successfully saved to: {output_file}")
|
| 105 |
+
print("[*] Ready to train! Run: python train.py")
|
| 106 |
+
|
| 107 |
+
except FileNotFoundError:
|
| 108 |
+
print(f"[-] Error: Could not find '{csv_filename}' in this directory.")
|
| 109 |
+
|
| 110 |
+
if __name__ == "__main__":
|
| 111 |
+
build_dataset_from_csv()
|
Version_4/checkpoints/phishing_gnn_master.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a9d89a611d4655e4d430f60432caa2b7d600587dbf385dd14fdfa992412d11cb
|
| 3 |
+
size 4696055
|
Version_4/config.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
# ==========================================
|
| 4 |
+
# DEFENDER V5 - GLOBAL CONFIGURATION
|
| 5 |
+
# ==========================================
|
| 6 |
+
|
| 7 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 8 |
+
CHECKPOINT_DIR = os.path.join(BASE_DIR, "checkpoints")
|
| 9 |
+
MODEL_SAVE_PATH = os.path.join(CHECKPOINT_DIR, "phishing_gnn_master.pt")
|
| 10 |
+
|
| 11 |
+
# Topology Definition
|
| 12 |
+
NODE_TYPES = ['ip', 'domain', 'asn', 'cert']
|
| 13 |
+
EDGE_TYPES = [
|
| 14 |
+
('domain', 'resolves_to', 'ip'),
|
| 15 |
+
('ip', 'hosted_on', 'asn'),
|
| 16 |
+
('domain', 'secured_by', 'cert'),
|
| 17 |
+
('cert', 'issued_to', 'ip'),
|
| 18 |
+
('domain', 'redirects_to', 'domain')
|
| 19 |
+
]
|
| 20 |
+
GRAPH_METADATA = (NODE_TYPES, EDGE_TYPES)
|
| 21 |
+
|
| 22 |
+
# Model Deep-Stack Hyperparameters
|
| 23 |
+
HIDDEN_CHANNELS = 128
|
| 24 |
+
NUM_HEADS = 8
|
| 25 |
+
NUM_LAYERS = 4
|
| 26 |
+
DROPOUT_RATE = 0.15
|
Version_4/models/__init__.py
ADDED
|
File without changes
|
Version_4/models/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (144 Bytes). View file
|
|
|
Version_4/models/__pycache__/gnn.cpython-313.pyc
ADDED
|
Binary file (4.74 kB). View file
|
|
|
Version_4/models/gnn.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
from torch.nn import ModuleDict, ModuleList, Linear, LayerNorm, Dropout
|
| 5 |
+
from torch_geometric.nn import HGTConv
|
| 6 |
+
from typing import Dict, Tuple, List
|
| 7 |
+
|
| 8 |
+
class PhishingGNN_Model(torch.nn.Module):
|
| 9 |
+
"""
|
| 10 |
+
Standalone Heterogeneous Graph Transformer (HGT) for pure topological threat analysis.
|
| 11 |
+
Processes multi-relational network metadata independently of lexical features.
|
| 12 |
+
"""
|
| 13 |
+
def __init__(
|
| 14 |
+
self,
|
| 15 |
+
metadata: Tuple[List[str], List[Tuple[str, str, str]]],
|
| 16 |
+
in_channels_dict: Dict[str, int], # Maps node types to their specific input sizes
|
| 17 |
+
hidden_channels: int,
|
| 18 |
+
num_heads: int,
|
| 19 |
+
num_layers: int,
|
| 20 |
+
dropout_rate: float
|
| 21 |
+
):
|
| 22 |
+
super().__init__()
|
| 23 |
+
self.node_types, self.edge_types = metadata
|
| 24 |
+
self.num_layers = num_layers
|
| 25 |
+
|
| 26 |
+
# Multi-type input embedding alignment using explicit feature dimensions
|
| 27 |
+
self.inputs_proj = ModuleDict({
|
| 28 |
+
node_type: Linear(in_channels_dict[node_type], hidden_channels)
|
| 29 |
+
for node_type in self.node_types
|
| 30 |
+
})
|
| 31 |
+
|
| 32 |
+
self.convs = ModuleList()
|
| 33 |
+
self.layer_norms = ModuleList()
|
| 34 |
+
self.dropouts = ModuleList()
|
| 35 |
+
|
| 36 |
+
for _ in range(num_layers):
|
| 37 |
+
# No 'group' parameter here to ensure PyG compatibility
|
| 38 |
+
self.convs.append(HGTConv(hidden_channels, hidden_channels, metadata, num_heads))
|
| 39 |
+
self.layer_norms.append(ModuleDict({
|
| 40 |
+
node_type: LayerNorm(hidden_channels) for node_type in self.node_types
|
| 41 |
+
}))
|
| 42 |
+
self.dropouts.append(Dropout(p=dropout_rate))
|
| 43 |
+
|
| 44 |
+
# Advanced classification projection head for targeting IP anomalies
|
| 45 |
+
self.classifier = torch.nn.Sequential(
|
| 46 |
+
Linear(hidden_channels, hidden_channels // 2),
|
| 47 |
+
torch.nn.GELU(),
|
| 48 |
+
Dropout(p=dropout_rate),
|
| 49 |
+
Linear(hidden_channels // 2, 1)
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
def forward(self, x_dict: Dict[str, torch.Tensor], edge_index_dict: Dict[Tuple[str, str, str], torch.Tensor]) -> torch.Tensor:
|
| 53 |
+
# Align input spaces
|
| 54 |
+
h_dict = {node_type: F.gelu(self.inputs_proj[node_type](x)) for node_type, x in x_dict.items()}
|
| 55 |
+
|
| 56 |
+
# Deep graph convolutions with normalization and skip connections
|
| 57 |
+
for i in range(self.num_layers):
|
| 58 |
+
h_residual_dict = {node_type: h.clone() for node_type, h in h_dict.items()}
|
| 59 |
+
h_dict = self.convs[i](h_dict, edge_index_dict)
|
| 60 |
+
|
| 61 |
+
for node_type in self.node_types:
|
| 62 |
+
h = self.dropouts[i](h_dict[node_type])
|
| 63 |
+
h = h + h_residual_dict[node_type]
|
| 64 |
+
h_dict[node_type] = self.layer_norms[i][node_type](h)
|
| 65 |
+
|
| 66 |
+
# Isolate the 'ip' node matrix for final threat probability generation
|
| 67 |
+
return torch.sigmoid(self.classifier(h_dict['ip']))
|
| 68 |
+
|
| 69 |
+
def safe_save(self, path: str):
|
| 70 |
+
"""Ensures parent directories exist before execution to prevent saving crashes."""
|
| 71 |
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
| 72 |
+
torch.save(self.state_dict(), path)
|
Version_4/network_telemetry.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
Version_4/pipeline/__init__.py
ADDED
|
File without changes
|
Version_4/pipeline/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (146 Bytes). View file
|
|
|
Version_4/pipeline/__pycache__/graph_engine.cpython-313.pyc
ADDED
|
Binary file (3.85 kB). View file
|
|
|
Version_4/pipeline/graph_engine.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from typing import Dict, List, Tuple
|
| 3 |
+
|
| 4 |
+
class TopologicalGraphEngine:
|
| 5 |
+
"""Translates incoming raw telemetry logs into PyTorch Geometric HeteroData formats."""
|
| 6 |
+
def __init__(self):
|
| 7 |
+
self.ip_map, self.domain_map, self.asn_map = {}, {}, {}
|
| 8 |
+
|
| 9 |
+
def extract_and_build(self, telemetries: List[dict]) -> Tuple[Dict[str, torch.Tensor], Dict[Tuple, torch.Tensor]]:
|
| 10 |
+
# Map dynamic components to index pointers
|
| 11 |
+
for log in telemetries:
|
| 12 |
+
if log.get('ip') and log['ip'] not in self.ip_map:
|
| 13 |
+
self.ip_map[log['ip']] = len(self.ip_map)
|
| 14 |
+
if log.get('domain') and log['domain'] not in self.domain_map:
|
| 15 |
+
self.domain_map[log['domain']] = len(self.domain_map)
|
| 16 |
+
if log.get('asn') and log['asn'] not in self.asn_map:
|
| 17 |
+
self.asn_map[log['asn']] = len(self.asn_map)
|
| 18 |
+
|
| 19 |
+
# High-dimensional hidden state initializers (using structural placeholders)
|
| 20 |
+
# Note: These sizes must match in_channels_dict in app.py and train.py
|
| 21 |
+
x_dict = {
|
| 22 |
+
'ip': torch.randn((max(1, len(self.ip_map)), 16)),
|
| 23 |
+
'domain': torch.randn((max(1, len(self.domain_map)), 32)),
|
| 24 |
+
'asn': torch.randn((max(1, len(self.asn_map)), 8)),
|
| 25 |
+
'cert': torch.randn((1, 16))
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
domain_to_ip = [[], []]
|
| 29 |
+
ip_to_asn = [[], []]
|
| 30 |
+
|
| 31 |
+
for log in telemetries:
|
| 32 |
+
if log.get('domain') and log.get('ip'):
|
| 33 |
+
domain_to_ip[0].append(self.domain_map[log['domain']])
|
| 34 |
+
domain_to_ip[1].append(self.ip_map[log['ip']])
|
| 35 |
+
if log.get('ip') and log.get('asn'):
|
| 36 |
+
ip_to_asn[0].append(self.ip_map[log['ip']])
|
| 37 |
+
ip_to_asn[1].append(self.asn_map[log['asn']])
|
| 38 |
+
|
| 39 |
+
edge_index_dict = {
|
| 40 |
+
('domain', 'resolves_to', 'ip'): torch.tensor(domain_to_ip, dtype=torch.long),
|
| 41 |
+
('ip', 'hosted_on', 'asn'): torch.tensor(ip_to_asn, dtype=torch.long),
|
| 42 |
+
('domain', 'secured_by', 'cert'): torch.empty((2, 0), dtype=torch.long),
|
| 43 |
+
('cert', 'issued_to', 'ip'): torch.empty((2, 0), dtype=torch.long),
|
| 44 |
+
('domain', 'redirects_to', 'domain'): torch.empty((2, 0), dtype=torch.long)
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
return x_dict, edge_index_dict
|
Version_4/requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
pydantic
|
| 3 |
+
uvicorn
|
| 4 |
+
torch
|
| 5 |
+
torch-geometric
|
Version_4/simulate_warfare.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.optim as optim
|
| 4 |
+
import random
|
| 5 |
+
from models.gnn import PhishingGNN_Model
|
| 6 |
+
from pipeline.graph_engine import TopologicalGraphEngine
|
| 7 |
+
from config import *
|
| 8 |
+
|
| 9 |
+
def generate_synthetic_warfare(batch_size=100):
|
| 10 |
+
"""Generates synthetic network logs simulating both normal traffic and advanced attacks."""
|
| 11 |
+
logs = []
|
| 12 |
+
labels = []
|
| 13 |
+
|
| 14 |
+
for _ in range(batch_size):
|
| 15 |
+
is_attack = random.random() > 0.5
|
| 16 |
+
|
| 17 |
+
if is_attack:
|
| 18 |
+
logs.append({
|
| 19 |
+
'ip': f"{random.randint(1, 255)}.{random.randint(1,255)}.0.0",
|
| 20 |
+
'domain': None,
|
| 21 |
+
'asn': random.choice([666, 9999, 5555])
|
| 22 |
+
})
|
| 23 |
+
labels.append([1.0])
|
| 24 |
+
else:
|
| 25 |
+
logs.append({
|
| 26 |
+
'ip': f"104.21.{random.randint(1,100)}.{random.randint(1,255)}",
|
| 27 |
+
'domain': f"safe-service-{random.randint(1,100)}.com",
|
| 28 |
+
'asn': 13335
|
| 29 |
+
})
|
| 30 |
+
labels.append([0.0])
|
| 31 |
+
|
| 32 |
+
return logs, torch.tensor(labels, dtype=torch.float32)
|
| 33 |
+
|
| 34 |
+
def run_war_games():
|
| 35 |
+
print("[*] Initiating Autonomous Threat War Games...")
|
| 36 |
+
|
| 37 |
+
in_channels_dict = {'ip': 16, 'domain': 32, 'asn': 8, 'cert': 16}
|
| 38 |
+
model = PhishingGNN_Model(
|
| 39 |
+
metadata=GRAPH_METADATA,
|
| 40 |
+
in_channels_dict=in_channels_dict,
|
| 41 |
+
hidden_channels=HIDDEN_CHANNELS,
|
| 42 |
+
num_heads=NUM_HEADS,
|
| 43 |
+
num_layers=NUM_LAYERS,
|
| 44 |
+
dropout_rate=DROPOUT_RATE
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
optimizer = optim.AdamW(model.parameters(), lr=0.001)
|
| 48 |
+
criterion = nn.BCELoss()
|
| 49 |
+
engine = TopologicalGraphEngine()
|
| 50 |
+
|
| 51 |
+
for wave in range(50):
|
| 52 |
+
logs, labels = generate_synthetic_warfare(batch_size=200)
|
| 53 |
+
x_dict, edge_index_dict = engine.extract_and_build(logs)
|
| 54 |
+
|
| 55 |
+
optimizer.zero_grad()
|
| 56 |
+
predictions = model(x_dict, edge_index_dict)
|
| 57 |
+
valid_preds = predictions[:len(labels)]
|
| 58 |
+
|
| 59 |
+
loss = criterion(valid_preds, labels)
|
| 60 |
+
loss.backward()
|
| 61 |
+
optimizer.step()
|
| 62 |
+
|
| 63 |
+
if (wave + 1) % 10 == 0:
|
| 64 |
+
print(f"Attack Wave {wave+1:02d}/50 Defeated | Model Penetration Loss: {loss.item():.4f}")
|
| 65 |
+
|
| 66 |
+
model.safe_save(MODEL_SAVE_PATH)
|
| 67 |
+
print(f"[+] War games complete. Apex model hardened and saved to: {MODEL_SAVE_PATH}")
|
| 68 |
+
|
| 69 |
+
if __name__ == "__main__":
|
| 70 |
+
run_war_games()
|
Version_4/train.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.optim as optim
|
| 5 |
+
from models.gnn import PhishingGNN_Model
|
| 6 |
+
from pipeline.graph_engine import TopologicalGraphEngine
|
| 7 |
+
from config import *
|
| 8 |
+
|
| 9 |
+
def run_training_pipeline():
|
| 10 |
+
print("[*] Launching Production Defender V5 Training Run...")
|
| 11 |
+
|
| 12 |
+
# 1. Try to load real data, fallback to mock if missing
|
| 13 |
+
try:
|
| 14 |
+
with open("network_telemetry.json", "r") as f:
|
| 15 |
+
dataset = json.load(f)
|
| 16 |
+
print(f"[+] Loaded {len(dataset)} real telemetry logs.")
|
| 17 |
+
except FileNotFoundError:
|
| 18 |
+
print("[-] network_telemetry.json not found. Falling back to simulated context.")
|
| 19 |
+
dataset = [
|
| 20 |
+
{'ip': '185.220.101.5', 'domain': 'shadow-dns-bypass.net', 'asn': 44050, 'is_malicious': 1.0},
|
| 21 |
+
{'ip': '193.56.28.14', 'asn': 57099, 'is_malicious': 1.0},
|
| 22 |
+
{'ip': '8.8.8.8', 'domain': 'dns.google', 'asn': 15169, 'is_malicious': 0.0}
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
# 2. Extract logs and structure target threat labels
|
| 26 |
+
engine = TopologicalGraphEngine()
|
| 27 |
+
x_dict, edge_index_dict = engine.extract_and_build(dataset)
|
| 28 |
+
|
| 29 |
+
labels_list = []
|
| 30 |
+
for ip_str in engine.ip_map.keys():
|
| 31 |
+
matching_logs = [log for log in dataset if log.get('ip') == ip_str]
|
| 32 |
+
label = matching_logs[0].get('is_malicious', 0.0) if matching_logs else 0.0
|
| 33 |
+
labels_list.append([label])
|
| 34 |
+
|
| 35 |
+
labels = torch.tensor(labels_list, dtype=torch.float32)
|
| 36 |
+
|
| 37 |
+
# 3. Model setup
|
| 38 |
+
in_channels_dict = {'ip': 16, 'domain': 32, 'asn': 8, 'cert': 16}
|
| 39 |
+
model = PhishingGNN_Model(
|
| 40 |
+
metadata=GRAPH_METADATA,
|
| 41 |
+
in_channels_dict=in_channels_dict,
|
| 42 |
+
hidden_channels=HIDDEN_CHANNELS,
|
| 43 |
+
num_heads=NUM_HEADS,
|
| 44 |
+
num_layers=NUM_LAYERS,
|
| 45 |
+
dropout_rate=DROPOUT_RATE
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
optimizer = optim.AdamW(model.parameters(), lr=0.0005, weight_decay=1e-3)
|
| 49 |
+
criterion = nn.BCELoss()
|
| 50 |
+
|
| 51 |
+
# 4. Optimization Loop
|
| 52 |
+
model.train()
|
| 53 |
+
for epoch in range(100):
|
| 54 |
+
optimizer.zero_grad()
|
| 55 |
+
predictions = model(x_dict, edge_index_dict)
|
| 56 |
+
|
| 57 |
+
# Only calculate loss on the nodes we have labels for
|
| 58 |
+
valid_preds = predictions[:len(labels)]
|
| 59 |
+
loss = criterion(valid_preds, labels)
|
| 60 |
+
|
| 61 |
+
loss.backward()
|
| 62 |
+
optimizer.step()
|
| 63 |
+
|
| 64 |
+
if (epoch + 1) % 20 == 0:
|
| 65 |
+
print(f"Epoch {epoch+1:03d}/100 | Topological Loss: {loss.item():.5f}")
|
| 66 |
+
|
| 67 |
+
model.safe_save(MODEL_SAVE_PATH)
|
| 68 |
+
print(f"[+] Operational weights successfully frozen at: {MODEL_SAVE_PATH}")
|
| 69 |
+
|
| 70 |
+
if __name__ == "__main__":
|
| 71 |
+
run_training_pipeline()
|