atharvawarade9807 commited on
Commit
ddcce43
·
verified ·
1 Parent(s): 0ea26e0

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +140 -63
main.py CHANGED
@@ -3,25 +3,22 @@ import sys
3
  import time
4
  import asyncio
5
  import importlib.util
6
- import uvicorn
7
 
8
  # =====================================================================
9
- # 1. CORE ARCHITECTURE & PERMANENT RUNTIME PATHS
10
  # =====================================================================
11
  CURRENT_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
12
- OUTPUT_DIR = os.path.join(CURRENT_BASE_DIR, "output")
13
- os.makedirs(OUTPUT_DIR, exist_ok=True)
14
-
15
- # 🚨 PR FIX 1: Paths must be permanently appended for runtime lazy-imports
16
- if CURRENT_BASE_DIR not in sys.path:
17
- sys.path.insert(0, CURRENT_BASE_DIR)
18
 
19
- # Append sub-engines to the END of sys.path.
20
- # This keeps them available for runtime execution without overriding root namespaces.
21
- for engine_dir in ["Version_1", "Version_2", "Version_3", "Version_4", "Version_5"]:
22
- engine_path = os.path.join(CURRENT_BASE_DIR, engine_dir)
23
- if os.path.exists(engine_path) and engine_path not in sys.path:
24
- sys.path.append(engine_path)
25
 
26
  # =====================================================================
27
  # 2. FRAMEWORK & CORE ENGINE IMPORTS
@@ -31,40 +28,66 @@ import xgboost as xgb
31
  from fastapi import FastAPI, HTTPException, APIRouter
32
  from pydantic import BaseModel, Field
33
  from typing import Dict, Any
34
- from dataclasses import asdict
35
  from playwright.async_api import async_playwright
36
- from fastapi.middleware.cors import CORSMiddleware
37
 
38
- # Initialize core application gateway
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  app = FastAPI(
40
  title="Master Threat Intelligence Hub Gateway",
41
- description="Unified routing architecture managing deployed sub-engines: V1 through V6."
42
  )
43
 
 
44
  app.add_middleware(
45
  CORSMiddleware,
46
- allow_origins=["*"],
47
- allow_credentials=False,
 
 
 
 
 
48
  allow_methods=["*"],
49
  allow_headers=["*"],
50
  )
51
 
 
 
 
52
  # =====================================================================
53
- # [ ENGINE 1 ] VERSION 1: HEURISTICS
54
  # =====================================================================
55
- # 🚨 PR FIX 3: Consolidated imports. Loading app and process_payload together to utilize sys.modules cache.
56
  try:
57
- from Version_1.main import app as v1_app, process_payload as v1_process
58
  app.include_router(v1_app.router, tags=["Version 1: Legacy Model"])
59
  print("[+] Successfully merged Version 1 into root UI")
60
  except Exception as e:
61
- v1_process = None
62
  print(f"[-] Could not merge Version 1. Error: {e}")
63
 
64
  # =====================================================================
65
- # [ ENGINE 2 ] VERSION 2: VISUAL RESNET18
66
  # =====================================================================
67
- VERSION_2_PATH = os.path.join(CURRENT_BASE_DIR, "Version_2")
68
  v2_router = APIRouter(prefix="/api/v2", tags=["Version 2: Visual ResNet18 Engine"])
69
 
70
  class V2VisionRequest(BaseModel):
@@ -76,16 +99,16 @@ v2_capture_screenshot = None
76
  try:
77
  print("[*] Attempting to load Version 2 visual engine...")
78
  v2_main_path = os.path.join(VERSION_2_PATH, "main.py")
79
- if os.path.exists(v2_main_path):
80
- spec = importlib.util.spec_from_file_location("v2_main", v2_main_path)
81
- v2_main = importlib.util.module_from_spec(spec)
82
- sys.modules["v2_main"] = v2_main
83
- spec.loader.exec_module(v2_main)
84
-
85
- V2_MODEL_PATH = os.path.join(VERSION_2_PATH, "models", "production_resnet_ema.pth")
86
- v2_analyzer = v2_main.ProductionAnalyzer(model_path=V2_MODEL_PATH)
87
- v2_capture_screenshot = v2_main.capture_screenshot
88
- print("[+] Successfully initialized Version 2 ResNet18 visual engine.")
89
  except Exception as e:
90
  print(f"[-] Could not load Version 2 visual engine. Error: {e}")
91
 
@@ -103,8 +126,9 @@ async def analyze_url_vision(payload: V2VisionRequest):
103
 
104
  try:
105
  screenshot_file = await v2_capture_screenshot(url, temp_img_path)
 
106
  if not screenshot_file or not os.path.exists(screenshot_file):
107
- raise HTTPException(status_code=502, detail="Failed to capture page frame.")
108
 
109
  result = v2_analyzer.analyze_image(screenshot_file)
110
  latency_ms = (time.perf_counter() - start_time) * 1000
@@ -112,9 +136,11 @@ async def analyze_url_vision(payload: V2VisionRequest):
112
  if "error" in result:
113
  raise HTTPException(status_code=500, detail=result["error"])
114
 
 
 
115
  return {
116
  "target_url": url,
117
- "verdict": "QUARANTINE" if result["prediction"] == "Phishing" else "PASS",
118
  "raw_prediction": result["prediction"],
119
  "confidence": result["confidence"],
120
  "latency_ms": round(latency_ms, 2)
@@ -126,13 +152,14 @@ async def analyze_url_vision(payload: V2VisionRequest):
126
  app.include_router(v2_router)
127
 
128
  # =====================================================================
129
- # [ ENGINE 3 ] VERSION 3: CONTENT TEXT SVM
130
  # =====================================================================
131
  try:
132
  print("[*] Attempting to load Version 3...")
133
  from Version_3.main import app as v3_app
134
  app.mount("/scan", v3_app, name="v3_email")
135
  print("[+] Successfully merged Version 3 email scanner")
 
136
  except Exception as e:
137
  print(f"[-] Could not merge Version 3. Error: {e}")
138
 
@@ -144,7 +171,6 @@ v4_router = APIRouter(prefix="/api/v4", tags=["Version 4: XGBoost Engine"])
144
  class XGBoostRequest(BaseModel):
145
  url: str = Field(..., description="Target URL to evaluate via structural feature mapping", example="google.com")
146
 
147
- VERSION_4_PATH = os.path.join(CURRENT_BASE_DIR, "Version_4")
148
  MODEL_PATH = os.path.join(VERSION_4_PATH, "models", "final_model.json")
149
  if os.path.exists(MODEL_PATH):
150
  bst = xgb.Booster()
@@ -153,14 +179,6 @@ else:
153
  bst = None
154
  print(f"[-] Warning: {MODEL_PATH} not found. V4 engine will return a configuration error.")
155
 
156
- try:
157
- from Version_4.features import extract_url_features
158
- except ModuleNotFoundError:
159
- try:
160
- from features import extract_url_features
161
- except ModuleNotFoundError:
162
- pass # Fails loudly at runtime endpoint as intended
163
-
164
  @v4_router.post("/evaluate")
165
  async def evaluate_xgboost_url(payload: XGBoostRequest):
166
  if bst is None:
@@ -178,9 +196,11 @@ async def evaluate_xgboost_url(payload: XGBoostRequest):
178
  features = extract_url_features(processed_url)
179
  dmatrix_payload = xgb.DMatrix(np.array([features]))
180
 
 
181
  safeness_prob = float(bst.predict(dmatrix_payload)[0])
182
  latency_ms = (time.perf_counter() - start_time) * 1000
183
 
 
184
  if safeness_prob < 0.50:
185
  verdict = "🚨 PHISHING DETECTED"
186
  confidence = (1.0 - safeness_prob) * 100
@@ -197,23 +217,70 @@ async def evaluate_xgboost_url(payload: XGBoostRequest):
197
  }
198
  except Exception as e:
199
  raise HTTPException(status_code=500, detail=f"Structural evaluation failure: {str(e)}")
200
-
201
  app.include_router(v4_router)
202
 
203
  # =====================================================================
204
  # [ ENGINE 5 ] VERSION 5: AGENTIC MULTI-SPECIALIST FORENSIC PANEL
205
  # =====================================================================
206
- # 🚨 PR FIX 2: Restored visibility of internal V5 dependencies for architectural tracking
207
- try:
208
- from Version_5.src.dom_scraper import extract_dom_features
209
- from Version_5.src.orchestrator import evaluate_consensus
 
 
 
 
 
 
 
 
210
 
211
- # Internal dependencies verified, mounting encapsulated router
212
- from Version_5.app import router as v5_router
213
- app.include_router(v5_router, prefix="/api/v5", tags=["Version 5: Agentic Panel"])
214
- print("[+] Successfully merged Version 5 Agentic panel into gateway.")
215
- except Exception as e:
216
- print(f"[-] Could not load Version 5 Agentic engine. Missing dependencies: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
  # =====================================================================
219
  # [ ENGINE 6 ] VERSION 6: INTERACTIVE THREAT COGNITIVE SANDBOX
@@ -288,9 +355,13 @@ async def gateway_status():
288
  "loaded_engines": ["V1", "V2", "V3", "V4", "V5", "V6"]
289
  }
290
 
 
291
  # =====================================================================
292
  # [ MASTER FUSION ENGINE ] COMBINES V1, V2 (CNN), and V4 (XGBoost)
293
  # =====================================================================
 
 
 
294
  class UnifiedRequest(BaseModel):
295
  url: str
296
 
@@ -302,7 +373,7 @@ async def unified_scan_endpoint(payload: UnifiedRequest):
302
 
303
  start_time = time.perf_counter()
304
 
305
- # 1. Run V1 (Heuristics) - Will crash loudly via 500 error if offline as intended for security
306
  v1_matrix = await v1_process(url)
307
  v1_score = v1_matrix.composite_score
308
 
@@ -315,17 +386,20 @@ async def unified_scan_endpoint(payload: UnifiedRequest):
315
  dmatrix_payload = xgb.DMatrix(np.array([features]))
316
 
317
  raw_safeness = float(bst.predict(dmatrix_payload)[0])
 
 
318
  v4_score = 1.0 - raw_safeness
 
319
  v4_msg = f"XGBoost Match: {round(v4_score * 100, 1)}% Phishing Risk"
320
  except Exception as e:
321
  v4_msg = f"V4 Error: {str(e)}"
322
-
323
  # 3. Run V2 (CNN ResNet18) - Wrapped securely to prevent timeout crashes
324
  v2_score = 0.0
325
  v2_msg = "Skipped (Timeout Prevention)"
326
  try:
327
  if v2_analyzer is not None and v2_capture_screenshot is not None:
328
  temp_img = os.path.join(OUTPUT_DIR, f"fusion_{int(time.time()*1000)}.png")
 
329
  screenshot = await asyncio.wait_for(v2_capture_screenshot(url, temp_img), timeout=10.0)
330
  if screenshot and os.path.exists(screenshot):
331
  result = v2_analyzer.analyze_image(screenshot)
@@ -337,10 +411,12 @@ async def unified_scan_endpoint(payload: UnifiedRequest):
337
  except Exception as e:
338
  v2_msg = f"V2 Vision Unavailable"
339
 
340
- # 4. Calculate the Master Risk Score
341
  if "Skipped" in v2_msg or "Unavailable" in v2_msg:
 
342
  master_score = (v1_score * 0.5) + (v4_score * 0.5)
343
  else:
 
344
  master_score = (v1_score * 0.4) + (v4_score * 0.4) + (v2_score * 0.2)
345
 
346
  latency = (time.perf_counter() - start_time) * 1000
@@ -349,7 +425,7 @@ async def unified_scan_endpoint(payload: UnifiedRequest):
349
  return {
350
  "composite_score": master_score,
351
  "latency_ms": round(latency, 2),
352
- "layer_scores": asdict(v1_matrix.layer_scores),
353
  "details": {
354
  "xgboost_analysis": {"score": v4_score, "message": v4_msg},
355
  "cnn_visual_analysis": {"score": v2_score, "message": v2_msg}
@@ -363,6 +439,7 @@ if __name__ == "__main__":
363
  print("[*] Resolving path mapping vectors securely...")
364
  print(f"[+] Directing Uvicorn to look inside application anchor: {CURRENT_BASE_DIR}")
365
 
 
366
  uvicorn.run(
367
  "main:app",
368
  host="0.0.0.0",
 
3
  import time
4
  import asyncio
5
  import importlib.util
6
+ import uvicorn # Programmatic bootstrapper
7
 
8
  # =====================================================================
9
+ # 1. CRITICAL PATH INJECTION FIX (Matched to Root Repository Layout)
10
  # =====================================================================
11
  CURRENT_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
12
+ VERSION_1_PATH = os.path.join(CURRENT_BASE_DIR, "Version_1")
13
+ # 🔥 FIXED: Added spaces around the hyphen to match your exact directory name
14
+ VERSION_2_PATH = os.path.join(CURRENT_BASE_DIR, "Version_2 - Copy")
15
+ VERSION_3_PATH = os.path.join(CURRENT_BASE_DIR, "Version_3")
16
+ VERSION_4_PATH = os.path.join(CURRENT_BASE_DIR, "Version_4")
17
+ VERSION_5_PATH = os.path.join(CURRENT_BASE_DIR, "Version_5")
18
 
19
+ for path in [CURRENT_BASE_DIR, VERSION_1_PATH, VERSION_2_PATH, VERSION_3_PATH, VERSION_4_PATH, VERSION_5_PATH]:
20
+ if os.path.exists(path) and path not in sys.path:
21
+ sys.path.insert(0, path)
 
 
 
22
 
23
  # =====================================================================
24
  # 2. FRAMEWORK & CORE ENGINE IMPORTS
 
28
  from fastapi import FastAPI, HTTPException, APIRouter
29
  from pydantic import BaseModel, Field
30
  from typing import Dict, Any
 
31
  from playwright.async_api import async_playwright
 
32
 
33
+ # Version 4 Imports
34
+ try:
35
+ from Version_4.features import extract_url_features
36
+ except ModuleNotFoundError:
37
+ from features import extract_url_features
38
+
39
+ # Version 5 Imports
40
+ try:
41
+ from Version_5.src.dom_scraper import extract_dom_features
42
+ from Version_5.src.sub_agents import (
43
+ agent_url_analyst,
44
+ agent_html_structure,
45
+ agent_content_semantics,
46
+ agent_brand_impersonation
47
+ )
48
+ from Version_5.src.orchestrator import evaluate_consensus, run_judge
49
+ except Exception as e:
50
+ print(f"[-] Warning: Could not import Version 5 modules: {e}")
51
+
52
+ # =====================================================================
53
+ # 3. INITIALIZE MASTER APP GATEWAY
54
+ # =====================================================================
55
+ from fastapi.middleware.cors import CORSMiddleware
56
  app = FastAPI(
57
  title="Master Threat Intelligence Hub Gateway",
58
+ description="The ultimate, fully unified routing architecture managing deployments: V1, V2, V3, V4, V5, and V6."
59
  )
60
 
61
+ # 🔥 UPDATED: Expanded CORS configurations to allow frontend origins (local, HF Space, and Netlify)
62
  app.add_middleware(
63
  CORSMiddleware,
64
+ allow_origins=[
65
+ "http://localhost:3000",
66
+ "http://127.0.0.1:3000",
67
+ "https://atharvawarade9807-duplicate.hf.space",
68
+ "https://*.netlify.app", # or your exact Netlify URL after deploy
69
+ ],
70
+ allow_credentials=False,
71
  allow_methods=["*"],
72
  allow_headers=["*"],
73
  )
74
 
75
+ OUTPUT_DIR = os.path.join(CURRENT_BASE_DIR, "output")
76
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
77
+
78
  # =====================================================================
79
+ # [ ENGINE 1 ] VERSION 1: LEGACY MODEL
80
  # =====================================================================
 
81
  try:
82
+ from Version_1.main import app as v1_app
83
  app.include_router(v1_app.router, tags=["Version 1: Legacy Model"])
84
  print("[+] Successfully merged Version 1 into root UI")
85
  except Exception as e:
 
86
  print(f"[-] Could not merge Version 1. Error: {e}")
87
 
88
  # =====================================================================
89
+ # [ ENGINE 2 ] VERSION 2: VISUAL RESNET18 PHISHING DETECTOR
90
  # =====================================================================
 
91
  v2_router = APIRouter(prefix="/api/v2", tags=["Version 2: Visual ResNet18 Engine"])
92
 
93
  class V2VisionRequest(BaseModel):
 
99
  try:
100
  print("[*] Attempting to load Version 2 visual engine...")
101
  v2_main_path = os.path.join(VERSION_2_PATH, "main.py")
102
+
103
+ spec = importlib.util.spec_from_file_location("v2_main", v2_main_path)
104
+ v2_main = importlib.util.module_from_spec(spec)
105
+ sys.modules["v2_main"] = v2_main
106
+ spec.loader.exec_module(v2_main)
107
+
108
+ V2_MODEL_PATH = os.path.join(VERSION_2_PATH, "models", "production_resnet_ema.pth")
109
+ v2_analyzer = v2_main.ProductionAnalyzer(model_path=V2_MODEL_PATH)
110
+ v2_capture_screenshot = v2_main.capture_screenshot
111
+ print("[+] Successfully initialized Version 2 ResNet18 visual engine.")
112
  except Exception as e:
113
  print(f"[-] Could not load Version 2 visual engine. Error: {e}")
114
 
 
126
 
127
  try:
128
  screenshot_file = await v2_capture_screenshot(url, temp_img_path)
129
+
130
  if not screenshot_file or not os.path.exists(screenshot_file):
131
+ raise HTTPException(status_code=502, detail="Failed to capture screenshot. The target may be offline.")
132
 
133
  result = v2_analyzer.analyze_image(screenshot_file)
134
  latency_ms = (time.perf_counter() - start_time) * 1000
 
136
  if "error" in result:
137
  raise HTTPException(status_code=500, detail=result["error"])
138
 
139
+ verdict = "QUARANTINE" if result["prediction"] == "Phishing" else "PASS"
140
+
141
  return {
142
  "target_url": url,
143
+ "verdict": verdict,
144
  "raw_prediction": result["prediction"],
145
  "confidence": result["confidence"],
146
  "latency_ms": round(latency_ms, 2)
 
152
  app.include_router(v2_router)
153
 
154
  # =====================================================================
155
+ # [ ENGINE 3 ] VERSION 3: LEGACY ONNX MODEL
156
  # =====================================================================
157
  try:
158
  print("[*] Attempting to load Version 3...")
159
  from Version_3.main import app as v3_app
160
  app.mount("/scan", v3_app, name="v3_email")
161
  print("[+] Successfully merged Version 3 email scanner")
162
+ print("[+] Successfully merged Version 3 into root UI")
163
  except Exception as e:
164
  print(f"[-] Could not merge Version 3. Error: {e}")
165
 
 
171
  class XGBoostRequest(BaseModel):
172
  url: str = Field(..., description="Target URL to evaluate via structural feature mapping", example="google.com")
173
 
 
174
  MODEL_PATH = os.path.join(VERSION_4_PATH, "models", "final_model.json")
175
  if os.path.exists(MODEL_PATH):
176
  bst = xgb.Booster()
 
179
  bst = None
180
  print(f"[-] Warning: {MODEL_PATH} not found. V4 engine will return a configuration error.")
181
 
 
 
 
 
 
 
 
 
182
  @v4_router.post("/evaluate")
183
  async def evaluate_xgboost_url(payload: XGBoostRequest):
184
  if bst is None:
 
196
  features = extract_url_features(processed_url)
197
  dmatrix_payload = xgb.DMatrix(np.array([features]))
198
 
199
+ # This is the "Safeness" probability (1.0 = Safe, 0.0 = Phishing)
200
  safeness_prob = float(bst.predict(dmatrix_payload)[0])
201
  latency_ms = (time.perf_counter() - start_time) * 1000
202
 
203
+ # INVERTED LOGIC: Low safeness means high phishing risk
204
  if safeness_prob < 0.50:
205
  verdict = "🚨 PHISHING DETECTED"
206
  confidence = (1.0 - safeness_prob) * 100
 
217
  }
218
  except Exception as e:
219
  raise HTTPException(status_code=500, detail=f"Structural evaluation failure: {str(e)}")
 
220
  app.include_router(v4_router)
221
 
222
  # =====================================================================
223
  # [ ENGINE 5 ] VERSION 5: AGENTIC MULTI-SPECIALIST FORENSIC PANEL
224
  # =====================================================================
225
+ v5_router = APIRouter(prefix="/api/v5", tags=["Version 5: Agentic Panel"])
226
+
227
+ class ThreatAnalysisRequest(BaseModel):
228
+ url: str = Field(..., description="Target landing page URL to analyze", example="http://example-verify-login.com")
229
+ sender: str = Field(..., description="Alleged sender address header", example="security@paypal.com")
230
+ email_body: str = Field(..., description="Full text/body payload of the incoming message")
231
+
232
+ @v5_router.post("/predict")
233
+ async def analyze_payload_endpoint(payload: ThreatAnalysisRequest):
234
+ url = payload.url.strip()
235
+ sender = payload.sender.strip()
236
+ email_body = payload.email_body.strip()
237
 
238
+ if not url and not email_body:
239
+ raise HTTPException(status_code=400, detail="Provide at least a validation URL or a message body.")
240
+
241
+ try:
242
+ start_time = time.perf_counter()
243
+ dom_data = extract_dom_features(url)
244
+
245
+ reports = {
246
+ "URL_Agent": agent_url_analyst(url),
247
+ "HTML_Agent": agent_html_structure(dom_data),
248
+ "Content_Agent": agent_content_semantics(email_body),
249
+ "Brand_Agent": agent_brand_impersonation(email_body, sender)
250
+ }
251
+
252
+ consensus_victory = evaluate_consensus(reports)
253
+
254
+ if consensus_victory:
255
+ final_verdict = {
256
+ "verdict": reports["URL_Agent"].claim if hasattr(reports["URL_Agent"], 'claim') else str(reports["URL_Agent"]),
257
+ "confidence_score": reports["URL_Agent"].confidence if hasattr(reports["URL_Agent"], 'confidence') else 1.0,
258
+ "justification": "Bypassed judicial review due to absolute sub-agent unanimity across forensics."
259
+ }
260
+ else:
261
+ reports_str = "\n".join([f"[{name}]\n{r.model_dump_json(indent=2) if hasattr(r, 'model_dump_json') else str(r)}" for name, r in reports.items()])
262
+ raw_data = f"Target URL: {url}\nTarget Sender: {sender}\nBody: {email_body}"
263
+ judge_verdict = run_judge(reports_summary=reports_str, raw_data=raw_data)
264
+ final_verdict = judge_verdict.model_dump() if hasattr(judge_verdict, 'model_dump') else judge_verdict
265
+
266
+ latency_ms = (time.perf_counter() - start_time) * 1000
267
+
268
+ serializable_reports = {}
269
+ for name, report in reports.items():
270
+ serializable_reports[name] = report.model_dump() if hasattr(report, "model_dump") else str(report)
271
+
272
+ return {
273
+ "target_url": url,
274
+ "target_sender": sender,
275
+ "consensus_reached": consensus_victory,
276
+ "latency_ms": round(latency_ms, 2),
277
+ "sub_agent_claims": serializable_reports,
278
+ "final_evaluation": final_verdict
279
+ }
280
+ except Exception as e:
281
+ raise HTTPException(status_code=500, detail=f"Internal agent execution crash: {str(e)}")
282
+
283
+ app.include_router(v5_router)
284
 
285
  # =====================================================================
286
  # [ ENGINE 6 ] VERSION 6: INTERACTIVE THREAT COGNITIVE SANDBOX
 
355
  "loaded_engines": ["V1", "V2", "V3", "V4", "V5", "V6"]
356
  }
357
 
358
+
359
  # =====================================================================
360
  # [ MASTER FUSION ENGINE ] COMBINES V1, V2 (CNN), and V4 (XGBoost)
361
  # =====================================================================
362
+ from Version_1.main import process_payload as v1_process
363
+ from dataclasses import asdict
364
+
365
  class UnifiedRequest(BaseModel):
366
  url: str
367
 
 
373
 
374
  start_time = time.perf_counter()
375
 
376
+ # 1. Run V1 (Heuristics)
377
  v1_matrix = await v1_process(url)
378
  v1_score = v1_matrix.composite_score
379
 
 
386
  dmatrix_payload = xgb.DMatrix(np.array([features]))
387
 
388
  raw_safeness = float(bst.predict(dmatrix_payload)[0])
389
+
390
+ # INVERT TO RISK: If safeness is 0.99 (Google), risk becomes 0.01 (1%)
391
  v4_score = 1.0 - raw_safeness
392
+
393
  v4_msg = f"XGBoost Match: {round(v4_score * 100, 1)}% Phishing Risk"
394
  except Exception as e:
395
  v4_msg = f"V4 Error: {str(e)}"
 
396
  # 3. Run V2 (CNN ResNet18) - Wrapped securely to prevent timeout crashes
397
  v2_score = 0.0
398
  v2_msg = "Skipped (Timeout Prevention)"
399
  try:
400
  if v2_analyzer is not None and v2_capture_screenshot is not None:
401
  temp_img = os.path.join(OUTPUT_DIR, f"fusion_{int(time.time()*1000)}.png")
402
+ # Set a strict timeout so Playwright doesn't hang the server
403
  screenshot = await asyncio.wait_for(v2_capture_screenshot(url, temp_img), timeout=10.0)
404
  if screenshot and os.path.exists(screenshot):
405
  result = v2_analyzer.analyze_image(screenshot)
 
411
  except Exception as e:
412
  v2_msg = f"V2 Vision Unavailable"
413
 
414
+ # 4. Calculate the Master Risk Score (Dynamic Weights)
415
  if "Skipped" in v2_msg or "Unavailable" in v2_msg:
416
+ # If CNN times out, re-balance weights so a dummy 0.0 doesn't dilute the risk
417
  master_score = (v1_score * 0.5) + (v4_score * 0.5)
418
  else:
419
+ # Standard 40/40/20 split
420
  master_score = (v1_score * 0.4) + (v4_score * 0.4) + (v2_score * 0.2)
421
 
422
  latency = (time.perf_counter() - start_time) * 1000
 
425
  return {
426
  "composite_score": master_score,
427
  "latency_ms": round(latency, 2),
428
+ "layer_scores": asdict(v1_matrix.layer_scores), # Safely unpacks the dataclass
429
  "details": {
430
  "xgboost_analysis": {"score": v4_score, "message": v4_msg},
431
  "cnn_visual_analysis": {"score": v2_score, "message": v2_msg}
 
439
  print("[*] Resolving path mapping vectors securely...")
440
  print(f"[+] Directing Uvicorn to look inside application anchor: {CURRENT_BASE_DIR}")
441
 
442
+ # 🔥 FIXED: Points directly to port 7860 for Hugging Face standard routing context
443
  uvicorn.run(
444
  "main:app",
445
  host="0.0.0.0",