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.
- synapse_shield/__init__.py +4 -0
- synapse_shield/cli.py +32 -0
- synapse_shield/engine.py +109 -0
- synapse_shield/features.py +114 -0
- synapse_shield/live_attacker.py +128 -0
- synapse_shield/main.py +196 -0
- synapse_shield/middleware.py +59 -0
- synapse_shield/static/index.html +500 -0
- synapse_shield/static/synapse-sdk.js +118 -0
- synapse_shield/tokens.py +81 -0
- synapse_shield-0.1.0.dist-info/METADATA +217 -0
- synapse_shield-0.1.0.dist-info/RECORD +16 -0
- synapse_shield-0.1.0.dist-info/WHEEL +5 -0
- synapse_shield-0.1.0.dist-info/entry_points.txt +2 -0
- synapse_shield-0.1.0.dist-info/licenses/LICENSE +21 -0
- synapse_shield-0.1.0.dist-info/top_level.txt +1 -0
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()
|
synapse_shield/engine.py
ADDED
|
@@ -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
|