Che237 commited on
Commit
5185c0d
·
verified ·
1 Parent(s): 8966ebc

Update the CyberForge ML service

Browse files
Files changed (1) hide show
  1. app.py +67 -0
app.py CHANGED
@@ -16,6 +16,7 @@ import subprocess
16
  import sys
17
  import threading
18
  import time
 
19
  from pathlib import Path
20
  from datetime import datetime
21
  import logging
@@ -1318,12 +1319,77 @@ async def api_analyze(request: Request):
1318
  return as_reasoning_text(result)
1319
 
1320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1321
  @api.post("/analyze-url")
1322
  async def api_analyze_url(request: Request):
1323
  """URL analysis – called by backend mlService.analyzeWebsite()
1324
  Returns per-model predictions + unified aggregate with risk_score (0-100).
1325
  All models now consistently include threat_score (probability of class-1).
1326
  """
 
1327
  body = await json_object(request)
1328
  url = text_field(body, "url")
1329
  if not url:
@@ -1384,6 +1450,7 @@ async def api_analyze_url(request: Request):
1384
  "model_predictions": predictions,
1385
  "features_analyzed": features,
1386
  "timestamp": datetime.utcnow().isoformat(),
 
1387
  }
1388
 
1389
 
 
16
  import sys
17
  import threading
18
  import time
19
+ import uuid
20
  from pathlib import Path
21
  from datetime import datetime
22
  import logging
 
1319
  return as_reasoning_text(result)
1320
 
1321
 
1322
+ ANALYSIS_SCHEMA = "cyberforge.ml.analysis.v1"
1323
+ VERDICT_POLICY = "trained_models_v1"
1324
+
1325
+
1326
+ def analysis_record(target: str, predictions: Dict, started: float) -> Dict:
1327
+ """The versioned record of one analysis (V2-ML02), beside the older fields.
1328
+
1329
+ Verdict policy trained_models_v1: "threat" when any trained model answered
1330
+ and flagged the target, "benign" when trained models answered and none
1331
+ flagged it, "unknown" when no trained model answered (rule-based estimates
1332
+ only). Scores are each model's output rescaled at its decision threshold:
1333
+ raw scores, not calibrated probabilities, so no probability is given."""
1334
+ detectors, warnings = [], []
1335
+ for name, p in predictions.items():
1336
+ trained = p.get("inference_source") == "ml_model"
1337
+ meta = ml_loader.metadata.get(name, {}) if trained else {}
1338
+ if "error" in p:
1339
+ verdict = "unknown"
1340
+ else:
1341
+ verdict = "threat" if p.get("prediction") == 1 else "benign"
1342
+ detectors.append({
1343
+ "id": name,
1344
+ "engine": "trained_model" if trained else "heuristic",
1345
+ "verdict": verdict,
1346
+ "raw_score": p.get("threat_score"),
1347
+ "calibrated_probability": None,
1348
+ "trained_at": meta.get("trained_at"),
1349
+ "decision_threshold": meta.get("decision_threshold"),
1350
+ "feature_set": meta.get("feature_set"),
1351
+ "missing_features": p.get("missing_features"),
1352
+ })
1353
+ if not trained:
1354
+ why = p.get("note") or ("features were missing" if p.get("missing_features") else "the model is not loaded")
1355
+ warnings.append(f"{name}: rule-based estimate, not the trained model ({why})")
1356
+ answered = [d for d in detectors if d["engine"] == "trained_model" and d["verdict"] != "unknown"]
1357
+ if not answered:
1358
+ verdict, reason = "unknown", "no trained model answered; only rule-based estimates are available"
1359
+ elif any(d["verdict"] == "threat" for d in answered):
1360
+ flagged = [d["id"] for d in answered if d["verdict"] == "threat"]
1361
+ verdict, reason = "threat", f"flagged by {', '.join(flagged)}"
1362
+ else:
1363
+ verdict, reason = "benign", f"{len(answered)} trained model(s) answered and none flagged it"
1364
+ warnings.append("scores are model outputs rescaled at each decision threshold, not calibrated probabilities")
1365
+ return {
1366
+ "schema": ANALYSIS_SCHEMA,
1367
+ "analysis_id": uuid.uuid4().hex,
1368
+ "task": "url",
1369
+ "target": target,
1370
+ "verdict": verdict,
1371
+ "verdict_reason": reason,
1372
+ "policy": VERDICT_POLICY,
1373
+ "coverage": {
1374
+ "detectors": len(detectors),
1375
+ "trained_models_answered": len(answered),
1376
+ "heuristic_estimates": sum(1 for d in detectors if d["engine"] == "heuristic"),
1377
+ "reputation_list_loaded": popular_domains_loaded(),
1378
+ },
1379
+ "detectors": detectors,
1380
+ "service_version": SERVICE_VERSION,
1381
+ "duration_ms": round((time.perf_counter() - started) * 1000, 1),
1382
+ "warnings": warnings,
1383
+ }
1384
+
1385
+
1386
  @api.post("/analyze-url")
1387
  async def api_analyze_url(request: Request):
1388
  """URL analysis – called by backend mlService.analyzeWebsite()
1389
  Returns per-model predictions + unified aggregate with risk_score (0-100).
1390
  All models now consistently include threat_score (probability of class-1).
1391
  """
1392
+ started = time.perf_counter()
1393
  body = await json_object(request)
1394
  url = text_field(body, "url")
1395
  if not url:
 
1450
  "model_predictions": predictions,
1451
  "features_analyzed": features,
1452
  "timestamp": datetime.utcnow().isoformat(),
1453
+ "analysis": analysis_record(url, predictions, started),
1454
  }
1455
 
1456