patchi 0.7.5__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 (496) hide show
  1. patchi/__init__.py +1 -0
  2. patchi/__main__.py +13 -0
  3. patchi/cli/__init__.py +0 -0
  4. patchi/cli/command_families.py +235 -0
  5. patchi/cli/commands/__init__.py +0 -0
  6. patchi/cli/commands/access_cmd.py +65 -0
  7. patchi/cli/commands/agent_stats_cmd.py +273 -0
  8. patchi/cli/commands/agents_cmd.py +187 -0
  9. patchi/cli/commands/ai_cmd.py +308 -0
  10. patchi/cli/commands/assure_cmd.py +538 -0
  11. patchi/cli/commands/audit_cmd.py +286 -0
  12. patchi/cli/commands/auto_cmd.py +110 -0
  13. patchi/cli/commands/blame_cmd.py +53 -0
  14. patchi/cli/commands/chains_cmd.py +327 -0
  15. patchi/cli/commands/charter_cmd.py +173 -0
  16. patchi/cli/commands/chat_cmd.py +597 -0
  17. patchi/cli/commands/check_cmd.py +232 -0
  18. patchi/cli/commands/cleanup_cmd.py +328 -0
  19. patchi/cli/commands/cockpit_cmd.py +208 -0
  20. patchi/cli/commands/commands_cmd.py +160 -0
  21. patchi/cli/commands/cross_repo_cmd.py +113 -0
  22. patchi/cli/commands/deps_cmd.py +110 -0
  23. patchi/cli/commands/dev_check_cmd.py +342 -0
  24. patchi/cli/commands/dev_cmd.py +445 -0
  25. patchi/cli/commands/doctor_cmd.py +457 -0
  26. patchi/cli/commands/findings_cmd.py +187 -0
  27. patchi/cli/commands/fix_cmd.py +648 -0
  28. patchi/cli/commands/fix_review_cmd.py +273 -0
  29. patchi/cli/commands/heatmap_cmd.py +62 -0
  30. patchi/cli/commands/help_cmd.py +159 -0
  31. patchi/cli/commands/hosted_cmd.py +980 -0
  32. patchi/cli/commands/init.py +456 -0
  33. patchi/cli/commands/key_cmd.py +571 -0
  34. patchi/cli/commands/learn_cmd.py +140 -0
  35. patchi/cli/commands/link_cmd.py +104 -0
  36. patchi/cli/commands/log_cmd.py +57 -0
  37. patchi/cli/commands/memory_cmd.py +215 -0
  38. patchi/cli/commands/model_cmd.py +207 -0
  39. patchi/cli/commands/notify_cmd.py +213 -0
  40. patchi/cli/commands/notify_prompts.py +29 -0
  41. patchi/cli/commands/patch_cmd.py +231 -0
  42. patchi/cli/commands/plan_cmd.py +128 -0
  43. patchi/cli/commands/plugins_cmd.py +307 -0
  44. patchi/cli/commands/queue_cmd.py +152 -0
  45. patchi/cli/commands/quick_cmd.py +159 -0
  46. patchi/cli/commands/ready_cmd.py +292 -0
  47. patchi/cli/commands/report_cmd.py +416 -0
  48. patchi/cli/commands/restrict_cmd.py +127 -0
  49. patchi/cli/commands/review_cmd.py +225 -0
  50. patchi/cli/commands/rules_cmd.py +233 -0
  51. patchi/cli/commands/scan_cmd.py +1908 -0
  52. patchi/cli/commands/settings_cmd.py +45 -0
  53. patchi/cli/commands/status_cmd.py +679 -0
  54. patchi/cli/commands/test_cmd.py +1052 -0
  55. patchi/cli/commands/trend_cmd.py +296 -0
  56. patchi/cli/commands/undo_cmd.py +235 -0
  57. patchi/cli/commands/update_cmd.py +345 -0
  58. patchi/cli/commands/verify_cmd.py +56 -0
  59. patchi/cli/commands/vr_cmd.py +440 -0
  60. patchi/cli/commands/watch_cmd.py +304 -0
  61. patchi/cli/commands/web_cmd.py +114 -0
  62. patchi/cli/console.py +101 -0
  63. patchi/cli/display/__init__.py +1 -0
  64. patchi/cli/display/live_audit.py +269 -0
  65. patchi/cli/display/live_progress.py +169 -0
  66. patchi/cli/framework.py +241 -0
  67. patchi/cli/logo.py +128 -0
  68. patchi/cli/main.py +222 -0
  69. patchi/cli/registry.py +1171 -0
  70. patchi/cli/style.py +68 -0
  71. patchi/cli/themes.py +187 -0
  72. patchi/cli/ux.py +444 -0
  73. patchi/core/__init__.py +0 -0
  74. patchi/core/agents/__init__.py +0 -0
  75. patchi/core/agents/api_fuzzer_agent.py +141 -0
  76. patchi/core/agents/attack_agent.py +242 -0
  77. patchi/core/agents/base.py +1012 -0
  78. patchi/core/agents/bug_predictor_agent.py +58 -0
  79. patchi/core/agents/build_tool_validator.py +150 -0
  80. patchi/core/agents/cache.py +192 -0
  81. patchi/core/agents/chaos_agent.py +133 -0
  82. patchi/core/agents/cicd_generator.py +240 -0
  83. patchi/core/agents/comment_scanner.py +280 -0
  84. patchi/core/agents/console_logging_agent.py +156 -0
  85. patchi/core/agents/coordinator.py +858 -0
  86. patchi/core/agents/core_scanner.py +269 -0
  87. patchi/core/agents/coverage_prioritizer.py +181 -0
  88. patchi/core/agents/dead_code_hygiene.py +296 -0
  89. patchi/core/agents/dead_code_scanner.py +692 -0
  90. patchi/core/agents/dependency_scanner.py +725 -0
  91. patchi/core/agents/doc_claim_agent.py +573 -0
  92. patchi/core/agents/duplicate_scanner.py +568 -0
  93. patchi/core/agents/env_scanner.py +410 -0
  94. patchi/core/agents/feature_flag_agent.py +89 -0
  95. patchi/core/agents/frontend_framework_agent.py +133 -0
  96. patchi/core/agents/goroutine_agent.py +82 -0
  97. patchi/core/agents/governor.py +1972 -0
  98. patchi/core/agents/i18n_agent.py +95 -0
  99. patchi/core/agents/license_compliance.py +247 -0
  100. patchi/core/agents/linking_agent.py +251 -0
  101. patchi/core/agents/memory_profiler_agent.py +102 -0
  102. patchi/core/agents/modernization_agent.py +84 -0
  103. patchi/core/agents/mutation_agent.py +174 -0
  104. patchi/core/agents/nl_query_agent.py +74 -0
  105. patchi/core/agents/promise_rejection_agent.py +100 -0
  106. patchi/core/agents/refactoring_agent.py +286 -0
  107. patchi/core/agents/resource_leak_agent.py +91 -0
  108. patchi/core/agents/route_graph_scanner.py +951 -0
  109. patchi/core/agents/run_agent.py +106 -0
  110. patchi/core/agents/rust_unwrap_agent.py +100 -0
  111. patchi/core/agents/sbom_generator.py +284 -0
  112. patchi/core/agents/scanners.py +67 -0
  113. patchi/core/agents/side/__init__.py +1 -0
  114. patchi/core/agents/side/build_agent.py +65 -0
  115. patchi/core/agents/side/format_agent.py +50 -0
  116. patchi/core/agents/side/install_agent.py +63 -0
  117. patchi/core/agents/side_file_scanner.py +549 -0
  118. patchi/core/agents/snapshot_drift_detector.py +146 -0
  119. patchi/core/agents/spa_route_inventory.py +203 -0
  120. patchi/core/agents/test_scanner.py +442 -0
  121. patchi/core/agents/tool_health.py +109 -0
  122. patchi/core/agents/tool_runner.py +162 -0
  123. patchi/core/agents/type_scanner.py +168 -0
  124. patchi/core/agents/ui_scanner.py +558 -0
  125. patchi/core/ai/__init__.py +20 -0
  126. patchi/core/ai/agent_audit.py +153 -0
  127. patchi/core/ai/agent_profiler.py +277 -0
  128. patchi/core/ai/client.py +499 -0
  129. patchi/core/ai/cost_tracker.py +107 -0
  130. patchi/core/ai/model_router.py +318 -0
  131. patchi/core/ai/orchestrator.py +625 -0
  132. patchi/core/ai/prompts.py +681 -0
  133. patchi/core/ai/smart.py +302 -0
  134. patchi/core/ai/tool_executor.py +24 -0
  135. patchi/core/ai/tools/__init__.py +10 -0
  136. patchi/core/ai/tools/executor.py +621 -0
  137. patchi/core/ai/tools/realize.py +1160 -0
  138. patchi/core/ai/tools/registry.py +1577 -0
  139. patchi/core/assurance/__init__.py +33 -0
  140. patchi/core/assurance/builder.py +211 -0
  141. patchi/core/assurance/graph.py +342 -0
  142. patchi/core/assurance/invariants.py +168 -0
  143. patchi/core/atomic.py +61 -0
  144. patchi/core/attackers/__init__.py +17 -0
  145. patchi/core/attackers/api_abuse.py +89 -0
  146. patchi/core/attackers/auth_bypass.py +73 -0
  147. patchi/core/attackers/base.py +91 -0
  148. patchi/core/attackers/business_logic.py +88 -0
  149. patchi/core/attackers/planner.py +108 -0
  150. patchi/core/attackers/priv_escalation.py +88 -0
  151. patchi/core/attackers/recon.py +87 -0
  152. patchi/core/brain/__init__.py +0 -0
  153. patchi/core/brain/ast_utils/__init__.py +81 -0
  154. patchi/core/brain/ast_utils/assignments.py +219 -0
  155. patchi/core/brain/ast_utils/calls.py +139 -0
  156. patchi/core/brain/ast_utils/config.py +132 -0
  157. patchi/core/brain/ast_utils/control_flow.py +74 -0
  158. patchi/core/brain/ast_utils/dead_symbols.py +205 -0
  159. patchi/core/brain/ast_utils/decorators.py +108 -0
  160. patchi/core/brain/ast_utils/helpers.py +144 -0
  161. patchi/core/brain/ast_utils/imports.py +169 -0
  162. patchi/core/brain/ast_utils/scan.py +143 -0
  163. patchi/core/brain/ast_utils/taint.py +310 -0
  164. patchi/core/brain/audit.py +379 -0
  165. patchi/core/brain/baseline.py +195 -0
  166. patchi/core/brain/blast_radius.py +312 -0
  167. patchi/core/brain/body_tags.py +144 -0
  168. patchi/core/brain/brain.py +1161 -0
  169. patchi/core/brain/brain_context.py +393 -0
  170. patchi/core/brain/brain_watcher.py +340 -0
  171. patchi/core/brain/charter.py +473 -0
  172. patchi/core/brain/classifier.py +214 -0
  173. patchi/core/brain/cockpit.py +350 -0
  174. patchi/core/brain/code_query.py +710 -0
  175. patchi/core/brain/contract.py +889 -0
  176. patchi/core/brain/contract_diff.py +155 -0
  177. patchi/core/brain/council.py +800 -0
  178. patchi/core/brain/cross_repo.py +104 -0
  179. patchi/core/brain/dead_code_tools.py +157 -0
  180. patchi/core/brain/doc_validator.py +197 -0
  181. patchi/core/brain/domain_activator.py +2183 -0
  182. patchi/core/brain/enriched_context.py +220 -0
  183. patchi/core/brain/file_corpus.py +220 -0
  184. patchi/core/brain/framework.py +953 -0
  185. patchi/core/brain/freshness.py +273 -0
  186. patchi/core/brain/git_aware.py +206 -0
  187. patchi/core/brain/heatmap.py +104 -0
  188. patchi/core/brain/ignore_parser.py +153 -0
  189. patchi/core/brain/import_graph.py +486 -0
  190. patchi/core/brain/languages.py +397 -0
  191. patchi/core/brain/layered_brain.py +419 -0
  192. patchi/core/brain/learning.py +229 -0
  193. patchi/core/brain/orphaned_endpoints.py +313 -0
  194. patchi/core/brain/personas/__init__.py +648 -0
  195. patchi/core/brain/personas/bad_user.py +165 -0
  196. patchi/core/brain/personas/base.py +349 -0
  197. patchi/core/brain/proactive.py +807 -0
  198. patchi/core/brain/project_context.py +121 -0
  199. patchi/core/brain/project_reader.py +521 -0
  200. patchi/core/brain/reasoning.py +269 -0
  201. patchi/core/brain/route_detector/__init__.py +15 -0
  202. patchi/core/brain/route_detector/base.py +31 -0
  203. patchi/core/brain/route_detector/csharp.py +209 -0
  204. patchi/core/brain/route_detector/file_based.py +81 -0
  205. patchi/core/brain/route_detector/go.py +152 -0
  206. patchi/core/brain/route_detector/java.py +195 -0
  207. patchi/core/brain/route_detector/javascript.py +197 -0
  208. patchi/core/brain/route_detector/php.py +158 -0
  209. patchi/core/brain/route_detector/python.py +193 -0
  210. patchi/core/brain/route_detector/registry.py +25 -0
  211. patchi/core/brain/route_detector/ruby.py +134 -0
  212. patchi/core/brain/route_detector/rust.py +216 -0
  213. patchi/core/brain/route_detector/swift.py +118 -0
  214. patchi/core/brain/route_mapper.py +265 -0
  215. patchi/core/brain/scanner.py +2136 -0
  216. patchi/core/brain/secrets.py +280 -0
  217. patchi/core/brain/symbol_graph.py +1955 -0
  218. patchi/core/brain/team.py +28 -0
  219. patchi/core/brain/trace_log.py +132 -0
  220. patchi/core/brain/type_checker/__init__.py +65 -0
  221. patchi/core/brain/type_checker/base.py +60 -0
  222. patchi/core/brain/type_checker/csharp.py +59 -0
  223. patchi/core/brain/type_checker/dart.py +59 -0
  224. patchi/core/brain/type_checker/go.py +81 -0
  225. patchi/core/brain/type_checker/java.py +75 -0
  226. patchi/core/brain/type_checker/kotlin.py +75 -0
  227. patchi/core/brain/type_checker/php.py +88 -0
  228. patchi/core/brain/type_checker/python.py +87 -0
  229. patchi/core/brain/type_checker/rust.py +81 -0
  230. patchi/core/brain/type_checker/swift.py +59 -0
  231. patchi/core/brain/type_checker/typescript.py +298 -0
  232. patchi/core/brain/understander.py +130 -0
  233. patchi/core/brain/verify.py +379 -0
  234. patchi/core/campaigns/__init__.py +14 -0
  235. patchi/core/campaigns/base.py +62 -0
  236. patchi/core/campaigns/data_flow_abuse.py +153 -0
  237. patchi/core/campaigns/orchestrator.py +140 -0
  238. patchi/core/campaigns/state_transitions.py +178 -0
  239. patchi/core/ci_bundle.py +104 -0
  240. patchi/core/config.py +311 -0
  241. patchi/core/constants.py +245 -0
  242. patchi/core/debug/__init__.py +25 -0
  243. patchi/core/debug/adapters/__init__.py +11 -0
  244. patchi/core/debug/adapters/codelldb.py +379 -0
  245. patchi/core/debug/adapters/node.py +222 -0
  246. patchi/core/debug/adapters/powershell.py +210 -0
  247. patchi/core/debug/adapters/python.py +244 -0
  248. patchi/core/debug/capture.py +210 -0
  249. patchi/core/debug/dap_client.py +268 -0
  250. patchi/core/detector/__init__.py +0 -0
  251. patchi/core/detector/audit.py +194 -0
  252. patchi/core/detector/bus.py +226 -0
  253. patchi/core/detector/dispatcher.py +275 -0
  254. patchi/core/detector/event.py +254 -0
  255. patchi/core/detector/sigma_engine.py +378 -0
  256. patchi/core/detector/triage.py +289 -0
  257. patchi/core/export/sarif.py +113 -0
  258. patchi/core/fix/__init__.py +17 -0
  259. patchi/core/fix/applier.py +574 -0
  260. patchi/core/fix/base.py +510 -0
  261. patchi/core/fix/code_fixer.py +169 -0
  262. patchi/core/fix/dead_code_remover.py +131 -0
  263. patchi/core/fix/fix_agents.py +775 -0
  264. patchi/core/fix/patch.py +444 -0
  265. patchi/core/fix/risk_gate.py +383 -0
  266. patchi/core/fix/security_fixer.py +93 -0
  267. patchi/core/fix/verify_loop.py +204 -0
  268. patchi/core/fuzz/__init__.py +8 -0
  269. patchi/core/fuzz/corpus.py +137 -0
  270. patchi/core/fuzz/input_fuzzer.py +256 -0
  271. patchi/core/fuzz/sequence_fuzzer.py +162 -0
  272. patchi/core/fuzz/state_fuzzer.py +236 -0
  273. patchi/core/health.py +407 -0
  274. patchi/core/hosted/__init__.py +1 -0
  275. patchi/core/hosted/anomaly.py +298 -0
  276. patchi/core/hosted/audit_log.py +102 -0
  277. patchi/core/hosted/compliance_report.py +148 -0
  278. patchi/core/hosted/ip_reputation.py +169 -0
  279. patchi/core/hosted/log_parsers.py +251 -0
  280. patchi/core/hosted/tokens.py +127 -0
  281. patchi/core/hosted/watchlist.py +164 -0
  282. patchi/core/hosted/webhooks.py +156 -0
  283. patchi/core/memory.py +437 -0
  284. patchi/core/notifications/__init__.py +18 -0
  285. patchi/core/notifications/channels.py +192 -0
  286. patchi/core/notifications/digest.py +141 -0
  287. patchi/core/notifications/escalation.py +158 -0
  288. patchi/core/notifications/notifier.py +182 -0
  289. patchi/core/notifications/quiet_hours.py +69 -0
  290. patchi/core/plugins/__init__.py +48 -0
  291. patchi/core/plugins/analyzer.py +326 -0
  292. patchi/core/plugins/analyzers/__init__.py +6 -0
  293. patchi/core/plugins/analyzers/dead_code_analyzer.py +120 -0
  294. patchi/core/plugins/analyzers/secret_scanner.py +158 -0
  295. patchi/core/plugins/analyzers/todo_scanner.py +115 -0
  296. patchi/core/plugins/registry.py +335 -0
  297. patchi/core/queue.py +375 -0
  298. patchi/core/reliability/__init__.py +13 -0
  299. patchi/core/reliability/chaos.py +163 -0
  300. patchi/core/reliability/fault_injector.py +172 -0
  301. patchi/core/reliability/idempotency.py +128 -0
  302. patchi/core/reliability/recovery.py +147 -0
  303. patchi/core/runtime/__init__.py +5 -0
  304. patchi/core/runtime/tracer.py +230 -0
  305. patchi/core/scan_bus.py +156 -0
  306. patchi/core/security/__init__.py +1 -0
  307. patchi/core/security/ai_validator.py +462 -0
  308. patchi/core/security/app_mapper.py +216 -0
  309. patchi/core/security/app_profile.py +464 -0
  310. patchi/core/security/attack_feedback.py +141 -0
  311. patchi/core/security/attack_scenarios/__init__.py +1 -0
  312. patchi/core/security/attack_tree.py +176 -0
  313. patchi/core/security/auth_audit_agent.py +344 -0
  314. patchi/core/security/authz_agent.py +602 -0
  315. patchi/core/security/auto_fix_proactive.py +620 -0
  316. patchi/core/security/auto_fixer.py +660 -0
  317. patchi/core/security/auto_ticket.py +53 -0
  318. patchi/core/security/bandit_agent.py +152 -0
  319. patchi/core/security/blast_radius.py +226 -0
  320. patchi/core/security/browser_tester.py +333 -0
  321. patchi/core/security/business_logic_agent.py +263 -0
  322. patchi/core/security/catch_block_auditor.py +395 -0
  323. patchi/core/security/cdn_cache_agent.py +357 -0
  324. patchi/core/security/chain_analyzer.py +321 -0
  325. patchi/core/security/chain_to_assurance.py +168 -0
  326. patchi/core/security/charter.py +510 -0
  327. patchi/core/security/cloud_waf_detector.py +295 -0
  328. patchi/core/security/codeql_agent.py +233 -0
  329. patchi/core/security/compliance_agent.py +348 -0
  330. patchi/core/security/confidence_gate.py +423 -0
  331. patchi/core/security/container_scanner.py +282 -0
  332. patchi/core/security/crypto_agent.py +378 -0
  333. patchi/core/security/cve_monitor.py +262 -0
  334. patchi/core/security/dast_agent.py +865 -0
  335. patchi/core/security/defectdojo.py +238 -0
  336. patchi/core/security/defenders/__init__.py +75 -0
  337. patchi/core/security/defenders/auth_middleware.py +115 -0
  338. patchi/core/security/defenders/base.py +97 -0
  339. patchi/core/security/defenders/block_ip.py +71 -0
  340. patchi/core/security/defenders/block_ws_origin.py +36 -0
  341. patchi/core/security/defenders/crypto_fix.py +118 -0
  342. patchi/core/security/defenders/enforce_rate_limit.py +108 -0
  343. patchi/core/security/defenders/fix_code.py +93 -0
  344. patchi/core/security/defenders/invalidate_session.py +40 -0
  345. patchi/core/security/defenders/patch_config.py +70 -0
  346. patchi/core/security/defenders/rotate_secret.py +54 -0
  347. patchi/core/security/defenders/suspend_account.py +41 -0
  348. patchi/core/security/defenders/update_dependency.py +160 -0
  349. patchi/core/security/defense_layer.py +217 -0
  350. patchi/core/security/dependency_vulnerability_agent.py +616 -0
  351. patchi/core/security/detection_pipeline.py +289 -0
  352. patchi/core/security/dns_security_agent.py +497 -0
  353. patchi/core/security/domain_activator_v2.py +913 -0
  354. patchi/core/security/domain_loader.py +1004 -0
  355. patchi/core/security/domains/__init__.py +0 -0
  356. patchi/core/security/email_authentication_agent.py +417 -0
  357. patchi/core/security/env_var_validator.py +304 -0
  358. patchi/core/security/evidence.py +100 -0
  359. patchi/core/security/falco_runtime_agent.py +387 -0
  360. patchi/core/security/fix-playbooks/__init__.py +0 -0
  361. patchi/core/security/gated_finding.py +85 -0
  362. patchi/core/security/git_diff_activator.py +367 -0
  363. patchi/core/security/governance.py +208 -0
  364. patchi/core/security/gradual_gate.py +34 -0
  365. patchi/core/security/history.py +315 -0
  366. patchi/core/security/iac_scanner.py +250 -0
  367. patchi/core/security/ignore_expiry.py +111 -0
  368. patchi/core/security/ignore_learner.py +416 -0
  369. patchi/core/security/injection_agent.py +319 -0
  370. patchi/core/security/insecure_randomness_agent.py +288 -0
  371. patchi/core/security/intent_analyzer.py +326 -0
  372. patchi/core/security/jwt_agent.py +453 -0
  373. patchi/core/security/kubernetes_agent.py +638 -0
  374. patchi/core/security/layer2_orchestrator.py +341 -0
  375. patchi/core/security/llm_security_agent.py +252 -0
  376. patchi/core/security/misconfig_agent.py +600 -0
  377. patchi/core/security/mobile_security_agent.py +474 -0
  378. patchi/core/security/network_agent.py +376 -0
  379. patchi/core/security/noise_filter.py +274 -0
  380. patchi/core/security/orchestrator.py +313 -0
  381. patchi/core/security/pattern_context.py +125 -0
  382. patchi/core/security/pattern_loader.py +101 -0
  383. patchi/core/security/pentest/__init__.py +4 -0
  384. patchi/core/security/pentest/base.py +58 -0
  385. patchi/core/security/pentest/dalfox_adapter.py +40 -0
  386. patchi/core/security/pentest/ffuf_adapter.py +48 -0
  387. patchi/core/security/pentest/nuclei_adapter.py +50 -0
  388. patchi/core/security/pentest/registry.py +80 -0
  389. patchi/core/security/pentest/shannon_adapter.py +107 -0
  390. patchi/core/security/pentest/sqlmap_adapter.py +56 -0
  391. patchi/core/security/pentest/zap_adapter.py +52 -0
  392. patchi/core/security/plan_auditor.py +276 -0
  393. patchi/core/security/policy_engine.py +272 -0
  394. patchi/core/security/prechecks.py +172 -0
  395. patchi/core/security/precommit_hook.py +247 -0
  396. patchi/core/security/privacy_agent.py +361 -0
  397. patchi/core/security/push_notification_agent.py +417 -0
  398. patchi/core/security/pysa_agent.py +143 -0
  399. patchi/core/security/reasoning.py +442 -0
  400. patchi/core/security/red_team_agent.py +184 -0
  401. patchi/core/security/red_team_engine.py +1142 -0
  402. patchi/core/security/remediation.py +360 -0
  403. patchi/core/security/request_interceptor.py +294 -0
  404. patchi/core/security/runtime_validator.py +245 -0
  405. patchi/core/security/saml_sso_agent.py +401 -0
  406. patchi/core/security/sast_agent.py +213 -0
  407. patchi/core/security/sast_gate.py +95 -0
  408. patchi/core/security/scheduler.py +287 -0
  409. patchi/core/security/secrets_runtime_agent.py +476 -0
  410. patchi/core/security/security_agents.py +175 -0
  411. patchi/core/security/security_config.py +551 -0
  412. patchi/core/security/security_probe.py +762 -0
  413. patchi/core/security/security_taint.py +1005 -0
  414. patchi/core/security/sensitive_data_agent.py +271 -0
  415. patchi/core/security/service_mesh_agent.py +412 -0
  416. patchi/core/security/session_management_agent.py +245 -0
  417. patchi/core/security/ssrf_agent.py +422 -0
  418. patchi/core/security/supply_chain.py +449 -0
  419. patchi/core/security/threat_model_generator.py +378 -0
  420. patchi/core/security/threat_model_updater.py +196 -0
  421. patchi/core/security/tool_adapters.py +135 -0
  422. patchi/core/security/tool_verify.py +173 -0
  423. patchi/core/security/websocket_security_agent.py +188 -0
  424. patchi/core/snapshot.py +242 -0
  425. patchi/core/tenant.py +386 -0
  426. patchi/core/testing/__init__.py +17 -0
  427. patchi/core/testing/_browser.py +147 -0
  428. patchi/core/testing/ads_agent.py +141 -0
  429. patchi/core/testing/api_contract_agent.py +450 -0
  430. patchi/core/testing/app_discovery_agent.py +641 -0
  431. patchi/core/testing/app_launcher.py +184 -0
  432. patchi/core/testing/browser_test_agent.py +516 -0
  433. patchi/core/testing/e2e_flow_agent.py +354 -0
  434. patchi/core/testing/flake_detector_agent.py +292 -0
  435. patchi/core/testing/gate.py +31 -0
  436. patchi/core/testing/live_test_runner.py +304 -0
  437. patchi/core/testing/live_v2/__init__.py +1 -0
  438. patchi/core/testing/live_v2/browser_pool.py +349 -0
  439. patchi/core/testing/live_v2/browser_test_runner.py +313 -0
  440. patchi/core/testing/live_v2/runner.py +626 -0
  441. patchi/core/testing/live_v2/screenshot_manager.py +474 -0
  442. patchi/core/testing/live_v2/stress_orchestrator.py +523 -0
  443. patchi/core/testing/live_v2/video_recorder.py +303 -0
  444. patchi/core/testing/regression_agent.py +302 -0
  445. patchi/core/testing/security_test_agent.py +253 -0
  446. patchi/core/testing/stress_test_agent.py +203 -0
  447. patchi/core/testing/test_agents.py +50 -0
  448. patchi/core/testing/test_config.py +194 -0
  449. patchi/core/testing/ui_accessibility_agent.py +324 -0
  450. patchi/core/testing/ui_button_agent.py +294 -0
  451. patchi/core/testing/ui_layout_agent.py +330 -0
  452. patchi/core/testing/unit_test_agent.py +1149 -0
  453. patchi/core/testing/visual_regression_agent.py +260 -0
  454. patchi/web/__init__.py +1 -0
  455. patchi/web/api/__init__.py +1 -0
  456. patchi/web/api/brain_map.py +114 -0
  457. patchi/web/api/charts.py +47 -0
  458. patchi/web/api/chat.py +224 -0
  459. patchi/web/api/cicd.py +576 -0
  460. patchi/web/api/dast_evidence.py +49 -0
  461. patchi/web/api/dev_check.py +320 -0
  462. patchi/web/api/fix.py +134 -0
  463. patchi/web/api/guard.py +65 -0
  464. patchi/web/api/hosted.py +128 -0
  465. patchi/web/api/hosted_v2.py +234 -0
  466. patchi/web/api/live_testing.py +699 -0
  467. patchi/web/api/scan.py +582 -0
  468. patchi/web/api/smart.py +111 -0
  469. patchi/web/api/tenant.py +117 -0
  470. patchi/web/api/tools.py +85 -0
  471. patchi/web/api_legacy.py +2401 -0
  472. patchi/web/app.py +310 -0
  473. patchi/web/events.py +483 -0
  474. patchi/web/routes/__init__.py +1 -0
  475. patchi/web/routes/assurance.py +541 -0
  476. patchi/web/routes/brain.py +36 -0
  477. patchi/web/routes/charter.py +227 -0
  478. patchi/web/routes/chat.py +23 -0
  479. patchi/web/routes/dashboard.py +558 -0
  480. patchi/web/routes/dashboard_v2.py +402 -0
  481. patchi/web/routes/findings.py +330 -0
  482. patchi/web/routes/guard.py +35 -0
  483. patchi/web/routes/history.py +126 -0
  484. patchi/web/routes/landing.py +52 -0
  485. patchi/web/routes/live_testing.py +24 -0
  486. patchi/web/routes/review.py +57 -0
  487. patchi/web/routes/self_improvement.py +110 -0
  488. patchi/web/routes/settings.py +49 -0
  489. patchi/web/routes/tokens.py +29 -0
  490. patchi/web/spawn.py +139 -0
  491. patchi/web/ws.py +141 -0
  492. patchi-0.7.5.dist-info/METADATA +315 -0
  493. patchi-0.7.5.dist-info/RECORD +496 -0
  494. patchi-0.7.5.dist-info/WHEEL +5 -0
  495. patchi-0.7.5.dist-info/licenses/LICENSE +60 -0
  496. patchi-0.7.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,137 @@
1
+ """FuzzCorpus — persistent corpus manager for accumulating interesting inputs.
2
+
3
+ Stores fuzz inputs that triggered interesting behavior (crashes, errors,
4
+ new code paths) so subsequent runs can build on prior discoveries.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import logging
11
+ import time
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ _log = logging.getLogger("patchi.fuzz.corpus")
17
+
18
+ CORPUS_FILE = ".patchi/fuzz_corpus.json"
19
+
20
+
21
+ @dataclass
22
+ class CorpusEntry:
23
+ """One entry in the fuzz corpus."""
24
+
25
+ label: str
26
+ value: Any
27
+ strategy: str
28
+ score: float # interestingness score (higher = more interesting)
29
+ hits: int = 1 # how many times this triggered something
30
+ added_at: float = 0.0
31
+
32
+ def __post_init__(self):
33
+ if self.added_at == 0.0:
34
+ self.added_at = time.time()
35
+
36
+ def to_dict(self) -> dict:
37
+ return {
38
+ "label": self.label,
39
+ "value": str(self.value)[:500],
40
+ "strategy": self.strategy,
41
+ "score": self.score,
42
+ "hits": self.hits,
43
+ "added_at": self.added_at,
44
+ }
45
+
46
+ @classmethod
47
+ def from_dict(cls, d: dict) -> CorpusEntry:
48
+ return cls(
49
+ label=d.get("label", "?"),
50
+ value=d.get("value", ""),
51
+ strategy=d.get("strategy", "unknown"),
52
+ score=float(d.get("score", 0)),
53
+ hits=int(d.get("hits", 1)),
54
+ added_at=float(d.get("added_at", 0)),
55
+ )
56
+
57
+
58
+ class FuzzCorpus:
59
+ """Persistent corpus for accumulating interesting fuzz inputs."""
60
+
61
+ def __init__(self, root: Path):
62
+ self._root = root
63
+ self._path = root / CORPUS_FILE
64
+ self._entries: dict[str, CorpusEntry] = {}
65
+ self._load()
66
+
67
+ def _load(self) -> None:
68
+ """Load corpus from disk."""
69
+ if not self._path.is_file():
70
+ return
71
+ try:
72
+ data = json.loads(self._path.read_text(encoding="utf-8"))
73
+ for ed in data.get("entries", []):
74
+ entry = CorpusEntry.from_dict(ed)
75
+ self._entries[entry.label] = entry
76
+ except Exception as e:
77
+ _log.warning("Failed to load fuzz corpus: %s", e)
78
+
79
+ def save(self) -> None:
80
+ """Persist corpus to disk."""
81
+ self._path.parent.mkdir(parents=True, exist_ok=True)
82
+ payload = {
83
+ "version": 1,
84
+ "entries": [e.to_dict() for e in self._entries.values()],
85
+ }
86
+ tmp = self._path.with_suffix(".json.tmp")
87
+ tmp.write_text(json.dumps(payload, indent=2), encoding="utf-8")
88
+ tmp.replace(self._path)
89
+
90
+ def add(self, entry: CorpusEntry) -> None:
91
+ """Add or update a corpus entry."""
92
+ if entry.label in self._entries:
93
+ existing = self._entries[entry.label]
94
+ existing.hits += 1
95
+ existing.score = max(existing.score, entry.score)
96
+ else:
97
+ self._entries[entry.label] = entry
98
+
99
+ def get_interesting(self, min_score: float = 0.5, limit: int = 50) -> list[CorpusEntry]:
100
+ """Get the most interesting entries, sorted by score."""
101
+ candidates = [e for e in self._entries.values() if e.score >= min_score]
102
+ candidates.sort(key=lambda e: (-e.score, -e.hits))
103
+ return candidates[:limit]
104
+
105
+ def get_all(self, limit: int = 200) -> list[CorpusEntry]:
106
+ """Get all entries, sorted by score."""
107
+ entries = list(self._entries.values())
108
+ entries.sort(key=lambda e: (-e.score, -e.hits))
109
+ return entries[:limit]
110
+
111
+ def update_score(self, label: str, score: float) -> bool:
112
+ """Update the score of an existing entry. Returns True if found."""
113
+ if label in self._entries:
114
+ self._entries[label].score = max(self._entries[label].score, score)
115
+ self._entries[label].hits += 1
116
+ return True
117
+ return False
118
+
119
+ def __len__(self) -> int:
120
+ return len(self._entries)
121
+
122
+ def stats(self) -> dict:
123
+ """Return corpus statistics."""
124
+ entries = list(self._entries.values())
125
+ if not entries:
126
+ return {"total": 0, "avg_score": 0, "strategies": {}}
127
+
128
+ strategies: dict[str, int] = {}
129
+ for e in entries:
130
+ strategies[e.strategy] = strategies.get(e.strategy, 0) + 1
131
+
132
+ return {
133
+ "total": len(entries),
134
+ "avg_score": sum(e.score for e in entries) / len(entries),
135
+ "max_score": max(e.score for e in entries),
136
+ "strategies": strategies,
137
+ }
@@ -0,0 +1,256 @@
1
+ """InputFuzzer — mutate inputs for security boundary testing.
2
+
3
+ Generates mutated variants of input values to test injection, overflow,
4
+ encoding bypass, and format-string vulnerabilities.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import random
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+
13
+
14
+ @dataclass
15
+ class FuzzInput:
16
+ """A single fuzz input variant."""
17
+
18
+ label: str
19
+ value: Any
20
+ strategy: str # "boundary", "injection", "encoding", "overflow", "format"
21
+ expected_impact: str = "" # "crash", "injection", "bypass", "error"
22
+
23
+ def to_dict(self) -> dict:
24
+ return {
25
+ "label": self.label,
26
+ "value": str(self.value)[:200],
27
+ "strategy": self.strategy,
28
+ "expected_impact": self.expected_impact,
29
+ }
30
+
31
+
32
+ # ── Boundary values per type ────────────────────────────────────────────────
33
+
34
+ _BOUNDARY_STRINGS = [
35
+ "", # empty
36
+ " ", # whitespace
37
+ "a" * 10_000, # long
38
+ "\x00", # null byte
39
+ "🔥" * 100, # emoji
40
+ "\n\r\t", # control chars
41
+ "true",
42
+ "false",
43
+ "null", # type confusion
44
+ "-1",
45
+ "0",
46
+ "2147483647", # numeric boundaries
47
+ "99999999999999999999", # overflow
48
+ ]
49
+
50
+ _INJECTION_STRINGS = [
51
+ "' OR 1=1 --",
52
+ "'; DROP TABLE users; --",
53
+ "<script>alert(1)</script>",
54
+ "{{7*7}}",
55
+ "${7*7}",
56
+ "{{constructor.constructor('return this')()}}",
57
+ "../../etc/passwd",
58
+ "'; exec('ls'); --",
59
+ "| cat /etc/passwd",
60
+ "$(whoami)",
61
+ "`id`",
62
+ "1; rm -rf /",
63
+ ]
64
+
65
+ _ENCODING_STRINGS = [
66
+ "%27%20OR%201%3D1", # URL-encoded
67
+ "&lt;script&gt;", # HTML entities
68
+ "JCBhbGVydCgxKQ==", # base64
69
+ "%00", # null byte URL
70
+ "\\u0027", # unicode escape
71
+ "%EF%BC%87", # fullwidth apostrophe
72
+ ".LogInformation", # log injection
73
+ "\r\nSet-Cookie: admin=1", # CRLF injection
74
+ ]
75
+
76
+ _FORMAT_STRINGS = [
77
+ "%s%s%s%s%s%s%s%s",
78
+ "%x%x%x%x%x%x%x%x",
79
+ "%n%n%n%n%n%n%n%n",
80
+ "{0.__class__.__bases__}",
81
+ "${{7*7}}",
82
+ ]
83
+
84
+
85
+ class InputFuzzer:
86
+ """Generate mutated input variants for endpoint/parameter testing."""
87
+
88
+ def __init__(self, seed: int | None = None):
89
+ self._rng = random.Random(seed)
90
+
91
+ def fuzz_string(self, original: str, count: int = 20) -> list[FuzzInput]:
92
+ """Generate mutated string variants."""
93
+ results: list[FuzzInput] = []
94
+
95
+ # Boundary values
96
+ for val in _BOUNDARY_STRINGS:
97
+ results.append(
98
+ FuzzInput(label=f"boundary_{val[:10]!r}", value=val, strategy="boundary")
99
+ )
100
+
101
+ # Injection payloads
102
+ for val in _INJECTION_STRINGS:
103
+ results.append(
104
+ FuzzInput(
105
+ label=f"injection_{val[:10]!r}",
106
+ value=val,
107
+ strategy="injection",
108
+ expected_impact="injection",
109
+ )
110
+ )
111
+
112
+ # Encoding bypasses
113
+ for val in _ENCODING_STRINGS:
114
+ results.append(
115
+ FuzzInput(
116
+ label=f"encoding_{val[:10]!r}",
117
+ value=val,
118
+ strategy="encoding",
119
+ expected_impact="bypass",
120
+ )
121
+ )
122
+
123
+ # Format strings
124
+ for val in _FORMAT_STRINGS:
125
+ results.append(
126
+ FuzzInput(
127
+ label=f"format_{val[:10]!r}",
128
+ value=val,
129
+ strategy="format",
130
+ expected_impact="error",
131
+ )
132
+ )
133
+
134
+ # Random mutations of the original
135
+ for i in range(max(0, count - len(results))):
136
+ mutated = self._mutate_string(original)
137
+ results.append(
138
+ FuzzInput(
139
+ label=f"random_{i}",
140
+ value=mutated,
141
+ strategy="random",
142
+ )
143
+ )
144
+
145
+ return results[:count]
146
+
147
+ def fuzz_numeric(self, original: int, count: int = 10) -> list[FuzzInput]:
148
+ """Generate mutated numeric variants."""
149
+ boundaries = [0, -1, 1, 2**31 - 1, 2**31, 2**63 - 1, -(2**31), -(2**63)]
150
+ results = [
151
+ FuzzInput(label=f"boundary_{v}", value=v, strategy="boundary") for v in boundaries
152
+ ]
153
+ for i in range(max(0, count - len(results))):
154
+ results.append(
155
+ FuzzInput(
156
+ label=f"random_{i}",
157
+ value=self._rng.randint(-10_000, 10_000),
158
+ strategy="random",
159
+ )
160
+ )
161
+ return results[:count]
162
+
163
+ def fuzz_dict(self, original: dict, count: int = 15) -> list[FuzzInput]:
164
+ """Generate mutated dict variants (extra keys, type confusion, nested depth)."""
165
+ results: list[FuzzInput] = []
166
+
167
+ # Extra keys
168
+ results.append(
169
+ FuzzInput(
170
+ label="extra_admin_key",
171
+ value={**original, "admin": True, "is_admin": True, "role": "admin"},
172
+ strategy="boundary",
173
+ expected_impact="privilege_escalation",
174
+ )
175
+ )
176
+
177
+ # Null values
178
+ results.append(
179
+ FuzzInput(
180
+ label="null_values",
181
+ value=dict.fromkeys(original),
182
+ strategy="boundary",
183
+ )
184
+ )
185
+
186
+ # Nested depth bomb
187
+ nested: Any = "leaf"
188
+ for _ in range(100):
189
+ nested = {"a": nested}
190
+ results.append(
191
+ FuzzInput(
192
+ label="nested_depth_100",
193
+ value={"__proto__": nested},
194
+ strategy="overflow",
195
+ expected_impact="crash",
196
+ )
197
+ )
198
+
199
+ # Prototype pollution
200
+ results.append(
201
+ FuzzInput(
202
+ label="proto_pollution",
203
+ value={**original, "__proto__": {"admin": True}},
204
+ strategy="injection",
205
+ expected_impact="privilege_escalation",
206
+ )
207
+ )
208
+
209
+ # Type confusion
210
+ for key in list(original.keys())[:3]:
211
+ confused = {**original}
212
+ confused[key] = [original[key]]
213
+ confused[f"{key}_array"] = original[key]
214
+ results.append(
215
+ FuzzInput(
216
+ label=f"type_confusion_{key}",
217
+ value=confused,
218
+ strategy="injection",
219
+ )
220
+ )
221
+
222
+ return results[:count]
223
+
224
+ def _mutate_string(self, s: str) -> str:
225
+ """Apply a random mutation to a string."""
226
+ ops = [
227
+ self._flip_case,
228
+ self._insert_special,
229
+ self._truncate,
230
+ self._repeat,
231
+ self._shuffle,
232
+ self._append_null,
233
+ ]
234
+ return self._rng.choice(ops)(s)
235
+
236
+ def _flip_case(self, s: str) -> str:
237
+ return "".join(c.swapcase() if c.isalpha() else c for c in s)
238
+
239
+ def _insert_special(self, s: str) -> str:
240
+ specials = ["\x00", "\n", "\r", "'", '"', "\\", "%00"]
241
+ pos = self._rng.randint(0, max(len(s), 1))
242
+ return s[:pos] + self._rng.choice(specials) + s[pos:]
243
+
244
+ def _truncate(self, s: str) -> str:
245
+ return s[: self._rng.randint(0, max(len(s) - 1, 0))]
246
+
247
+ def _repeat(self, s: str) -> str:
248
+ return s * self._rng.randint(2, 5)
249
+
250
+ def _shuffle(self, s: str) -> str:
251
+ lst = list(s)
252
+ self._rng.shuffle(lst)
253
+ return "".join(lst)
254
+
255
+ def _append_null(self, s: str) -> str:
256
+ return s + "\x00" * self._rng.randint(1, 10)
@@ -0,0 +1,162 @@
1
+ """SequenceFuzzer — reorder and combine API operations to find race conditions.
2
+
3
+ Given a set of API operations, generates permutations that test TOCTOU,
4
+ replay, and ordering vulnerabilities.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import itertools
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+
13
+
14
+ @dataclass
15
+ class FuzzSequence:
16
+ """A sequence of operations to execute against the target."""
17
+
18
+ label: str
19
+ operations: list[dict[str, Any]]
20
+ strategy: str # "permutation", "replay", "concurrent", "interleaved"
21
+ description: str = ""
22
+
23
+ def to_dict(self) -> dict:
24
+ return {
25
+ "label": self.label,
26
+ "operations": self.operations[:10], # cap for display
27
+ "strategy": self.strategy,
28
+ "description": self.description,
29
+ }
30
+
31
+
32
+ class SequenceFuzzer:
33
+ """Generate operation sequences that test ordering vulnerabilities."""
34
+
35
+ def __init__(self, max_permutations: int = 20):
36
+ self._max_perm = max_permutations
37
+
38
+ def generate(
39
+ self,
40
+ operations: list[dict[str, Any]],
41
+ max_depth: int = 4,
42
+ ) -> list[FuzzSequence]:
43
+ """Generate fuzz sequences from a list of API operations."""
44
+ results: list[FuzzSequence] = []
45
+
46
+ if len(operations) < 1:
47
+ return results
48
+
49
+ # 1. Pairwise permutations (all 2-element orderings)
50
+ results.extend(self._pairwise_permutations(operations))
51
+
52
+ # 2. Replay sequences (same op repeated)
53
+ results.extend(self._replay_sequences(operations))
54
+
55
+ # 3. Concurrent pairs (two ops that should be atomic)
56
+ results.extend(self._concurrent_pairs(operations))
57
+
58
+ # 4. Full permutations (if small enough)
59
+ if len(operations) <= 6:
60
+ results.extend(self._full_permutations(operations))
61
+
62
+ return results[: self._max_perm]
63
+
64
+ def _pairwise_permutations(self, operations: list[dict[str, Any]]) -> list[FuzzSequence]:
65
+ """Test every pair of operations in both orderings."""
66
+ results: list[FuzzSequence] = []
67
+ for i, op_a in enumerate(operations):
68
+ for j, op_b in enumerate(operations):
69
+ if i >= j:
70
+ continue
71
+ # Normal order
72
+ results.append(
73
+ FuzzSequence(
74
+ label=f"pair_{i}_{j}_normal",
75
+ operations=[op_a, op_b],
76
+ strategy="permutation",
77
+ description=f"Execute {op_a.get('method', '?')} then {op_b.get('method', '?')}",
78
+ )
79
+ )
80
+ # Reversed order
81
+ results.append(
82
+ FuzzSequence(
83
+ label=f"pair_{j}_{i}_reversed",
84
+ operations=[op_b, op_a],
85
+ strategy="permutation",
86
+ description=f"Execute {op_b.get('method', '?')} then {op_a.get('method', '?')} (reversed)",
87
+ )
88
+ )
89
+ return results
90
+
91
+ def _replay_sequences(self, operations: list[dict[str, Any]]) -> list[FuzzSequence]:
92
+ """Test replaying the same operation multiple times."""
93
+ results: list[FuzzSequence] = []
94
+ for i, op in enumerate(operations):
95
+ # Double replay
96
+ results.append(
97
+ FuzzSequence(
98
+ label=f"replay_{i}_double",
99
+ operations=[op, op],
100
+ strategy="replay",
101
+ description=f"Replay {op.get('method', '?')} twice (idempotency test)",
102
+ )
103
+ )
104
+ # Triple replay
105
+ results.append(
106
+ FuzzSequence(
107
+ label=f"replay_{i}_triple",
108
+ operations=[op, op, op],
109
+ strategy="replay",
110
+ description=f"Replay {op.get('method', '?')} three times (idempotency test)",
111
+ )
112
+ )
113
+ return results
114
+
115
+ def _concurrent_pairs(self, operations: list[dict[str, Any]]) -> list[FuzzSequence]:
116
+ """Mark operation pairs as concurrent (for testing race conditions)."""
117
+ results: list[FuzzSequence] = []
118
+ for i, op_a in enumerate(operations):
119
+ for j, op_b in enumerate(operations):
120
+ if i >= j:
121
+ continue
122
+ if self._are_concurrent(op_a, op_b):
123
+ results.append(
124
+ FuzzSequence(
125
+ label=f"concurrent_{i}_{j}",
126
+ operations=[op_a, op_b],
127
+ strategy="concurrent",
128
+ description=f"Execute {op_a.get('method', '?')} and {op_b.get('method', '?')} concurrently (race condition test)",
129
+ )
130
+ )
131
+ return results
132
+
133
+ def _full_permutations(self, operations: list[dict[str, Any]]) -> list[FuzzSequence]:
134
+ """Generate all permutations of small operation sets."""
135
+ results: list[FuzzSequence] = []
136
+ for perm in itertools.permutations(range(len(operations))):
137
+ ops = [operations[i] for i in perm]
138
+ results.append(
139
+ FuzzSequence(
140
+ label=f"perm_{'_'.join(map(str, perm))}",
141
+ operations=ops,
142
+ strategy="permutation",
143
+ description=f"Permutation: {' → '.join(o.get('method', '?') for o in ops)}",
144
+ )
145
+ )
146
+ return results
147
+
148
+ def _are_concurrent(self, op_a: dict, op_b: dict) -> bool:
149
+ """Determine if two operations could race (same resource, different actions)."""
150
+ path_a = op_a.get("path", "")
151
+ path_b = op_b.get("path", "")
152
+ method_a = op_a.get("method", "")
153
+ method_b = op_b.get("method", "")
154
+
155
+ # Same path but different HTTP methods
156
+ if path_a == path_b and method_a != method_b:
157
+ return True
158
+ # Both mutating the same resource
159
+ mutating = {"POST", "PUT", "PATCH", "DELETE"}
160
+ if path_a == path_b and method_a in mutating and method_b in mutating:
161
+ return True
162
+ return False