pentool 0.2.7__tar.gz → 0.2.9__tar.gz

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 (181) hide show
  1. {pentool-0.2.7 → pentool-0.2.9}/PKG-INFO +22 -2
  2. {pentool-0.2.7 → pentool-0.2.9}/README.md +21 -1
  3. {pentool-0.2.7 → pentool-0.2.9}/pentool/__init__.py +12 -1
  4. {pentool-0.2.7 → pentool-0.2.9}/pentool/__main__.py +31 -0
  5. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/intruder_api.py +115 -120
  6. pentool-0.2.9/pentool/api/intruder_repository.py +250 -0
  7. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/spider_api.py +70 -1
  8. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/config.py +2 -0
  9. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/database.py +89 -0
  10. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/events.py +36 -1
  11. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/license.py +24 -1
  12. pentool-0.2.9/pentool/core/notification_sound.py +123 -0
  13. pentool-0.2.9/pentool/modules/intruder.py +920 -0
  14. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/proxy.py +28 -15
  15. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/spider.py +88 -7
  16. {pentool-0.2.7 → pentool-0.2.9}/pentool/services/proxy_service.py +41 -1
  17. {pentool-0.2.7 → pentool-0.2.9}/pentool/services/repeater_service.py +4 -1
  18. {pentool-0.2.7 → pentool-0.2.9}/pentool/services/scan_service.py +45 -19
  19. pentool-0.2.9/pentool/storage/base_sqlite_storage.py +87 -0
  20. {pentool-0.2.7 → pentool-0.2.9}/pentool/storage/http_storage.py +9 -20
  21. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/app.py +61 -0
  22. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/project_manager.py +10 -2
  23. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/dashboard/screen.py +31 -9
  24. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/intruder/screen.py +779 -105
  25. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/intruder/screen.tcss +19 -1
  26. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/proxy/screen.py +126 -30
  27. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/repeater/screen.py +127 -10
  28. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/repeater/screen.tcss +7 -0
  29. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/settings/screen.py +1 -51
  30. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/settings/screen.tcss +3 -11
  31. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/spider/screen.py +4 -1
  32. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/target/screen.py +2 -1
  33. pentool-0.2.9/pentool/tui/widgets/diff_panel.py +70 -0
  34. pentool-0.2.9/pentool/tui/widgets/diff_panel.tcss +20 -0
  35. pentool-0.2.9/pentool/tui/widgets/notification_center.py +122 -0
  36. pentool-0.2.9/pentool/tui/widgets/notification_center.tcss +54 -0
  37. pentool-0.2.9/pentool/tui/widgets/option_cycler.py +74 -0
  38. pentool-0.2.9/pentool/tui/widgets/option_cycler.tcss +11 -0
  39. pentool-0.2.9/pentool/utils/auth_headers.py +31 -0
  40. pentool-0.2.9/pentool/utils/scope.py +61 -0
  41. {pentool-0.2.7 → pentool-0.2.9}/pentool.egg-info/PKG-INFO +22 -2
  42. {pentool-0.2.7 → pentool-0.2.9}/pentool.egg-info/SOURCES.txt +11 -0
  43. {pentool-0.2.7 → pentool-0.2.9}/pyproject.toml +1 -1
  44. pentool-0.2.7/pentool/modules/intruder.py +0 -460
  45. {pentool-0.2.7 → pentool-0.2.9}/LICENSE +0 -0
  46. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/__init__.py +0 -0
  47. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/base_api.py +0 -0
  48. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/comparer_api.py +0 -0
  49. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/decoder_api.py +0 -0
  50. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/proxy_api.py +0 -0
  51. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/repeater_api.py +0 -0
  52. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/sequencer_api.py +0 -0
  53. {pentool-0.2.7 → pentool-0.2.9}/pentool/api/target_api.py +0 -0
  54. {pentool-0.2.7 → pentool-0.2.9}/pentool/cli/__init__.py +0 -0
  55. {pentool-0.2.7 → pentool-0.2.9}/pentool/cli/main.py +0 -0
  56. {pentool-0.2.7 → pentool-0.2.9}/pentool/cli/project.py +0 -0
  57. {pentool-0.2.7 → pentool-0.2.9}/pentool/cli/proxy.py +0 -0
  58. {pentool-0.2.7 → pentool-0.2.9}/pentool/cli/scan.py +0 -0
  59. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/__init__.py +0 -0
  60. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/crash_reporter.py +0 -0
  61. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/event_bus.py +0 -0
  62. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/features.py +0 -0
  63. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/logging.py +0 -0
  64. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/plugin_manager.py +0 -0
  65. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/project.py +0 -0
  66. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/storage_interface.py +0 -0
  67. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/updater.py +0 -0
  68. {pentool-0.2.7 → pentool-0.2.9}/pentool/core/utils.py +0 -0
  69. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/__init__.py +0 -0
  70. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/comparer.py +0 -0
  71. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/decoder.py +0 -0
  72. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/match_replace.py +0 -0
  73. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/repeater.py +0 -0
  74. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/sequencer.py +0 -0
  75. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/target.py +0 -0
  76. {pentool-0.2.7 → pentool-0.2.9}/pentool/modules/websocket_handler.py +0 -0
  77. {pentool-0.2.7 → pentool-0.2.9}/pentool/plugins/__init__.py +0 -0
  78. {pentool-0.2.7 → pentool-0.2.9}/pentool/plugins/builtin/__init__.py +0 -0
  79. {pentool-0.2.7 → pentool-0.2.9}/pentool/plugins/example_plugin.py +0 -0
  80. {pentool-0.2.7 → pentool-0.2.9}/pentool/plugins/example_plugin.tcss +0 -0
  81. {pentool-0.2.7 → pentool-0.2.9}/pentool/services/__init__.py +0 -0
  82. {pentool-0.2.7 → pentool-0.2.9}/pentool/services/base_service.py +0 -0
  83. {pentool-0.2.7 → pentool-0.2.9}/pentool/services/intruder_service.py +0 -0
  84. {pentool-0.2.7 → pentool-0.2.9}/pentool/storage/__init__.py +0 -0
  85. {pentool-0.2.7 → pentool-0.2.9}/pentool/storage/large_body_handler.py +0 -0
  86. {pentool-0.2.7 → pentool-0.2.9}/pentool/storage/lru_cache.py +0 -0
  87. {pentool-0.2.7 → pentool-0.2.9}/pentool/t.py +0 -0
  88. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/__init__.py +0 -0
  89. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/constants.py +0 -0
  90. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/__init__.py +0 -0
  91. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/cert_dialog.py +0 -0
  92. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/cert_dialog.tcss +0 -0
  93. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/comment_dialog.py +0 -0
  94. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/comment_dialog.tcss +0 -0
  95. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/file_selector.py +0 -0
  96. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/file_selector.tcss +0 -0
  97. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/load_from_proxy.py +0 -0
  98. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/load_from_proxy.tcss +0 -0
  99. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/match_replace_dialog.py +0 -0
  100. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/match_replace_dialog.tcss +0 -0
  101. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/project_dialog.py +0 -0
  102. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/project_dialog.tcss +0 -0
  103. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/scope_dialog.py +0 -0
  104. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/dialogs/scope_dialog.tcss +0 -0
  105. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/messages.py +0 -0
  106. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/mixins/__init__.py +0 -0
  107. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/mixins/app_mixin.py +0 -0
  108. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/mixins/dialog_cancel.py +0 -0
  109. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/mixins/request_context_menu.py +0 -0
  110. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/__init__.py +0 -0
  111. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/base.py +0 -0
  112. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/comparer/__init__.py +0 -0
  113. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/comparer/screen.py +0 -0
  114. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/comparer/screen.tcss +0 -0
  115. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/dashboard/__init__.py +0 -0
  116. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/dashboard/live_dashboard.py +0 -0
  117. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/dashboard/screen.tcss +0 -0
  118. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/decoder/__init__.py +0 -0
  119. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/decoder/screen.py +0 -0
  120. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/decoder/screen.tcss +0 -0
  121. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/extensions/__init__.py +0 -0
  122. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/extensions/screen.py +0 -0
  123. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/extensions/screen.tcss +0 -0
  124. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/intruder/__init__.py +0 -0
  125. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/proxy/__init__.py +0 -0
  126. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/proxy/screen.tcss +0 -0
  127. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/repeater/__init__.py +0 -0
  128. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/scanner_unavailable.py +0 -0
  129. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/sequencer/__init__.py +0 -0
  130. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/sequencer/screen.py +0 -0
  131. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/sequencer/screen.tcss +0 -0
  132. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/settings/__init__.py +0 -0
  133. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/settings/hotkeys.py +0 -0
  134. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/settings/hotkeys.tcss +0 -0
  135. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/spider/__init__.py +0 -0
  136. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/spider/screen.tcss +0 -0
  137. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/target/__init__.py +0 -0
  138. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/target/screen.tcss +0 -0
  139. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/terminal/__init__.py +0 -0
  140. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/terminal/screen.py +0 -0
  141. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/screens/terminal/screen.tcss +0 -0
  142. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/__init__.py +0 -0
  143. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/activity_indicator.py +0 -0
  144. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/activity_indicator.tcss +0 -0
  145. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/context_menu.py +0 -0
  146. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/context_menu.tcss +0 -0
  147. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/filter_bar.py +0 -0
  148. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/filter_bar.tcss +0 -0
  149. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/inspector_panel.py +0 -0
  150. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/inspector_panel.tcss +0 -0
  151. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/menu.tcss +0 -0
  152. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/menu_bar.tcss +0 -0
  153. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/module_tabs.py +0 -0
  154. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/module_tabs.tcss +0 -0
  155. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/nice_checkbox.py +0 -0
  156. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/payload_drop_zone.py +0 -0
  157. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/payload_drop_zone.tcss +0 -0
  158. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/request_editor.py +0 -0
  159. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/request_editor.tcss +0 -0
  160. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/resize_handle.py +0 -0
  161. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/resize_handle.tcss +0 -0
  162. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/search_bar.py +0 -0
  163. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/search_bar.tcss +0 -0
  164. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/statusbar.py +0 -0
  165. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/statusbar.tcss +0 -0
  166. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/toolbar_button.py +0 -0
  167. {pentool-0.2.7 → pentool-0.2.9}/pentool/tui/widgets/toolbar_button.tcss +0 -0
  168. {pentool-0.2.7 → pentool-0.2.9}/pentool/utils/__init__.py +0 -0
  169. {pentool-0.2.7 → pentool-0.2.9}/pentool/utils/cert.py +0 -0
  170. {pentool-0.2.7 → pentool-0.2.9}/pentool/utils/coder.py +0 -0
  171. {pentool-0.2.7 → pentool-0.2.9}/pentool/utils/copy_as.py +0 -0
  172. {pentool-0.2.7 → pentool-0.2.9}/pentool/utils/diff.py +0 -0
  173. {pentool-0.2.7 → pentool-0.2.9}/pentool/utils/http_client.py +0 -0
  174. {pentool-0.2.7 → pentool-0.2.9}/pentool/utils/parser.py +0 -0
  175. {pentool-0.2.7 → pentool-0.2.9}/pentool/utils/terminal_check.py +0 -0
  176. {pentool-0.2.7 → pentool-0.2.9}/pentool.egg-info/dependency_links.txt +0 -0
  177. {pentool-0.2.7 → pentool-0.2.9}/pentool.egg-info/entry_points.txt +0 -0
  178. {pentool-0.2.7 → pentool-0.2.9}/pentool.egg-info/requires.txt +0 -0
  179. {pentool-0.2.7 → pentool-0.2.9}/pentool.egg-info/top_level.txt +0 -0
  180. {pentool-0.2.7 → pentool-0.2.9}/setup.cfg +0 -0
  181. {pentool-0.2.7 → pentool-0.2.9}/tests/test_user_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pentool
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: Modern Web Pentesting TUI - Professional security testing tool
5
5
  Author-email: Pentool Team <dev@pentool.pro>
6
6
  License: AGPL-3.0
@@ -58,6 +58,25 @@ Dynamic: license-file
58
58
 
59
59
  ---
60
60
 
61
+ ## 💸 Support & Pricing
62
+
63
+ > Pentool is open-source and free to use. If it saves you time on a pentest — consider supporting development.
64
+ > Full pricing details and sponsorship options: **[pentool.pro](https://pentool.pro)**
65
+
66
+ | | | |
67
+ |---|---|---|
68
+ | ⭐ **Sponsor (individual)** | **$5** one-time or /month | [Sponsor on GitHub](https://github.com/sponsors/DrXOps) |
69
+ | 🏢 **Sponsor (company)** | **$50** one-time or /month | Logo in README + website · [pentool.pro/sponsor](https://pentool.pro/sponsor) |
70
+ | 🔑 **PRO License — Early Access** | **$29** *(beta price, until Dec 31 2026)* | After release: $99/year |
71
+
72
+ > **🎁 Beta loyalty discount:** Everyone who buys PRO for $29 gets a **lifetime 50% discount on annual renewal** (i.e. $49.50/year instead of $99).
73
+ > This discount applies **only to first buyers during the beta — until December 31, 2026**.
74
+
75
+ **PRO license is issued manually during beta.**
76
+ To purchase, contact: **[@sudores](https://t.me/sudores)** (Telegram) · **dev@pentool.pro**
77
+
78
+ ---
79
+
61
80
  **Pentool** is a terminal-based (TUI) security toolkit for penetration testers and security researchers.
62
81
  It combines HTTP interception, vulnerability scanning, automated attacks, and data analysis — all inside your terminal.
63
82
  Fast, transparent, and built for real-world testing.
@@ -186,7 +205,8 @@ Pentool is built and maintained by a solo developer in spare time.
186
205
  If it saves you hours on a pentest — consider giving back. Every contribution directly funds new features, fixes, and faster releases.
187
206
 
188
207
  - ⭐ **[Star on GitHub](https://github.com/DrXOps/pentool)** — free, takes 2 seconds, helps visibility enormously
189
- - 🔑 **PRO license** — get early access and support development → **[@sudores](https://t.me/sudores)**
208
+ - 💸 **[Sponsor the project](https://pentool.pro/sponsor)** — $5 (individual) or $50 (company, includes logo placement)
209
+ - 🔑 **[PRO license — $29 beta price](https://pentool.pro)** — early access + lifetime loyalty discount
190
210
  - 💬 **Share** — tell a colleague, post a review, or mention Pentool in your writeups
191
211
 
192
212
  > Building tools is lonely work. A star or a kind word genuinely matters. Thank you. 🙏
@@ -12,6 +12,25 @@
12
12
 
13
13
  ---
14
14
 
15
+ ## 💸 Support & Pricing
16
+
17
+ > Pentool is open-source and free to use. If it saves you time on a pentest — consider supporting development.
18
+ > Full pricing details and sponsorship options: **[pentool.pro](https://pentool.pro)**
19
+
20
+ | | | |
21
+ |---|---|---|
22
+ | ⭐ **Sponsor (individual)** | **$5** one-time or /month | [Sponsor on GitHub](https://github.com/sponsors/DrXOps) |
23
+ | 🏢 **Sponsor (company)** | **$50** one-time or /month | Logo in README + website · [pentool.pro/sponsor](https://pentool.pro/sponsor) |
24
+ | 🔑 **PRO License — Early Access** | **$29** *(beta price, until Dec 31 2026)* | After release: $99/year |
25
+
26
+ > **🎁 Beta loyalty discount:** Everyone who buys PRO for $29 gets a **lifetime 50% discount on annual renewal** (i.e. $49.50/year instead of $99).
27
+ > This discount applies **only to first buyers during the beta — until December 31, 2026**.
28
+
29
+ **PRO license is issued manually during beta.**
30
+ To purchase, contact: **[@sudores](https://t.me/sudores)** (Telegram) · **dev@pentool.pro**
31
+
32
+ ---
33
+
15
34
  **Pentool** is a terminal-based (TUI) security toolkit for penetration testers and security researchers.
16
35
  It combines HTTP interception, vulnerability scanning, automated attacks, and data analysis — all inside your terminal.
17
36
  Fast, transparent, and built for real-world testing.
@@ -140,7 +159,8 @@ Pentool is built and maintained by a solo developer in spare time.
140
159
  If it saves you hours on a pentest — consider giving back. Every contribution directly funds new features, fixes, and faster releases.
141
160
 
142
161
  - ⭐ **[Star on GitHub](https://github.com/DrXOps/pentool)** — free, takes 2 seconds, helps visibility enormously
143
- - 🔑 **PRO license** — get early access and support development → **[@sudores](https://t.me/sudores)**
162
+ - 💸 **[Sponsor the project](https://pentool.pro/sponsor)** — $5 (individual) or $50 (company, includes logo placement)
163
+ - 🔑 **[PRO license — $29 beta price](https://pentool.pro)** — early access + lifetime loyalty discount
144
164
  - 💬 **Share** — tell a colleague, post a review, or mention Pentool in your writeups
145
165
 
146
166
  > Building tools is lonely work. A star or a kind word genuinely matters. Thank you. 🙏
@@ -1,6 +1,17 @@
1
1
  """Pentool — professional web security testing toolkit with Textual TUI."""
2
2
 
3
- __version__ = "0.2.7"
3
+ from importlib.metadata import PackageNotFoundError, version as _pkg_version
4
+
5
+ try:
6
+ # Single source of truth: the version pip actually installed (matches
7
+ # `pentool --version` / `pip show pentool`), including dev-build suffixes
8
+ # like "0.2.8.dev5" that CI stamps only into the package metadata, not
9
+ # into this file. Falls back to the literal below for editable/dev
10
+ # checkouts where the package isn't registered in the environment.
11
+ __version__ = _pkg_version("pentool")
12
+ except PackageNotFoundError:
13
+ __version__ = "0.2.9"
14
+
4
15
  __author__ = "pentool"
5
16
 
6
17
 
@@ -50,6 +50,37 @@ def main() -> None:
50
50
  except Exception:
51
51
  compatible, warning = True, "" # never block startup over this check itself
52
52
 
53
+ if not compatible:
54
+ # Try to self-heal BEFORE deciding to exit. check_and_update_pro_
55
+ # package() is normally only reached from inside a running TUI
56
+ # session (on_mount's background worker) or from `pentool
57
+ # update`/`--upgrade` — but a bare `pentool` launch with an
58
+ # incompatible package used to bail out via SystemExit below
59
+ # before ever getting a chance to call it, so a broken install
60
+ # could never repair itself from a plain `pentool` invocation:
61
+ # the user was stuck needing `pentool license activate <key>`
62
+ # even though the exact same license key/machine_id could have
63
+ # fixed it automatically. Attempt one repair pass here first.
64
+ try:
65
+ import asyncio
66
+
67
+ from pentool.core.license import check_and_update_pro_package
68
+ result = asyncio.run(check_and_update_pro_package())
69
+ if result.updated:
70
+ print(
71
+ "[pentool] PRO package was out of sync — "
72
+ "re-downloaded successfully.",
73
+ file=sys.stderr,
74
+ )
75
+ compatible, warning = True, ""
76
+ elif not result.warning:
77
+ # No warning back means check_and_update_pro_package()
78
+ # itself now considers things fine (e.g. it just healed
79
+ # a previously-broken package with a matching build_id).
80
+ compatible, warning = is_pro_package_compatible()
81
+ except Exception:
82
+ pass # fall through to the original warning/exit below
83
+
53
84
  if not compatible:
54
85
  if unsafe_skip_pro_check:
55
86
  print(
@@ -5,11 +5,18 @@ from __future__ import annotations
5
5
  import asyncio
6
6
 
7
7
  from pentool.api.base_api import ExportableAPI
8
+ from pentool.api.intruder_repository import IntruderRepository
8
9
  from pentool.modules.intruder import (
9
10
  AttackType,
11
+ ChainedPayloadSource,
12
+ CharPayloadSource,
13
+ FilePayloadSource,
10
14
  IntruderAttack,
11
15
  IntruderConfig,
12
16
  IntruderResult,
17
+ NumericPayloadSource,
18
+ ProcessedPayloads,
19
+ count_lines_with_progress,
13
20
  count_markers,
14
21
  extract_marker_defaults,
15
22
  generate_char_payloads,
@@ -24,6 +31,12 @@ __all__ = [
24
31
  "IntruderAttack",
25
32
  "IntruderConfig",
26
33
  "IntruderResult",
34
+ "ChainedPayloadSource",
35
+ "CharPayloadSource",
36
+ "FilePayloadSource",
37
+ "NumericPayloadSource",
38
+ "ProcessedPayloads",
39
+ "count_lines_with_progress",
27
40
  "count_markers",
28
41
  "extract_marker_defaults",
29
42
  "generate_char_payloads",
@@ -33,13 +46,33 @@ __all__ = [
33
46
  ]
34
47
 
35
48
 
49
+
36
50
  class IntruderAPI(ExportableAPI):
37
51
 
38
- def __init__(self, db_path: str | None = None) -> None:
52
+ def __init__(self, db_path: str | None = None, http_client=None) -> None:
39
53
  self._db_path = db_path
54
+ # Optional injected HTTPClient (DIP — see
55
+ # MYPLANS/ARCHITECTURE_REFACTOR_PLAN_2026-08-09.md section 2.2).
56
+ # IntruderAttack already accepted this as an optional constructor
57
+ # param (reusing one HTTPClient/connection pool across the whole
58
+ # attack — see the БАГ-D fix in modules/intruder.py) but IntruderAPI
59
+ # had no way to pass one in — it always let IntruderAttack create
60
+ # its own from scratch. Threading it through here just extends the
61
+ # existing `http_client=None` pattern one layer up, matching how
62
+ # ScannerAPI(db_path, http_client) already works. Does not apply to
63
+ # Turbo mode — TurboIntruderAttack manages its own aiohttp session
64
+ # pool internally by design (Keep-Alive tuning specific to Turbo),
65
+ # unrelated to this DI change.
66
+ self._http_client = http_client
40
67
  self._attack: IntruderAttack | None = None
41
68
  self._task: asyncio.Task | None = None
42
69
  self._restored_results: list = []
70
+ # SQL for tab state / attack results lives in IntruderRepository
71
+ # (see MYPLANS/ARCHITECTURE_REFACTOR_PLAN_2026-08-09.md section 2.6)
72
+ # — mirrors ScannerTabRepository, the same extraction already done
73
+ # for Scanner. IntruderAPI keeps its existing public method names
74
+ # as a thin facade so no caller needs to change.
75
+ self._repo = IntruderRepository(db_path=db_path)
43
76
 
44
77
  async def start_attack(
45
78
  self,
@@ -48,27 +81,56 @@ class IntruderAPI(ExportableAPI):
48
81
  on_progress=None,
49
82
  turbo_mode: bool = False,
50
83
  ) -> str:
84
+ """Run the attack to completion and return its attack_id.
85
+
86
+ NOTE: this method awaits the whole attack — it does NOT return as
87
+ soon as the attack starts. Before this fix it fired the attack via
88
+ `asyncio.create_task(...)` and returned immediately without
89
+ awaiting it, so a caller doing `await api.start_attack(...)` got
90
+ control back right after the attack merely started, not when it
91
+ finished — `get_results()` called right after would see a near-
92
+ empty/partial result set (see IntruderService.start_attack, which
93
+ relies on start_attack() having completed the attack before it
94
+ reads get_results() and emits IntruderFinished). The task handle is
95
+ still kept on self._task so stop()/pause()/resume() and callers
96
+ that want to cancel mid-attack (via self._task) keep working the
97
+ same way as before.
98
+ """
51
99
  if turbo_mode:
52
100
  # Turbo Mode: connection pooling + Keep-Alive
53
101
  from pentool.modules.intruder_turbo import TurboIntruderAttack
54
102
  self._attack = TurboIntruderAttack(config)
55
103
  else:
56
104
  # Standard mode
57
- self._attack = IntruderAttack(config, db_path=self._db_path)
105
+ self._attack = IntruderAttack(config, db_path=self._db_path, http_client=self._http_client)
58
106
 
59
107
  _on_result = on_result if on_result else lambda r: None
60
108
  _on_progress = on_progress if on_progress else lambda d, t: None
61
109
 
62
110
  self._task = asyncio.create_task(self._attack.run(_on_result, _on_progress))
111
+ try:
112
+ await self._task
113
+ except asyncio.CancelledError:
114
+ pass
63
115
  return self._attack.attack_id if hasattr(self._attack, 'attack_id') else "turbo"
64
116
 
65
117
  async def pause(self) -> None:
66
- if self._attack:
118
+ """Pause the running attack, if supported.
119
+
120
+ TurboIntruderAttack has no pause/resume — Turbo mode intentionally
121
+ runs to completion or stop() only (see modules/intruder_turbo.py).
122
+ Before this API method was actually reachable from IntruderScreen,
123
+ turbo_mode was silently never honored there (a pre-existing bug —
124
+ see MYPLANS/ARCHITECTURE_REFACTOR_PLAN_2026-08-09.md section 2.7),
125
+ so Pause/Resume during a real Turbo run was never exercised. Guard
126
+ with hasattr so enabling real Turbo mode doesn't crash Pause.
127
+ """
128
+ if self._attack and hasattr(self._attack, "pause"):
67
129
  await self._attack.pause()
68
130
 
69
131
  async def resume(self) -> None:
70
- """Resume the attack after a pause."""
71
- if self._attack:
132
+ """Resume the attack after a pause, if supported (see pause() note)."""
133
+ if self._attack and hasattr(self._attack, "resume"):
72
134
  await self._attack.resume()
73
135
 
74
136
  async def stop(self) -> None:
@@ -97,11 +159,24 @@ class IntruderAPI(ExportableAPI):
97
159
  return load_payloads_from_file(path)
98
160
 
99
161
  async def generate_numeric(self, start: int, end: int, step: int = 1) -> list[str]:
162
+ """Eager numeric generation — kept for backward compatibility.
163
+
164
+ Prefer constructing `NumericPayloadSource(start, end, step)` directly
165
+ for anything the UI will hold onto (see IntruderScreen._GenerateDialog),
166
+ since this materializes the whole range into a list immediately.
167
+ """
100
168
  return generate_numeric_payloads(start, end, step)
101
169
 
102
170
  async def generate_chars(
103
171
  self, charset: str, min_len: int, max_len: int
104
172
  ) -> list[str]:
173
+ """Eager charset brute-force generation — kept for backward
174
+ compatibility. Prefer constructing
175
+ `CharPayloadSource(charset, min_len, max_len)` directly — this
176
+ materializes every combination into a list immediately, which for
177
+ even a modest charset/length is a combinatorial explosion (see
178
+ CharPayloadSource's docstring).
179
+ """
105
180
  return generate_char_payloads(charset, min_len, max_len)
106
181
 
107
182
  def export_csv(self, path: str) -> None:
@@ -116,132 +191,52 @@ class IntruderAPI(ExportableAPI):
116
191
  template: str,
117
192
  attack_type: str,
118
193
  payloads: list[list[str]],
194
+ tab_uid: str = "",
119
195
  ) -> None:
120
196
  """Save Intruder tab state (template, attack type, payloads) to DB."""
121
- if not self._db_path:
122
- return
123
- import json
124
-
125
- from pentool.core.database import get_db
126
-
127
- async with get_db(self._db_path) as db:
128
- # Delete old state for this tab
129
- await db.execute("DELETE FROM intruder_state WHERE tab_name = ?", (tab_name,))
130
- # Insert new state
131
- await db.execute(
132
- """INSERT INTO intruder_state (tab_name, template, attack_type, payloads_json, updated_at)
133
- VALUES (?, ?, ?, ?, datetime('now'))""",
134
- (tab_name, template, attack_type, json.dumps(payloads)),
135
- )
136
- await db.commit()
137
-
138
- async def load_state(self, tab_name: str) -> dict | None:
197
+ await self._repo.save_state(tab_name, template, attack_type, payloads, tab_uid=tab_uid)
198
+
199
+ async def load_state(self, tab_name: str, tab_uid: str = "") -> dict | None:
139
200
  """Load Intruder tab state from DB."""
140
- if not self._db_path:
141
- return None
142
- import json
143
-
144
- from pentool.core.database import get_db
145
-
146
- async with get_db(self._db_path) as db:
147
- cursor = await db.execute(
148
- "SELECT template, attack_type, payloads_json FROM intruder_state WHERE tab_name = ?",
149
- (tab_name,),
150
- )
151
- row = await cursor.fetchone()
152
- if not row:
153
- return None
154
- return {
155
- "template": row[0],
156
- "attack_type": row[1],
157
- "payloads": json.loads(row[2]),
158
- }
159
-
160
- async def save_result(self, result: IntruderResult, project_id: int | None = None) -> None:
201
+ return await self._repo.load_state(tab_name, tab_uid=tab_uid)
202
+
203
+ async def get_tabs(self) -> list[dict]:
204
+ """List all Intruder tabs with saved state, most recently updated first."""
205
+ return await self._repo.get_tabs()
206
+
207
+ async def delete_tab(self, tab_uid: str) -> None:
208
+ """Delete a tab's saved state and results by its stable tab_uid."""
209
+ await self._repo.delete_tab(tab_uid)
210
+
211
+ async def switch_db(self, db_path: str) -> None:
212
+ """Point this API's repository at a different project DB file.
213
+
214
+ Lets IntruderScreen keep ONE persistent IntruderAPI/IntruderRepository
215
+ instance for the app's lifetime (mirrors ProxyService/HttpStorage)
216
+ instead of constructing a new one — and therefore a new SQLite
217
+ connection — on every single call. See BaseSqliteStorage.switch_db.
218
+ """
219
+ self._db_path = db_path
220
+ await self._repo.switch_db(db_path)
221
+
222
+ async def close(self) -> None:
223
+ """Close the underlying persistent SQLite connection, if open."""
224
+ await self._repo.close()
225
+
226
+ async def save_result(
227
+ self, result: IntruderResult, project_id: int | None = None, tab_uid: str = ""
228
+ ) -> None:
161
229
  """Save a single intruder result to DB."""
162
- if not self._db_path:
163
- return
164
- import json
165
-
166
- from pentool.core.database import get_db
167
-
168
- async with get_db(self._db_path) as db:
169
- await db.execute(
170
- """INSERT INTO intruder_results
171
- (project_id, attack_id, request_number, payload_values, request_raw,
172
- response_raw, response_status, response_length, response_time_ms, error, timestamp)
173
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
174
- (
175
- project_id,
176
- result.attack_id,
177
- result.request_number,
178
- json.dumps(result.payload_values),
179
- result.request_raw,
180
- result.response_raw,
181
- result.response_status,
182
- result.response_length,
183
- result.response_time_ms,
184
- result.error,
185
- result.timestamp.isoformat(),
186
- ),
187
- )
188
- await db.commit()
230
+ await self._repo.save_result(result, project_id, tab_uid=tab_uid)
189
231
 
190
232
  async def get_results_from_db(
191
233
  self,
192
234
  attack_id: str | None = None,
193
235
  limit: int = 1000,
236
+ tab_uid: str = "",
194
237
  ) -> list[IntruderResult]:
195
238
  """Load intruder results from DB."""
196
- if not self._db_path:
197
- return []
198
- import json
199
- from datetime import datetime, timezone
200
-
201
- from pentool.core.database import get_db
202
-
203
- async with get_db(self._db_path) as db:
204
- if attack_id:
205
- cursor = await db.execute(
206
- """SELECT id, attack_id, request_number, payload_values, request_raw,
207
- response_raw, response_status, response_length, response_time_ms, error, timestamp
208
- FROM intruder_results WHERE attack_id = ?
209
- ORDER BY request_number LIMIT ?""",
210
- (attack_id, limit),
211
- )
212
- else:
213
- cursor = await db.execute(
214
- """SELECT id, attack_id, request_number, payload_values, request_raw,
215
- response_raw, response_status, response_length, response_time_ms, error, timestamp
216
- FROM intruder_results
217
- ORDER BY timestamp DESC LIMIT ?""",
218
- (limit,),
219
- )
220
- rows = await cursor.fetchall()
221
- results = []
222
- for row in rows:
223
- try:
224
- ts = datetime.fromisoformat(row[10])
225
- if ts.tzinfo is None:
226
- ts = ts.replace(tzinfo=timezone.utc)
227
- except Exception:
228
- ts = datetime.now(timezone.utc)
229
- results.append(
230
- IntruderResult(
231
- id=str(row[0]),
232
- attack_id=row[1],
233
- request_number=row[2],
234
- payload_values=json.loads(row[3]) if row[3] else [],
235
- request_raw=row[4],
236
- response_raw=row[5],
237
- response_status=row[6],
238
- response_length=row[7],
239
- response_time_ms=row[8],
240
- error=row[9],
241
- timestamp=ts,
242
- )
243
- )
244
- return results
239
+ return await self._repo.get_results(attack_id, limit, tab_uid=tab_uid)
245
240
 
246
241
  # ── Project persistence ────────────────────────────────────────────────────
247
242