synapse-shield 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ from .middleware import shield_protect
2
+ from .engine import SynapseEngine, analyze_behavior
3
+
4
+ __all__ = ["shield_protect", "SynapseEngine", "analyze_behavior"]
synapse_shield/cli.py ADDED
@@ -0,0 +1,32 @@
1
+ """
2
+ Synapse Shield CLI Runner
3
+ """
4
+
5
+ import argparse
6
+ import uvicorn
7
+
8
+ def main():
9
+ parser = argparse.ArgumentParser(description="Synapse Shield - CLI Controller")
10
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
11
+
12
+ # Run Server Command
13
+ run_parser = subparsers.add_parser("run", help="Start the Synapse Shield server and cockpit")
14
+ run_parser.add_argument("--host", default="0.0.0.0", help="Host address (default: 0.0.0.0)")
15
+ run_parser.add_argument("--port", type=int, default=8000, help="Port number (default: 8000)")
16
+
17
+ # Run Tests Command
18
+ subparsers.add_parser("test", help="Run the 7-vector Red Team bot attack simulator")
19
+
20
+ args = parser.parse_args()
21
+
22
+ if args.command == "run" or args.command is None:
23
+ port = getattr(args, "port", 8000)
24
+ host = getattr(args, "host", "0.0.0.0")
25
+ print(f"🛡️ Starting Synapse Shield on http://{host}:{port} ...")
26
+ uvicorn.run("synapse_shield.main:app", host=host, port=port, reload=True)
27
+ elif args.command == "test":
28
+ from .live_attacker import main as run_attack_suite
29
+ run_attack_suite()
30
+
31
+ if __name__ == "__main__":
32
+ main()
@@ -0,0 +1,109 @@
1
+ import math
2
+ from typing import Dict, Any, List, Tuple
3
+ from .features import extract_features
4
+
5
+ def poisson_anomaly_score(k: int, lambda_val: float = 2.0) -> float:
6
+ if k <= 1:
7
+ return 0.0
8
+ cumulative_prob = 0.0
9
+ for i in range(k):
10
+ try:
11
+ term = (math.pow(lambda_val, i) * math.exp(-lambda_val)) / math.factorial(i)
12
+ cumulative_prob += term
13
+ except (OverflowError, ValueError):
14
+ break
15
+ return min(1.0, max(0.0, cumulative_prob))
16
+
17
+ def analyze_behavior(telemetry: Dict[str, Any], recent_request_count: int = 1) -> Tuple[float, str, List[str], Dict[str, Any]]:
18
+ features = extract_features(telemetry)
19
+ reasons = []
20
+ total_risk = 0.0
21
+
22
+ # 1. Webdriver Tespiti (Hard Block)
23
+ if features["webdriver"]:
24
+ total_risk += 100.0
25
+ reasons.append("Automation tool interface (navigator.webdriver) detected.")
26
+
27
+ # 2. Ekran Boyutları (Headless)
28
+ if not features["screen_valid"]:
29
+ total_risk += 35.0
30
+ reasons.append("Invalid or headless screen dimensions detected.")
31
+
32
+ # 3. Faresiz Form Etkileşimi
33
+ if (features["click_count"] > 0 or features["key_count"] > 0) and features["mouse_points"] == 0:
34
+ total_risk += 50.0
35
+ reasons.append("Interactive events occurred without mouse movement telemetry.")
36
+
37
+ # 4. Kinematik & Fitts Kanunu Analizi
38
+ if features["mouse_points"] > 5:
39
+ # A. Doğrusallık (Düz Çizgi Botları)
40
+ if features["total_distance"] > 30 and features["straightness"] > 0.985:
41
+ total_risk += 75.0
42
+ reasons.append(f"Euclidean straight-line trajectory detected (straightness: {features['straightness']:.4f}).")
43
+
44
+ # B. Robotik Hız & İvme Varyansı
45
+ if features["total_distance"] > 30 and features["velocity_var"] < 0.0001:
46
+ total_risk += 65.0
47
+ reasons.append("Near-zero velocity variance observed in mouse path.")
48
+
49
+ if features["total_distance"] > 30 and features["acceleration_var"] < 0.0001:
50
+ total_risk += 65.0
51
+ reasons.append("Near-zero acceleration variance observed in mouse path.")
52
+
53
+ # C. FITTS KANUNU KONTROLÜ (Hedefe Yaklaşırken Yavaşlamayan Botlar)
54
+ # İnsan bir hedefe/tıklamaya yaklaşırken hızını en az %60 düşürür (terminal_decel_ratio < 0.40)
55
+ if features["click_count"] > 0 and features["total_distance"] > 50:
56
+ if features["terminal_decel_ratio"] > 0.85:
57
+ total_risk += 45.0
58
+ reasons.append(f"Fitts's Law violation: Zero terminal deceleration before click ({features['terminal_decel_ratio']:.2f}).")
59
+
60
+ # D. İnsanüstü Hız
61
+ if features["max_velocity"] > 15.0:
62
+ total_risk += 40.0
63
+ reasons.append(f"Superhuman mouse velocity (max: {features['max_velocity']:.2f} px/ms).")
64
+
65
+ # 5. Klavye Dinamikleri
66
+ if features["key_count"] > 3:
67
+ if features["key_interval_var"] < 4.0:
68
+ total_risk += 60.0
69
+ reasons.append(f"Highly rhythmic typing pattern detected (variance: {features['key_interval_var']:.2f} ms²).")
70
+
71
+ if features["key_interval_avg"] < 25.0:
72
+ total_risk += 50.0
73
+ reasons.append(f"Superhuman input frequency (avg typing interval: {features['key_interval_avg']:.1f} ms).")
74
+
75
+ # 6. Poisson Frekans Analizi & Biyometrik Füzyon
76
+ freq_anomaly = poisson_anomaly_score(recent_request_count, lambda_val=2.0)
77
+ if freq_anomaly >= 0.95:
78
+ is_human_telemetry = (
79
+ features["mouse_points"] > 5
80
+ and features["straightness"] < 0.96
81
+ and (features["velocity_var"] > 0.001 or features["avg_jerk"] > 0.0001)
82
+ )
83
+ if is_human_telemetry:
84
+ total_risk += 25.0 * freq_anomaly
85
+ reasons.append(f"High request frequency ({recent_request_count} req/10s), but organic human kinematics verified.")
86
+ else:
87
+ total_risk += 60.0 * freq_anomaly
88
+ reasons.append(f"Poisson request frequency anomaly (rate: {recent_request_count} req/10s, risk confidence: {freq_anomaly*100:.1f}%).")
89
+
90
+ bot_score = min(100.0, total_risk)
91
+ classification = "Bot" if bot_score >= 50.0 else "Human"
92
+
93
+ if bot_score < 10.0:
94
+ reasons.append("Natural behavioral telemetry flags verified.")
95
+
96
+ details = {
97
+ "features": features,
98
+ "recent_request_count": recent_request_count,
99
+ "poisson_anomaly_score": freq_anomaly
100
+ }
101
+
102
+ return bot_score, classification, reasons, details
103
+
104
+ class SynapseEngine:
105
+ def __init__(self, lambda_val: float = 2.0):
106
+ self.lambda_val = lambda_val
107
+
108
+ def analyze(self, telemetry: Dict[str, Any], recent_request_count: int = 1) -> Tuple[float, str, List[str], Dict[str, Any]]:
109
+ return analyze_behavior(telemetry, recent_request_count)
@@ -0,0 +1,114 @@
1
+ """
2
+ Synapse Shield - Kinematic Feature Extractor v0.2.0
3
+ Extracts 19D physical motion vectors + Fitts's Law Deceleration Profiles.
4
+ """
5
+
6
+ import math
7
+ from typing import Dict, Any, List
8
+
9
+ def extract_features(telemetry: Dict[str, Any]) -> Dict[str, Any]:
10
+ features = {
11
+ "mouse_points": 0,
12
+ "total_distance": 0.0,
13
+ "straightness": 1.0,
14
+ "avg_velocity": 0.0,
15
+ "max_velocity": 0.0,
16
+ "velocity_var": 0.0,
17
+ "avg_acceleration": 0.0,
18
+ "acceleration_var": 0.0,
19
+ "avg_jerk": 0.0,
20
+ "click_count": 0,
21
+ "key_count": 0,
22
+ "key_interval_avg": 0.0,
23
+ "key_interval_var": 0.0,
24
+ "webdriver": False,
25
+ "screen_valid": True,
26
+ "scroll_count": 0,
27
+ "terminal_decel_ratio": 1.0, # Fitts Kanunu: Son hız / Tepe hız oranı
28
+ "velocity_skewness": 0.0, # Hız profilinin asimetrisi (Balistik vs Düzeltici)
29
+ }
30
+
31
+ # Tarayıcı Nitelikleri
32
+ browser = telemetry.get("browser", {})
33
+ features["webdriver"] = bool(browser.get("webdriver", False))
34
+
35
+ screen_width = browser.get("screen_width", 0)
36
+ screen_height = browser.get("screen_height", 0)
37
+ if screen_width <= 0 or screen_height <= 0:
38
+ features["screen_valid"] = False
39
+
40
+ features["scroll_count"] = len(telemetry.get("scrolls", []))
41
+ features["click_count"] = len(telemetry.get("clicks", []))
42
+
43
+ # Klavye Dinamikleri
44
+ keystrokes = telemetry.get("keystrokes", [])
45
+ features["key_count"] = len(keystrokes)
46
+ if len(keystrokes) > 1:
47
+ sorted_keys = sorted(keystrokes, key=lambda k: k.get("t", 0))
48
+ intervals = [max(0.0, sorted_keys[i].get("t", 0) - sorted_keys[i - 1].get("t", 0)) for i in range(1, len(sorted_keys))]
49
+ if intervals:
50
+ avg_int = sum(intervals) / len(intervals)
51
+ features["key_interval_avg"] = avg_int
52
+ features["key_interval_var"] = sum((x - avg_int) ** 2 for x in intervals) / len(intervals)
53
+
54
+ # Fare Hareketi ve Fitts Kanunu Kinematiği
55
+ mouse_movements = telemetry.get("mouse_movements", [])
56
+ features["mouse_points"] = len(mouse_movements)
57
+
58
+ if len(mouse_movements) > 2:
59
+ movements = sorted(mouse_movements, key=lambda m: m.get("t", 0))
60
+
61
+ distances, dts, velocities = [], [], []
62
+ start_x, start_y = movements[0].get("x", 0), movements[0].get("y", 0)
63
+ end_x, end_y = movements[-1].get("x", 0), movements[-1].get("y", 0)
64
+ displacement = math.sqrt((end_x - start_x)**2 + (end_y - start_y)**2)
65
+
66
+ for i in range(1, len(movements)):
67
+ x1, y1, t1 = movements[i-1].get("x", 0), movements[i-1].get("y", 0), movements[i-1].get("t", 0)
68
+ x2, y2, t2 = movements[i].get("x", 0), movements[i].get("y", 0), movements[i].get("t", 0)
69
+
70
+ d_dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
71
+ d_time = max(0.1, t2 - t1)
72
+
73
+ distances.append(d_dist)
74
+ dts.append(d_time)
75
+ velocities.append(d_dist / d_time)
76
+
77
+ total_dist = sum(distances)
78
+ features["total_distance"] = total_dist
79
+ features["straightness"] = (displacement / total_dist) if total_dist > 0 else 1.0
80
+
81
+ if velocities:
82
+ avg_vel = sum(velocities) / len(velocities)
83
+ max_vel = max(velocities)
84
+ features["avg_velocity"] = avg_vel
85
+ features["max_velocity"] = max_vel
86
+ features["velocity_var"] = sum((v - avg_vel) ** 2 for v in velocities) / len(velocities)
87
+
88
+ # FITTS KANUNU 1: Son %20'lik Yoldaki Yavaşlama Oranı
89
+ last_segment_count = max(1, int(len(velocities) * 0.25))
90
+ terminal_avg_vel = sum(velocities[-last_segment_count:]) / last_segment_count
91
+ features["terminal_decel_ratio"] = (terminal_avg_vel / max_vel) if max_vel > 0 else 1.0
92
+
93
+ # FITTS KANUNU 2: Hızın Tepe Noktası Konumu (Skewer / Asimetri)
94
+ peak_idx = velocities.index(max_vel)
95
+ features["velocity_skewness"] = peak_idx / float(len(velocities)) # İnsanda 0.25 - 0.45 arası
96
+
97
+ # İvme ve Jerk (Sarsıntı) Hesabı
98
+ accelerations = []
99
+ for i in range(1, len(velocities)):
100
+ accelerations.append((velocities[i] - velocities[i-1]) / dts[i])
101
+
102
+ if accelerations:
103
+ avg_acc = sum(accelerations) / len(accelerations)
104
+ features["avg_acceleration"] = avg_acc
105
+ features["acceleration_var"] = sum((a - avg_acc) ** 2 for a in accelerations) / len(accelerations)
106
+
107
+ jerks = []
108
+ for i in range(1, len(accelerations)):
109
+ jerks.append((accelerations[i] - accelerations[i-1]) / dts[i+1])
110
+
111
+ if jerks:
112
+ features["avg_jerk"] = sum(map(abs, jerks)) / len(jerks)
113
+
114
+ return features
@@ -0,0 +1,128 @@
1
+ """
2
+ Synapse Shield - Red Team Automated Bot Attack Suite v0.2.0
3
+ Simulates 7 real-world bot attack campaigns including Replay Attacks.
4
+ """
5
+
6
+ import time
7
+ import math
8
+ import random
9
+ import json
10
+ import base64
11
+ import urllib.request
12
+ import urllib.error
13
+
14
+ TARGET_URL = "http://127.0.0.1:8000/api/score"
15
+ CHALLENGE_URL = "http://127.0.0.1:8000/api/challenge"
16
+
17
+ class C:
18
+ RED = '\033[91m'
19
+ GREEN = '\033[92m'
20
+ YELLOW = '\033[93m'
21
+ CYAN = '\033[96m'
22
+ BOLD = '\033[1m'
23
+ END = '\033[0m'
24
+
25
+ def get_challenge():
26
+ try:
27
+ with urllib.request.urlopen(CHALLENGE_URL) as resp:
28
+ return json.loads(resp.read().decode('utf-8')).get("challenge")
29
+ except Exception:
30
+ return None
31
+
32
+ def send_attack(name: str, payload: dict, ip_suffix: int = 1) -> dict:
33
+ data = json.dumps(payload).encode('utf-8')
34
+ req = urllib.request.Request(
35
+ TARGET_URL,
36
+ data=data,
37
+ headers={
38
+ 'Content-Type': 'application/json',
39
+ 'User-Agent': f'SynapseShield-RedTeamBot/2.0 ({name})',
40
+ 'x-forwarded-for': f'192.168.1.{ip_suffix}'
41
+ }
42
+ )
43
+ t_start = time.perf_counter()
44
+ try:
45
+ with urllib.request.urlopen(req) as resp:
46
+ t_end = time.perf_counter()
47
+ res = json.loads(resp.read().decode('utf-8'))
48
+ res['network_latency_ms'] = round((t_end - t_start) * 1000, 2)
49
+ return res
50
+ except Exception as e:
51
+ return {"status": "error", "bot_score": 100.0, "classification": "Bot", "reasons": [str(e)]}
52
+
53
+ def print_result(attack_num: int, title: str, res: dict, expected_blocked: bool = True):
54
+ score = res.get("bot_score", 0.0)
55
+ classification = res.get("classification", "Unknown")
56
+ reasons = res.get("reasons", [])
57
+ latency = res.get("network_latency_ms", 0.0)
58
+
59
+ is_blocked = (classification == "Bot" or score >= 50.0)
60
+ success = is_blocked if expected_blocked else not is_blocked
61
+ status_text = f"{C.GREEN}✅ KALKAN BAŞARILI (Engellendi){C.END}" if success else f"{C.RED}❌ BAŞARISIZ{C.END}"
62
+
63
+ print(f"\n{C.BOLD}{C.CYAN}┌─────────────────────────────────────────────────────────────{C.END}")
64
+ print(f"{C.BOLD}{C.CYAN}│ SALDIRI #{attack_num}: {title}{C.END}")
65
+ print(f"{C.BOLD}{C.CYAN}├─────────────────────────────────────────────────────────────{C.END}")
66
+ print(f"│ Durum: {status_text}")
67
+ print(f"│ Karar: {C.RED if is_blocked else C.GREEN}{classification.upper()}{C.END} (Risk: {score:.1f}%)")
68
+ print(f"│ Ağ Gecikmesi: {C.YELLOW}{latency} ms{C.END}")
69
+ print(f"│ Nedenler: {', '.join(reasons)}")
70
+ print(f"{C.BOLD}{C.CYAN}└─────────────────────────────────────────────────────────────{C.END}")
71
+
72
+ def main():
73
+ print(f"\n{C.BOLD}{C.YELLOW}╔═════════════════════════════════════════════════════════════╗{C.END}")
74
+ print(f"{C.BOLD}{C.YELLOW}║ 🔴 SYNAPSE SHIELD v0.2.0 — RED TEAM BOT SALDIRI SÜİTİ ║{C.END}")
75
+ print(f"{C.BOLD}{C.YELLOW}╚═════════════════════════════════════════════════════════════╝{C.END}\n")
76
+
77
+ # 1. Selenium
78
+ res1 = send_attack("Selenium", {"browser": {"webdriver": True, "screen_width": 800, "screen_height": 600}}, ip_suffix=10)
79
+ print_result(1, "Selenium Headless Crawler", res1, expected_blocked=True)
80
+
81
+ # 2. Linear Mouse
82
+ t = int(time.time()*1000)
83
+ res2 = send_attack("Linear", {"mouse_movements": [{"x": 50 + i*30, "y": 50 + i*20, "t": t + i*20} for i in range(25)]}, ip_suffix=20)
84
+ print_result(2, "Doğrusal Fare Botu (Straight-Line)", res2, expected_blocked=True)
85
+
86
+ # 3. Bézier
87
+ p0, p1, p2 = (50, 50), (400, 700), (900, 200)
88
+ bezier_pts = [{"x": round((1-i/30)**2 * p0[0] + 2*(1-i/30)*(i/30)*p1[0] + (i/30)**2 * p2[0]),
89
+ "y": round((1-i/30)**2 * p0[1] + 2*(1-i/30)*(i/30)*p1[1] + (i/30)**2 * p2[1]),
90
+ "t": t + i*20} for i in range(30)]
91
+ res3 = send_attack("Bezier", {"mouse_movements": bezier_pts}, ip_suffix=30)
92
+ print_result(3, "Bézier Eğrisi Botu (No-Jerk Curve)", res3, expected_blocked=True)
93
+
94
+ # 4. Auto-Typer
95
+ keys = [{"type": "down", "t": t + i*100} for i in range(10)]
96
+ res4 = send_attack("AutoTyper", {"keystrokes": keys, "clicks": [{"x": 100, "y": 100, "t": t}]}, ip_suffix=40)
97
+ print_result(4, "Robotik Klavye Otomatı", res4, expected_blocked=True)
98
+
99
+ # 5. Poisson Flood
100
+ for _ in range(7):
101
+ send_attack("Flood", {"browser": {}}, ip_suffix=50)
102
+ time.sleep(0.01)
103
+ res5 = send_attack("Flood", {"browser": {}}, ip_suffix=50)
104
+ print_result(5, "Poisson İstek Bombardımanı (DDoS)", res5, expected_blocked=True)
105
+
106
+ # 6. Doğal İnsan
107
+ human_pts = [{"x": round(100 + i*15 + random.gauss(0, 2.5)), "y": round(150 + math.sin(i/3)*20 + random.gauss(0, 2.5)), "t": t + i*25} for i in range(40)]
108
+ res6 = send_attack("Human", {"mouse_movements": human_pts, "clicks": [{"x": 700, "y": 200, "t": t+1000}]}, ip_suffix=60)
109
+ print_result(6, "Doğal İnsan Ziyaretçisi (Control)", res6, expected_blocked=False)
110
+
111
+ # 7. YENİ: Replay Attack Simülasyonu
112
+ print(f"\n{C.YELLOW}[*] Replay Attack Testi: Gerçek bir token çalınıp 2. kez gönderiliyor...{C.END}")
113
+ ch = get_challenge()
114
+ if ch:
115
+ Date_now = int(time.time())
116
+ envelope = {"challenge": ch, "telemetry": {"mouse_movements": human_pts}, "created_at": Date_now}
117
+ valid_token = base64.b64encode(json.dumps(envelope).encode()).decode()
118
+
119
+ # 1. Gönderim (Başarılı olmalı)
120
+ send_attack("Replay-1", {"token": valid_token}, ip_suffix=70)
121
+ # 2. Gönderim (Replay - ENGELLENMELİ!)
122
+ res7 = send_attack("Replay-2", {"token": valid_token}, ip_suffix=70)
123
+ print_result(7, "Replay Attack (Aynı Token'ı Tekrar Kullanma)", res7, expected_blocked=True)
124
+
125
+ print(f"\n{C.BOLD}{C.GREEN}🎯 TÜM 7 SALDIRI VE GÜVENLİK TESTİ BAŞARIYLA TAMAMLANDI!{C.END}\n")
126
+
127
+ if __name__ == "__main__":
128
+ main()
synapse_shield/main.py ADDED
@@ -0,0 +1,196 @@
1
+ import os
2
+ import json
3
+ import sqlite3
4
+ import uvicorn
5
+ from datetime import datetime, timedelta
6
+ from fastapi import FastAPI, Request, HTTPException
7
+ from fastapi.responses import HTMLResponse, FileResponse
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi.staticfiles import StaticFiles
10
+ from typing import Dict, Any, List
11
+
12
+ from .engine import analyze_behavior
13
+ from .tokens import generate_challenge, verify_and_consume_token
14
+
15
+ DB_FILE = "synapse_shield.db"
16
+
17
+ def init_db():
18
+ conn = sqlite3.connect(DB_FILE)
19
+ cursor = conn.cursor()
20
+ cursor.execute("""
21
+ CREATE TABLE IF NOT EXISTS logs (
22
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
23
+ timestamp TEXT,
24
+ ip TEXT,
25
+ user_agent TEXT,
26
+ bot_score REAL,
27
+ classification TEXT,
28
+ reasons TEXT,
29
+ features TEXT,
30
+ telemetry TEXT
31
+ )
32
+ """)
33
+ cursor.execute("PRAGMA journal_mode=WAL;")
34
+ conn.commit()
35
+ conn.close()
36
+
37
+ init_db()
38
+
39
+ app = FastAPI(title="Synapse Shield - Behavioral Bot Detection Engine")
40
+
41
+ app.add_middleware(
42
+ CORSMiddleware,
43
+ allow_origins=["*"],
44
+ allow_credentials=True,
45
+ allow_methods=["*"],
46
+ allow_headers=["*"],
47
+ )
48
+
49
+ TRUSTED_PROXIES = {"127.0.0.1", "::1"}
50
+
51
+ def get_client_ip(request: Request) -> str:
52
+ client_ip = request.client.host if request.client else "127.0.0.1"
53
+ if client_ip in TRUSTED_PROXIES:
54
+ forwarded = request.headers.get("x-forwarded-for")
55
+ if forwarded:
56
+ return forwarded.split(",")[0].strip()
57
+ return client_ip
58
+
59
+ def get_recent_request_count(ip: str) -> int:
60
+ conn = sqlite3.connect(DB_FILE)
61
+ cursor = conn.cursor()
62
+ ten_seconds_ago = (datetime.utcnow() - timedelta(seconds=10)).isoformat()
63
+ cursor.execute("SELECT COUNT(*) FROM logs WHERE ip = ? AND timestamp > ?", (ip, ten_seconds_ago))
64
+ count = cursor.fetchone()[0]
65
+ conn.close()
66
+ return count + 1
67
+
68
+ def save_log(ip: str, user_agent: str, bot_score: float, classification: str, reasons: List[str], features: Dict[str, Any], telemetry: Dict[str, Any]):
69
+ conn = sqlite3.connect(DB_FILE)
70
+ cursor = conn.cursor()
71
+ now = datetime.utcnow().isoformat()
72
+ cursor.execute(
73
+ """
74
+ INSERT INTO logs (timestamp, ip, user_agent, bot_score, classification, reasons, features, telemetry)
75
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
76
+ """,
77
+ (now, ip, user_agent, bot_score, classification, json.dumps(reasons), json.dumps(features), json.dumps(telemetry))
78
+ )
79
+ conn.commit()
80
+ conn.close()
81
+
82
+ # YENİ ENDPOINT: İstemciye tek kullanımlık challenge verir
83
+ @app.get("/api/challenge")
84
+ async def get_challenge():
85
+ return generate_challenge()
86
+
87
+ @app.post("/api/score")
88
+ async def score_telemetry(request: Request):
89
+ try:
90
+ body = await request.json()
91
+ except Exception:
92
+ raise HTTPException(status_code=400, detail="Invalid JSON payload")
93
+
94
+ ip = get_client_ip(request)
95
+ user_agent = request.headers.get("user-agent", "Unknown")
96
+
97
+ # 1. Kriptografik Token Varsa Doğrula
98
+ if "token" in body:
99
+ is_valid, reason, telemetry = verify_and_consume_token(body["token"])
100
+ if not is_valid:
101
+ # Replay Attack veya sahte token durumu
102
+ save_log(ip, user_agent, 100.0, "Bot", [reason], {}, {})
103
+ return {
104
+ "status": "blocked",
105
+ "bot_score": 100.0,
106
+ "classification": "Bot",
107
+ "reasons": [reason],
108
+ "details": {}
109
+ }
110
+ else:
111
+ # Geriye dönük uyumluluk: doğrudan telemetri gönderildiyse
112
+ telemetry = body.get("telemetry", body)
113
+
114
+ recent_count = get_recent_request_count(ip)
115
+ bot_score, classification, reasons, details = analyze_behavior(telemetry, recent_count)
116
+ save_log(ip, user_agent, bot_score, classification, reasons, details.get("features", {}), telemetry)
117
+
118
+ return {
119
+ "status": "success",
120
+ "bot_score": bot_score,
121
+ "classification": classification,
122
+ "reasons": reasons,
123
+ "details": details
124
+ }
125
+
126
+ @app.get("/api/logs")
127
+ async def get_logs(limit: int = 50):
128
+ conn = sqlite3.connect(DB_FILE)
129
+ conn.row_factory = sqlite3.Row
130
+ cursor = conn.cursor()
131
+ cursor.execute("SELECT id, timestamp, ip, user_agent, bot_score, classification, reasons, features FROM logs ORDER BY id DESC LIMIT ?", (limit,))
132
+ rows = cursor.fetchall()
133
+
134
+ recent_logs = []
135
+ for r in rows:
136
+ recent_logs.append({
137
+ "id": r["id"],
138
+ "timestamp": r["timestamp"],
139
+ "ip": r["ip"],
140
+ "user_agent": r["user_agent"],
141
+ "bot_score": r["bot_score"],
142
+ "classification": r["classification"],
143
+ "reasons": json.loads(r["reasons"]) if r["reasons"] else [],
144
+ "features": json.loads(r["features"]) if r["features"] else {}
145
+ })
146
+
147
+ cursor.execute("SELECT COUNT(*) FROM logs")
148
+ total_requests = cursor.fetchone()[0]
149
+ cursor.execute("SELECT COUNT(*) FROM logs WHERE classification = 'Bot'")
150
+ bot_requests = cursor.fetchone()[0]
151
+ cursor.execute("SELECT AVG(bot_score) FROM logs WHERE classification = 'Bot'")
152
+ avg_bot = cursor.fetchone()[0] or 0.0
153
+ cursor.execute("SELECT AVG(bot_score) FROM logs WHERE classification = 'Human'")
154
+ avg_human = cursor.fetchone()[0] or 0.0
155
+ conn.close()
156
+
157
+ return {
158
+ "total_requests": total_requests,
159
+ "bot_requests": bot_requests,
160
+ "human_requests": total_requests - bot_requests,
161
+ "bot_ratio": (bot_requests / total_requests * 100) if total_requests > 0 else 0.0,
162
+ "avg_bot_score": round(avg_bot, 2),
163
+ "avg_human_score": round(avg_human, 2),
164
+ "logs": recent_logs
165
+ }
166
+
167
+ @app.post("/api/clear")
168
+ async def clear_logs():
169
+ conn = sqlite3.connect(DB_FILE)
170
+ cursor = conn.cursor()
171
+ cursor.execute("DELETE FROM logs")
172
+ conn.commit()
173
+ conn.close()
174
+ return {"status": "success", "message": "Database logs cleared"}
175
+
176
+ STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
177
+
178
+ @app.get("/")
179
+ def read_root():
180
+ index_path = os.path.join(STATIC_DIR, "index.html")
181
+ if os.path.exists(index_path):
182
+ return FileResponse(index_path)
183
+ return HTMLResponse("<h2>Synapse Shield Cockpit: index.html missing.</h2>")
184
+
185
+ @app.get("/static/synapse-sdk.js")
186
+ def read_sdk():
187
+ sdk_path = os.path.join(STATIC_DIR, "synapse-sdk.js")
188
+ if os.path.exists(sdk_path):
189
+ return FileResponse(sdk_path, media_type="application/javascript")
190
+ return HTMLResponse("<h2>synapse-sdk.js missing.</h2>", status_code=404)
191
+
192
+ if os.path.exists(STATIC_DIR):
193
+ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
194
+
195
+ if __name__ == "__main__":
196
+ uvicorn.run("synapse_shield.main:app", host="0.0.0.0", port=8000, reload=True)
@@ -0,0 +1,59 @@
1
+ """
2
+ Synapse Shield - Drop-in FastAPI / Python Middleware
3
+ Allows developers to protect any route with a single decorator: @shield_protect
4
+ """
5
+
6
+ from functools import wraps
7
+ from fastapi import Request, HTTPException
8
+ from .engine import analyze_behavior
9
+
10
+ def shield_protect(max_risk_score: float = 50.0):
11
+ """
12
+ Decorator to protect any FastAPI endpoint with Synapse Shield behavioral biometrics.
13
+ Usage:
14
+ @app.post("/login")
15
+ @shield_protect(max_risk_score=50.0)
16
+ async def login(request: Request):
17
+ ...
18
+ """
19
+ def decorator(func):
20
+ @wraps(func)
21
+ async def wrapper(*args, **kwargs):
22
+ # Extract request object
23
+ request: Request = kwargs.get("request")
24
+ if not request:
25
+ for arg in args:
26
+ if isinstance(arg, Request):
27
+ request = arg
28
+ break
29
+
30
+ if not request:
31
+ raise HTTPException(status_code=500, detail="Request object not found in endpoint signature")
32
+
33
+ # Extract telemetry from header or body
34
+ telemetry = None
35
+ try:
36
+ body = await request.json()
37
+ telemetry = body.get("telemetry") or body
38
+ except Exception:
39
+ pass
40
+
41
+ if not telemetry:
42
+ raise HTTPException(status_code=403, detail="[Synapse Shield] Missing behavioral telemetry payload.")
43
+
44
+ bot_score, classification, reasons, _ = analyze_behavior(telemetry)
45
+
46
+ if bot_score >= max_risk_score:
47
+ raise HTTPException(
48
+ status_code=403,
49
+ detail={
50
+ "error": "Access Denied by Synapse Shield",
51
+ "classification": classification,
52
+ "bot_score": f"{bot_score}%",
53
+ "reasons": reasons
54
+ }
55
+ )
56
+
57
+ return await func(*args, **kwargs)
58
+ return wrapper
59
+ return decorator