nat-engine 1__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.
Files changed (299) hide show
  1. mannf/__init__.py +33 -0
  2. mannf/__main__.py +10 -0
  3. mannf/_version.py +8 -0
  4. mannf/agents/__init__.py +7 -0
  5. mannf/agents/analyzer_agent.py +9 -0
  6. mannf/agents/base.py +9 -0
  7. mannf/agents/bdi_agent.py +9 -0
  8. mannf/agents/belief_state.py +9 -0
  9. mannf/agents/coordinator_agent.py +9 -0
  10. mannf/agents/executor_agent.py +9 -0
  11. mannf/agents/monitor_agent.py +9 -0
  12. mannf/agents/oracle_agent.py +9 -0
  13. mannf/agents/planner_agent.py +9 -0
  14. mannf/agents/test_agent.py +9 -0
  15. mannf/anomaly/__init__.py +7 -0
  16. mannf/anomaly/enhanced_detector.py +9 -0
  17. mannf/cli.py +9 -0
  18. mannf/core/__init__.py +26 -0
  19. mannf/core/agents/__init__.py +52 -0
  20. mannf/core/agents/accessibility_scanner_agent.py +245 -0
  21. mannf/core/agents/analyzer_agent.py +224 -0
  22. mannf/core/agents/autonomous_loop_agent.py +1086 -0
  23. mannf/core/agents/autonomous_loop_models.py +62 -0
  24. mannf/core/agents/autonomous_run_differ.py +427 -0
  25. mannf/core/agents/base.py +128 -0
  26. mannf/core/agents/bdi_agent.py +330 -0
  27. mannf/core/agents/belief_state.py +202 -0
  28. mannf/core/agents/browser_coordinator_agent.py +224 -0
  29. mannf/core/agents/browser_executor_agent.py +410 -0
  30. mannf/core/agents/coordinator_agent.py +262 -0
  31. mannf/core/agents/executor_agent.py +222 -0
  32. mannf/core/agents/monitor_agent.py +188 -0
  33. mannf/core/agents/oracle_agent.py +150 -0
  34. mannf/core/agents/performance_testing_agent.py +279 -0
  35. mannf/core/agents/planner_agent.py +128 -0
  36. mannf/core/agents/test_agent.py +249 -0
  37. mannf/core/agents/visual_regression_agent.py +311 -0
  38. mannf/core/agents/web_crawler_agent.py +510 -0
  39. mannf/core/agents/worker_pool.py +366 -0
  40. mannf/core/anomaly/__init__.py +14 -0
  41. mannf/core/anomaly/enhanced_detector.py +541 -0
  42. mannf/core/browser/__init__.py +63 -0
  43. mannf/core/browser/accessibility_scanner.py +424 -0
  44. mannf/core/browser/discovery_model.py +178 -0
  45. mannf/core/browser/dom_snapshot.py +349 -0
  46. mannf/core/browser/ingestor_bridge.py +371 -0
  47. mannf/core/browser/performance_metrics.py +217 -0
  48. mannf/core/browser/reflection_analyzer.py +442 -0
  49. mannf/core/browser/scenario_generator.py +1100 -0
  50. mannf/core/browser/security_scenario_generator.py +695 -0
  51. mannf/core/browser/visual_comparer.py +159 -0
  52. mannf/core/diagnostics/__init__.py +28 -0
  53. mannf/core/diagnostics/failure_clusterer.py +211 -0
  54. mannf/core/diagnostics/flake_detector.py +233 -0
  55. mannf/core/diagnostics/root_cause_analyzer.py +273 -0
  56. mannf/core/distributed/__init__.py +16 -0
  57. mannf/core/distributed/endpoint.py +139 -0
  58. mannf/core/distributed/system_under_test.py +207 -0
  59. mannf/core/functional_orchestrator.py +428 -0
  60. mannf/core/messaging/__init__.py +11 -0
  61. mannf/core/messaging/bus.py +113 -0
  62. mannf/core/messaging/messages.py +89 -0
  63. mannf/core/nat_orchestrator.py +342 -0
  64. mannf/core/neural/__init__.py +183 -0
  65. mannf/core/orchestrator.py +272 -0
  66. mannf/core/prioritization/__init__.py +17 -0
  67. mannf/core/prioritization/adaptive_controller.py +509 -0
  68. mannf/core/prioritization/belief_prioritizer.py +231 -0
  69. mannf/core/prioritization/risk_scorer.py +430 -0
  70. mannf/core/reporting/__init__.py +12 -0
  71. mannf/core/reporting/unified_report.py +664 -0
  72. mannf/core/testing/__init__.py +17 -0
  73. mannf/core/testing/adaptive_controller.py +149 -0
  74. mannf/core/testing/models.py +179 -0
  75. mannf/core/validation/__init__.py +10 -0
  76. mannf/core/validation/self_validation_runner.py +180 -0
  77. mannf/dashboard/__init__.py +7 -0
  78. mannf/dashboard/app.py +9 -0
  79. mannf/dashboard/models.py +9 -0
  80. mannf/dashboard/static/index.html +2538 -0
  81. mannf/dashboard/telemetry.py +9 -0
  82. mannf/distributed/__init__.py +7 -0
  83. mannf/distributed/endpoint.py +9 -0
  84. mannf/distributed/system_under_test.py +9 -0
  85. mannf/healing/__init__.py +7 -0
  86. mannf/healing/graphql_schema_diff.py +9 -0
  87. mannf/healing/healer.py +9 -0
  88. mannf/healing/models.py +9 -0
  89. mannf/healing/schema_diff.py +9 -0
  90. mannf/integrations/__init__.py +7 -0
  91. mannf/integrations/auth.py +9 -0
  92. mannf/integrations/graphql_parser.py +9 -0
  93. mannf/integrations/graphql_sut.py +9 -0
  94. mannf/integrations/http_sut.py +9 -0
  95. mannf/integrations/openapi_parser.py +9 -0
  96. mannf/integrations/postman_parser.py +9 -0
  97. mannf/llm/__init__.py +7 -0
  98. mannf/llm/anthropic_provider.py +9 -0
  99. mannf/llm/base.py +9 -0
  100. mannf/llm/config.py +9 -0
  101. mannf/llm/factory.py +9 -0
  102. mannf/llm/openai_provider.py +9 -0
  103. mannf/llm/prompts.py +9 -0
  104. mannf/messaging/__init__.py +7 -0
  105. mannf/messaging/bus.py +9 -0
  106. mannf/messaging/messages.py +9 -0
  107. mannf/nat_orchestrator.py +9 -0
  108. mannf/neural/__init__.py +7 -0
  109. mannf/orchestrator.py +9 -0
  110. mannf/prioritization/__init__.py +7 -0
  111. mannf/prioritization/adaptive_controller.py +9 -0
  112. mannf/prioritization/belief_prioritizer.py +9 -0
  113. mannf/prioritization/risk_scorer.py +9 -0
  114. mannf/product/__init__.py +29 -0
  115. mannf/product/admin/__init__.py +3 -0
  116. mannf/product/admin/routes.py +514 -0
  117. mannf/product/auth/__init__.py +5 -0
  118. mannf/product/auth/saml.py +212 -0
  119. mannf/product/billing/__init__.py +5 -0
  120. mannf/product/billing/audit.py +160 -0
  121. mannf/product/billing/feature_gates.py +180 -0
  122. mannf/product/billing/metering.py +179 -0
  123. mannf/product/billing/notifications.py +181 -0
  124. mannf/product/billing/plans.py +133 -0
  125. mannf/product/billing/rate_limits.py +35 -0
  126. mannf/product/billing/stripe_billing.py +906 -0
  127. mannf/product/billing/tenant_auth.py +233 -0
  128. mannf/product/billing/tenant_manager.py +873 -0
  129. mannf/product/cli.py +3900 -0
  130. mannf/product/cli_admin.py +408 -0
  131. mannf/product/dashboard/__init__.py +61 -0
  132. mannf/product/dashboard/app.py +3567 -0
  133. mannf/product/dashboard/models.py +460 -0
  134. mannf/product/dashboard/static/index.html +6347 -0
  135. mannf/product/dashboard/static/manifest.json +25 -0
  136. mannf/product/dashboard/static/pwa-icon-192.png +0 -0
  137. mannf/product/dashboard/static/pwa-icon-512.png +0 -0
  138. mannf/product/dashboard/static/sw.js +64 -0
  139. mannf/product/dashboard/telemetry.py +547 -0
  140. mannf/product/database.py +145 -0
  141. mannf/product/demo.py +844 -0
  142. mannf/product/doctor.py +509 -0
  143. mannf/product/exporters/__init__.py +65 -0
  144. mannf/product/exporters/azuredevops_exporter.py +257 -0
  145. mannf/product/exporters/base.py +307 -0
  146. mannf/product/exporters/bugzilla_exporter.py +200 -0
  147. mannf/product/exporters/dedup.py +275 -0
  148. mannf/product/exporters/finding_adapter.py +216 -0
  149. mannf/product/exporters/github_exporter.py +197 -0
  150. mannf/product/exporters/gitlab_exporter.py +215 -0
  151. mannf/product/exporters/jira_exporter.py +180 -0
  152. mannf/product/exporters/linear_exporter.py +195 -0
  153. mannf/product/exporters/loader.py +233 -0
  154. mannf/product/exporters/pagerduty_exporter.py +363 -0
  155. mannf/product/exporters/sentry_exporter.py +322 -0
  156. mannf/product/exporters/servicenow_exporter.py +240 -0
  157. mannf/product/exporters/shortcut_exporter.py +231 -0
  158. mannf/product/exporters/webhook_exporter.py +383 -0
  159. mannf/product/formatters/__init__.py +18 -0
  160. mannf/product/formatters/allure_formatter.py +161 -0
  161. mannf/product/formatters/ctrf_formatter.py +149 -0
  162. mannf/product/healing/__init__.py +30 -0
  163. mannf/product/healing/graphql_schema_diff.py +152 -0
  164. mannf/product/healing/healer.py +141 -0
  165. mannf/product/healing/models.py +175 -0
  166. mannf/product/healing/schema_diff.py +251 -0
  167. mannf/product/ingestors/__init__.py +77 -0
  168. mannf/product/ingestors/base.py +256 -0
  169. mannf/product/ingestors/bgstm_ingestor.py +764 -0
  170. mannf/product/ingestors/curl_ingestor.py +1019 -0
  171. mannf/product/ingestors/cypress_ingestor.py +487 -0
  172. mannf/product/ingestors/gherkin_ingestor.py +967 -0
  173. mannf/product/ingestors/graphql_ingestor.py +845 -0
  174. mannf/product/ingestors/grpc_ingestor.py +591 -0
  175. mannf/product/ingestors/har_ingestor.py +976 -0
  176. mannf/product/ingestors/loader.py +284 -0
  177. mannf/product/ingestors/models.py +146 -0
  178. mannf/product/ingestors/openapi_ingestor.py +606 -0
  179. mannf/product/ingestors/playwright_ingestor.py +449 -0
  180. mannf/product/ingestors/postman_ingestor.py +631 -0
  181. mannf/product/ingestors/traffic_ingestor.py +679 -0
  182. mannf/product/ingestors/websocket_ingestor.py +526 -0
  183. mannf/product/integrations/__init__.py +21 -0
  184. mannf/product/integrations/auth.py +190 -0
  185. mannf/product/integrations/graphql_parser.py +436 -0
  186. mannf/product/integrations/graphql_sut.py +247 -0
  187. mannf/product/integrations/grpc_sut.py +469 -0
  188. mannf/product/integrations/http_sut.py +237 -0
  189. mannf/product/integrations/kafka_adapter.py +342 -0
  190. mannf/product/integrations/openapi_parser.py +513 -0
  191. mannf/product/integrations/postman_parser.py +467 -0
  192. mannf/product/integrations/webhook_receiver.py +344 -0
  193. mannf/product/integrations/websocket_sut.py +434 -0
  194. mannf/product/llm/__init__.py +25 -0
  195. mannf/product/llm/anthropic_provider.py +94 -0
  196. mannf/product/llm/base.py +267 -0
  197. mannf/product/llm/config.py +48 -0
  198. mannf/product/llm/factory.py +42 -0
  199. mannf/product/llm/openai_provider.py +93 -0
  200. mannf/product/llm/prompts.py +403 -0
  201. mannf/product/llm/root_cause_service.py +311 -0
  202. mannf/product/llm/test_plan_models.py +78 -0
  203. mannf/product/metrics.py +149 -0
  204. mannf/product/middleware/__init__.py +3 -0
  205. mannf/product/middleware/audit_middleware.py +112 -0
  206. mannf/product/middleware/tenant_isolation.py +114 -0
  207. mannf/product/models.py +347 -0
  208. mannf/product/notifications/__init__.py +24 -0
  209. mannf/product/notifications/dispatcher.py +411 -0
  210. mannf/product/onboarding.py +190 -0
  211. mannf/product/orchestration/__init__.py +39 -0
  212. mannf/product/orchestration/ingest_scan_orchestrator.py +339 -0
  213. mannf/product/orchestration/pipeline.py +401 -0
  214. mannf/product/orchestrator.py +987 -0
  215. mannf/product/orchestrator_models.py +269 -0
  216. mannf/product/regression/__init__.py +36 -0
  217. mannf/product/regression/differ.py +172 -0
  218. mannf/product/regression/masking.py +100 -0
  219. mannf/product/regression/models.py +232 -0
  220. mannf/product/regression/recorder.py +124 -0
  221. mannf/product/regression/replayer.py +168 -0
  222. mannf/product/reports/__init__.py +10 -0
  223. mannf/product/reports/pdf.py +132 -0
  224. mannf/product/scheduling/__init__.py +57 -0
  225. mannf/product/scheduling/cron_utils.py +251 -0
  226. mannf/product/scheduling/engine.py +473 -0
  227. mannf/product/scheduling/models.py +86 -0
  228. mannf/product/scheduling/queue.py +894 -0
  229. mannf/product/scheduling/store.py +235 -0
  230. mannf/product/security/__init__.py +21 -0
  231. mannf/product/security/belief_guided.py +143 -0
  232. mannf/product/security/checks/__init__.py +55 -0
  233. mannf/product/security/checks/base.py +69 -0
  234. mannf/product/security/checks/bfla.py +77 -0
  235. mannf/product/security/checks/bola.py +77 -0
  236. mannf/product/security/checks/bopla.py +80 -0
  237. mannf/product/security/checks/broken_auth.py +86 -0
  238. mannf/product/security/checks/graphql_security.py +299 -0
  239. mannf/product/security/checks/inventory.py +70 -0
  240. mannf/product/security/checks/misconfig.py +158 -0
  241. mannf/product/security/checks/resource_consumption.py +70 -0
  242. mannf/product/security/checks/sensitive_flows.py +80 -0
  243. mannf/product/security/checks/ssrf.py +101 -0
  244. mannf/product/security/checks/unsafe_consumption.py +120 -0
  245. mannf/product/security/models.py +92 -0
  246. mannf/product/security/plugin_loader.py +182 -0
  247. mannf/product/security/reporter.py +92 -0
  248. mannf/product/security/scanner.py +183 -0
  249. mannf/product/server.py +6220 -0
  250. mannf/product/setup_wizard.py +873 -0
  251. mannf/product/status.py +404 -0
  252. mannf/product/storage/__init__.py +10 -0
  253. mannf/product/storage/artifact_store.py +343 -0
  254. mannf/product/telemetry.py +300 -0
  255. mannf/product/uninstall.py +169 -0
  256. mannf/product/upgrade.py +139 -0
  257. mannf/product/weights/__init__.py +13 -0
  258. mannf/product/weights/blob_store.py +299 -0
  259. mannf/product/weights/factory.py +42 -0
  260. mannf/product/weights/registry.py +159 -0
  261. mannf/product/weights/store.py +210 -0
  262. mannf/regression/__init__.py +7 -0
  263. mannf/regression/differ.py +9 -0
  264. mannf/regression/masking.py +9 -0
  265. mannf/regression/models.py +9 -0
  266. mannf/regression/recorder.py +9 -0
  267. mannf/regression/replayer.py +9 -0
  268. mannf/security/__init__.py +7 -0
  269. mannf/security/belief_guided.py +9 -0
  270. mannf/security/checks/__init__.py +7 -0
  271. mannf/security/checks/base.py +9 -0
  272. mannf/security/checks/bfla.py +9 -0
  273. mannf/security/checks/bola.py +9 -0
  274. mannf/security/checks/bopla.py +9 -0
  275. mannf/security/checks/broken_auth.py +9 -0
  276. mannf/security/checks/graphql_security.py +9 -0
  277. mannf/security/checks/inventory.py +9 -0
  278. mannf/security/checks/misconfig.py +9 -0
  279. mannf/security/checks/resource_consumption.py +9 -0
  280. mannf/security/checks/sensitive_flows.py +9 -0
  281. mannf/security/checks/ssrf.py +9 -0
  282. mannf/security/checks/unsafe_consumption.py +9 -0
  283. mannf/security/models.py +9 -0
  284. mannf/security/reporter.py +9 -0
  285. mannf/security/scanner.py +9 -0
  286. mannf/server.py +9 -0
  287. mannf/testing/__init__.py +7 -0
  288. mannf/testing/adaptive_controller.py +9 -0
  289. mannf/testing/models.py +9 -0
  290. mannf/weights/__init__.py +7 -0
  291. mannf/weights/registry.py +9 -0
  292. mannf/weights/store.py +9 -0
  293. nat_engine-1.dist-info/METADATA +555 -0
  294. nat_engine-1.dist-info/RECORD +299 -0
  295. nat_engine-1.dist-info/WHEEL +5 -0
  296. nat_engine-1.dist-info/entry_points.txt +4 -0
  297. nat_engine-1.dist-info/licenses/LICENSE +651 -0
  298. nat_engine-1.dist-info/licenses/NOTICE +178 -0
  299. nat_engine-1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,92 @@
1
+ # Copyright (C) 2026 Brad Guider
2
+ # This file is part of NAT (Neural Agent Testing Framework).
3
+ # Licensed under the AGPL-3.0. See LICENSE for details.
4
+ # Commercial licensing available — see COMMERCIAL_LICENSE.md.
5
+
6
+ """Security report output formatters."""
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+
12
+ from mannf.product.security.models import SecurityFinding, SecurityReport, _severity_order
13
+
14
+ _SEVERITY_EMOJI = {
15
+ "critical": "🔴",
16
+ "high": "🟠",
17
+ "medium": "🟡",
18
+ "low": "🔵",
19
+ "info": "ℹ️",
20
+ }
21
+
22
+
23
+ class SecurityReporter:
24
+ def to_json(self, report: SecurityReport) -> str:
25
+ return json.dumps(report.to_dict(), indent=2)
26
+
27
+ def to_markdown(self, report: SecurityReport) -> str:
28
+ lines: list[str] = []
29
+
30
+ lines.append("# NAT Security Scan Report")
31
+ lines.append("")
32
+ lines.append(f"**Scan ID:** `{report.scan_id}` ")
33
+ lines.append(f"**Timestamp:** {report.timestamp} ")
34
+ lines.append(f"**Target:** {report.target_url} ")
35
+ lines.append(f"**Endpoints scanned:** {report.total_endpoints_scanned} ")
36
+ lines.append(f"**Belief-guided:** {report.belief_guided} ")
37
+ lines.append("")
38
+
39
+ lines.append("## Summary")
40
+ lines.append("")
41
+ lines.append("| Severity | Count |")
42
+ lines.append("|----------|-------|")
43
+ for sev in sorted(_severity_order, key=lambda s: _severity_order[s]):
44
+ count = report.summary.get(sev, 0)
45
+ emoji = _SEVERITY_EMOJI.get(sev, "")
46
+ lines.append(f"| {emoji} {sev.capitalize()} | {count} |")
47
+ lines.append("")
48
+
49
+ total = len(report.findings)
50
+ lines.append(f"**Total findings:** {total}")
51
+ lines.append("")
52
+
53
+ if not report.findings:
54
+ lines.append("_No findings above threshold._")
55
+ return "\n".join(lines)
56
+
57
+ lines.append("## Findings")
58
+ lines.append("")
59
+
60
+ by_severity: dict[str, list[SecurityFinding]] = {}
61
+ for f in report.findings:
62
+ by_severity.setdefault(f.severity, []).append(f)
63
+
64
+ for sev in sorted(_severity_order, key=lambda s: _severity_order[s]):
65
+ group = by_severity.get(sev, [])
66
+ if not group:
67
+ continue
68
+ emoji = _SEVERITY_EMOJI.get(sev, "")
69
+ lines.append(f"### {emoji} {sev.capitalize()} ({len(group)})")
70
+ lines.append("")
71
+
72
+ for finding in group:
73
+ lines.append(f"#### {finding.title}")
74
+ lines.append("")
75
+ lines.append(f"- **Check:** {finding.check_id} — {finding.check_name}")
76
+ lines.append(f"- **Endpoint:** `{finding.endpoint}`")
77
+ lines.append(f"- **CWE:** {finding.cwe_id}")
78
+ lines.append(f"- **Confidence:** {finding.confidence:.0%}")
79
+ lines.append("")
80
+ lines.append(f"**Description:** {finding.description}")
81
+ lines.append("")
82
+ lines.append("**Evidence:**")
83
+ lines.append("```")
84
+ lines.append(finding.evidence)
85
+ lines.append("```")
86
+ lines.append("")
87
+ lines.append(f"**Remediation:** {finding.remediation}")
88
+ lines.append("")
89
+ lines.append("---")
90
+ lines.append("")
91
+
92
+ return "\n".join(lines)
@@ -0,0 +1,183 @@
1
+ # Copyright (C) 2026 Brad Guider
2
+ # This file is part of NAT (Neural Agent Testing Framework).
3
+ # Licensed under the AGPL-3.0. See LICENSE for details.
4
+ # Commercial licensing available — see COMMERCIAL_LICENSE.md.
5
+
6
+ """Main SecurityScanner orchestrator."""
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import uuid
12
+ from datetime import datetime, timezone
13
+ from typing import TYPE_CHECKING
14
+
15
+ import httpx
16
+
17
+ from mannf.product.security.belief_guided import prioritize_for_security
18
+ from mannf.product.security.checks import (
19
+ BFLACheck,
20
+ BOLACheck,
21
+ BOPLACheck,
22
+ BrokenAuthCheck,
23
+ EndpointInfo,
24
+ InventoryCheck,
25
+ MisconfigCheck,
26
+ ResourceConsumptionCheck,
27
+ SecurityCheck,
28
+ SensitiveFlowsCheck,
29
+ SSRFCheck,
30
+ UnsafeConsumptionCheck,
31
+ )
32
+ from mannf.product.security.models import EndpointSecurityPriority, SecurityFinding, SecurityReport, _severity_order
33
+
34
+ if TYPE_CHECKING:
35
+ from mannf.core.agents.belief_state import BeliefState
36
+ from mannf.core.prioritization.risk_scorer import RiskScorer
37
+
38
+ _ALL_CHECKS: list[SecurityCheck] = [
39
+ BOLACheck(),
40
+ BrokenAuthCheck(),
41
+ BOPLACheck(),
42
+ ResourceConsumptionCheck(),
43
+ BFLACheck(),
44
+ SensitiveFlowsCheck(),
45
+ SSRFCheck(),
46
+ MisconfigCheck(),
47
+ InventoryCheck(),
48
+ UnsafeConsumptionCheck(),
49
+ ]
50
+
51
+ _HIGH_RISK_THRESHOLD = 0.7
52
+
53
+
54
+ class SecurityScanner:
55
+ def __init__(
56
+ self,
57
+ base_url: str,
58
+ enabled_checks: list[str] | None = None,
59
+ severity_threshold: str = "low",
60
+ timeout: float = 10.0,
61
+ belief_guided: bool = True,
62
+ adaptive_checks: bool = True,
63
+ extra_checks: list[SecurityCheck] | None = None,
64
+ ) -> None:
65
+ self.base_url = base_url.rstrip("/")
66
+ self.severity_threshold = severity_threshold
67
+ self.timeout = timeout
68
+ self.belief_guided = belief_guided
69
+ self.adaptive_checks = adaptive_checks
70
+
71
+ all_checks = list(_ALL_CHECKS) + list(extra_checks or [])
72
+
73
+ if enabled_checks is None:
74
+ self._checks = all_checks
75
+ else:
76
+ prefixes = [c.upper() for c in enabled_checks]
77
+ self._checks = [
78
+ c for c in all_checks
79
+ if any(c.owasp_id.upper().startswith(p) for p in prefixes)
80
+ ]
81
+
82
+ def _checks_for_ids(self, check_ids: list[str]) -> list[SecurityCheck]:
83
+ """Return the subset of enabled checks matching the given OWASP IDs."""
84
+ id_set = {cid.upper() for cid in check_ids}
85
+ return [c for c in self._checks if c.owasp_id.upper() in id_set]
86
+
87
+ async def scan(
88
+ self,
89
+ endpoints: list[EndpointInfo],
90
+ risk_report: list[dict] | None = None,
91
+ belief_states: list[BeliefState] | None = None,
92
+ risk_scorer: RiskScorer | None = None,
93
+ ) -> SecurityReport:
94
+ scan_id = str(uuid.uuid4())
95
+ timestamp = datetime.now(timezone.utc).isoformat()
96
+
97
+ prioritization_entries: list[EndpointSecurityPriority] = []
98
+
99
+ if self.belief_guided:
100
+ prioritized = prioritize_for_security(
101
+ endpoints,
102
+ risk_report=risk_report,
103
+ belief_states=belief_states,
104
+ risk_scorer=risk_scorer,
105
+ )
106
+ ordered_endpoints = [ep for ep, _score, _ids, _bs, _rs in prioritized]
107
+ else:
108
+ prioritized = []
109
+ ordered_endpoints = endpoints
110
+
111
+ all_findings: list[SecurityFinding] = []
112
+
113
+ async with httpx.AsyncClient(
114
+ timeout=self.timeout, follow_redirects=True
115
+ ) as client:
116
+ for i, endpoint in enumerate(ordered_endpoints):
117
+ if self.belief_guided and self.adaptive_checks and i < len(prioritized):
118
+ _, priority_score, check_ids, belief_score, risk_score_val = prioritized[i]
119
+ # High-risk endpoints get all enabled checks as a safety net
120
+ if priority_score > _HIGH_RISK_THRESHOLD:
121
+ checks_to_run = self._checks
122
+ else:
123
+ checks_to_run = self._checks_for_ids(check_ids) or self._checks
124
+ checks_run = [c.owasp_id for c in checks_to_run]
125
+ prioritization_entries.append(
126
+ EndpointSecurityPriority(
127
+ endpoint=f"{endpoint.method} {endpoint.path}",
128
+ priority_score=priority_score,
129
+ belief_score=belief_score,
130
+ risk_score=risk_score_val,
131
+ check_ids=check_ids,
132
+ checks_run=checks_run,
133
+ )
134
+ )
135
+ else:
136
+ checks_to_run = self._checks
137
+ if self.belief_guided and i < len(prioritized):
138
+ _, priority_score, check_ids, belief_score, risk_score_val = prioritized[i]
139
+ prioritization_entries.append(
140
+ EndpointSecurityPriority(
141
+ endpoint=f"{endpoint.method} {endpoint.path}",
142
+ priority_score=priority_score,
143
+ belief_score=belief_score,
144
+ risk_score=risk_score_val,
145
+ check_ids=check_ids,
146
+ checks_run=[c.owasp_id for c in checks_to_run],
147
+ )
148
+ )
149
+
150
+ results = await asyncio.gather(
151
+ *[check.run(endpoint, client) for check in checks_to_run],
152
+ return_exceptions=True,
153
+ )
154
+ for result in results:
155
+ if isinstance(result, list):
156
+ all_findings.extend(result)
157
+
158
+ filtered = self._filter_by_severity(all_findings)
159
+ filtered.sort(key=lambda f: _severity_order.get(f.severity, 99))
160
+
161
+ summary: dict[str, int] = {s: 0 for s in _severity_order}
162
+ for f in filtered:
163
+ summary[f.severity] = summary.get(f.severity, 0) + 1
164
+
165
+ return SecurityReport(
166
+ scan_id=scan_id,
167
+ timestamp=timestamp,
168
+ target_url=self.base_url,
169
+ total_endpoints_scanned=len(ordered_endpoints),
170
+ findings=filtered,
171
+ summary=summary,
172
+ belief_guided=self.belief_guided,
173
+ prioritization=prioritization_entries,
174
+ )
175
+
176
+ def _filter_by_severity(
177
+ self, findings: list[SecurityFinding]
178
+ ) -> list[SecurityFinding]:
179
+ threshold_order = _severity_order.get(self.severity_threshold, 99)
180
+ return [
181
+ f for f in findings
182
+ if _severity_order.get(f.severity, 99) <= threshold_order
183
+ ]