hydrasight 4.1.0__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 (75) hide show
  1. hydrasight/__init__.py +27 -0
  2. hydrasight/__main__.py +25 -0
  3. hydrasight/cli/__init__.py +37 -0
  4. hydrasight/cli/display.py +236 -0
  5. hydrasight/cli/shell.py +393 -0
  6. hydrasight/cli/shell_handlers.py +752 -0
  7. hydrasight/cli/shell_renderer.py +897 -0
  8. hydrasight/config/__init__.py +35 -0
  9. hydrasight/config/defaults.py +229 -0
  10. hydrasight/config/loader.py +99 -0
  11. hydrasight/constants.py +37 -0
  12. hydrasight/core/__init__.py +3 -0
  13. hydrasight/core/builtin_actions.py +202 -0
  14. hydrasight/core/command_builder.py +93 -0
  15. hydrasight/core/engine.py +1101 -0
  16. hydrasight/core/hash_crack.py +75 -0
  17. hydrasight/core/planner.py +317 -0
  18. hydrasight/core/profiles.py +77 -0
  19. hydrasight/core/registry.py +46 -0
  20. hydrasight/integrations/__init__.py +17 -0
  21. hydrasight/integrations/exploit_db.py +192 -0
  22. hydrasight/integrations/exploit_suggestion.py +534 -0
  23. hydrasight/integrations/kali_api.py +120 -0
  24. hydrasight/models/__init__.py +15 -0
  25. hydrasight/models/actions.py +64 -0
  26. hydrasight/models/commands.py +154 -0
  27. hydrasight/models/finding_confidence.py +9 -0
  28. hydrasight/models/finding_record.py +411 -0
  29. hydrasight/models/findings.py +461 -0
  30. hydrasight/models/planner_state.py +182 -0
  31. hydrasight/models/report_model.py +279 -0
  32. hydrasight/models/roe.py +170 -0
  33. hydrasight/models/timeline.py +32 -0
  34. hydrasight/parsers/__init__.py +5 -0
  35. hydrasight/parsers/base_parser.py +246 -0
  36. hydrasight/reporting/__init__.py +7 -0
  37. hydrasight/reporting/json_reporter.py +32 -0
  38. hydrasight/reporting/outcome.py +78 -0
  39. hydrasight/reporting/pdf_reporter.py +522 -0
  40. hydrasight/reporting/remediation.py +197 -0
  41. hydrasight/security/__init__.py +1 -0
  42. hydrasight/security/audit.py +356 -0
  43. hydrasight/security/authorization.py +255 -0
  44. hydrasight/security/command_sanitizer.py +556 -0
  45. hydrasight/services/__init__.py +35 -0
  46. hydrasight/services/action_planner.py +235 -0
  47. hydrasight/services/ai_client.py +192 -0
  48. hydrasight/services/chat_ai_client.py +133 -0
  49. hydrasight/services/chat_controller.py +137 -0
  50. hydrasight/services/command_router.py +133 -0
  51. hydrasight/services/confirmation_manager.py +143 -0
  52. hydrasight/services/context_builder.py +157 -0
  53. hydrasight/services/dispatcher.py +390 -0
  54. hydrasight/services/execution_policy.py +153 -0
  55. hydrasight/services/intent_classifier.py +549 -0
  56. hydrasight/services/intent_router.py +90 -0
  57. hydrasight/services/post_access/__init__.py +38 -0
  58. hydrasight/services/post_access/base.py +70 -0
  59. hydrasight/services/post_access/factory.py +70 -0
  60. hydrasight/services/post_access/ftp_handler.py +99 -0
  61. hydrasight/services/post_access/msf_handlers.py +148 -0
  62. hydrasight/services/post_access/ssh_handler.py +53 -0
  63. hydrasight/services/post_access/types.py +48 -0
  64. hydrasight/services/post_access/web_handler.py +144 -0
  65. hydrasight/services/session_manager.py +127 -0
  66. hydrasight/services/verifier.py +323 -0
  67. hydrasight/utils/__init__.py +6 -0
  68. hydrasight/utils/ip_utils.py +49 -0
  69. hydrasight/utils/time_utils.py +8 -0
  70. hydrasight-4.1.0.dist-info/METADATA +678 -0
  71. hydrasight-4.1.0.dist-info/RECORD +75 -0
  72. hydrasight-4.1.0.dist-info/WHEEL +5 -0
  73. hydrasight-4.1.0.dist-info/entry_points.txt +2 -0
  74. hydrasight-4.1.0.dist-info/licenses/LICENSE +32 -0
  75. hydrasight-4.1.0.dist-info/top_level.txt +1 -0
hydrasight/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ """HydraSight — AI-Orchestrated, authorization-gated Penetration Testing Framework."""
2
+
3
+ from hydrasight.constants import (
4
+ APP_NAME,
5
+ CODENAME,
6
+ VERSION,
7
+ __app_name__,
8
+ __author__,
9
+ __codename__,
10
+ __license__,
11
+ __summary__,
12
+ __title__,
13
+ __version__,
14
+ )
15
+
16
+ __all__ = [
17
+ "VERSION",
18
+ "CODENAME",
19
+ "APP_NAME",
20
+ "__version__",
21
+ "__codename__",
22
+ "__app_name__",
23
+ "__title__",
24
+ "__license__",
25
+ "__author__",
26
+ "__summary__",
27
+ ]
hydrasight/__main__.py ADDED
@@ -0,0 +1,25 @@
1
+ """Entry point — python -m hydrasight"""
2
+
3
+ import sys
4
+ import traceback
5
+
6
+ from hydrasight.cli.shell import Shell
7
+ from hydrasight.config.loader import load_config
8
+
9
+
10
+ def main() -> None:
11
+
12
+ cfg = load_config()
13
+ try:
14
+ Shell(cfg).run()
15
+ except KeyboardInterrupt:
16
+ print("\n[!] interrupted")
17
+ sys.exit(130)
18
+ except Exception: # noqa: BLE001
19
+ print("\n[!] fatal error:")
20
+ traceback.print_exc()
21
+ sys.exit(1)
22
+
23
+
24
+ if __name__ == "__main__":
25
+ main()
@@ -0,0 +1,37 @@
1
+ """CLI package."""
2
+
3
+ from hydrasight.cli.display import (
4
+ analysis_panel,
5
+ console,
6
+ div,
7
+ err,
8
+ hit,
9
+ info,
10
+ label,
11
+ ok,
12
+ phase_header,
13
+ raw_output,
14
+ result_line,
15
+ spinner,
16
+ stats_line,
17
+ task_line,
18
+ warn,
19
+ )
20
+
21
+ __all__ = [
22
+ "console",
23
+ "div",
24
+ "ok",
25
+ "warn",
26
+ "info",
27
+ "err",
28
+ "hit",
29
+ "label",
30
+ "spinner",
31
+ "phase_header",
32
+ "task_line",
33
+ "result_line",
34
+ "analysis_panel",
35
+ "raw_output",
36
+ "stats_line",
37
+ ]
@@ -0,0 +1,236 @@
1
+ """
2
+ All Rich terminal UI helpers — panels, spinners, stats line, phase headers.
3
+
4
+ The module-level `console` object is the single shared Rich Console
5
+ used throughout the entire application.
6
+ """
7
+
8
+ import json
9
+ import re
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ from rich import box
13
+ from rich.console import Console
14
+ from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
15
+ from rich.rule import Rule
16
+ from rich.table import Table
17
+
18
+ from hydrasight.config.defaults import TOOL_LABELS, P
19
+
20
+ if TYPE_CHECKING:
21
+ from hydrasight.models.findings import Findings
22
+
23
+ # ── shared console ────────────────────────────────────────────────────────────
24
+ console = Console(highlight=False)
25
+
26
+
27
+ # ── dividers ──────────────────────────────────────────────────────────────────
28
+
29
+
30
+ def div(label: str = "") -> None:
31
+ if label:
32
+ console.print(Rule(f"[{P.MUTED}] {label} [/]", style=P.DIM))
33
+ else:
34
+ console.print(Rule(style=P.DIM))
35
+
36
+
37
+ # ── one-liner status messages ─────────────────────────────────────────────────
38
+
39
+
40
+ def ok(msg: str) -> None:
41
+ console.print(f" [{P.PRIMARY}][+][/] [{P.TEXT}]{msg}[/]")
42
+
43
+
44
+ def warn(msg: str) -> None:
45
+ console.print(f" [{P.AMBER}][!][/] [{P.AMBER}]{msg}[/]")
46
+
47
+
48
+ def info(msg: str) -> None:
49
+ console.print(f" [{P.DIM}][>][/] [{P.MUTED}]{msg}[/]")
50
+
51
+
52
+ def err(msg: str) -> None:
53
+ console.print(f" [{P.RED}][x][/] [{P.RED}]{msg}[/]")
54
+
55
+
56
+ def hit(msg: str) -> None:
57
+ console.print(f" [{P.BRIGHT}][*][/] [{P.BRIGHT}]{msg}[/]")
58
+
59
+
60
+ def label(key: str, val: str, kw: int = 14) -> None:
61
+ console.print(f" [{P.MUTED}]{key.ljust(kw)}[/] [{P.TEXT}]{val}[/]")
62
+
63
+
64
+ # ── spinners ──────────────────────────────────────────────────────────────────
65
+
66
+
67
+ def spinner(text: str) -> Progress:
68
+ return Progress(
69
+ SpinnerColumn(spinner_name="line", style=P.PRIMARY),
70
+ TextColumn(f"[{P.MUTED}]{text}[/]"),
71
+ TimeElapsedColumn(),
72
+ console=console,
73
+ transient=True,
74
+ )
75
+
76
+
77
+ # ── phase header ──────────────────────────────────────────────────────────────
78
+
79
+
80
+ def phase_header(phase_id: str, phase_label: str, color: str, idx: int, total: int) -> None:
81
+ pct = int((idx / total) * 100)
82
+ filled = int((idx / total) * 24)
83
+ progress_bar = f"[{P.PRIMARY}]{'█' * filled}[/][{P.DIM}]{'─' * (24 - filled)}[/]"
84
+ console.print()
85
+ console.print(
86
+ f" [{P.DIM}]┌──[/] [bold {color}]{phase_label.upper()}[/]"
87
+ f" [{P.DIM}]│[/] [{P.MUTED}]phase {idx}/{total}[/]"
88
+ f" [{P.DIM}]│[/] [{P.MUTED}]{phase_id}[/]"
89
+ )
90
+ console.print(f" [{P.DIM}]│[/] {progress_bar} [{P.PRIMARY}]{pct}%[/]")
91
+ console.print(f" [{P.DIM}]└──────────────────────────────[/]")
92
+
93
+
94
+ # ── tool execution lines ──────────────────────────────────────────────────────
95
+
96
+
97
+ def task_line(tool: str) -> None:
98
+ console.print(f"\n [{P.MUTED}]exec[/] [{P.PRIMARY}]{TOOL_LABELS.get(tool, tool)}[/]")
99
+
100
+
101
+ def result_line(tool: str, elapsed: float, chars: int, warnings: list[str]) -> None:
102
+ lbl = TOOL_LABELS.get(tool, tool)
103
+ char_s = f"{chars:,}" if chars else "0"
104
+ color = P.PRIMARY if chars else P.AMBER
105
+ console.print(
106
+ f" [{P.PRIMARY}][+][/] [{P.MUTED}]{lbl}[/]"
107
+ f" [{P.DIM}]│[/] [{P.DIM}]{elapsed:.1f}s[/]"
108
+ f" [{P.DIM}]│[/] [{color}]{char_s} bytes[/]"
109
+ )
110
+ for w in warnings:
111
+ warn(w)
112
+
113
+
114
+ # ── AI analysis panel ─────────────────────────────────────────────────────────
115
+
116
+
117
+ def analysis_panel(text: str) -> None:
118
+ section_map = {
119
+ "PORTS": (P.BLUE, "ports "),
120
+ "VULNS": (P.AMBER, "vulns "),
121
+ "CREDS": (P.PRIMARY, "creds "),
122
+ "SESSIONS": (P.PRIMARY, "session "),
123
+ "NOTES": (P.MUTED, "notes "),
124
+ }
125
+ lines_out: list[tuple[str, str, str]] = []
126
+ clean = re.sub(r"```(?:json)?|```", "", text).strip()
127
+ try:
128
+ data = json.loads(clean)
129
+ if isinstance(data, dict):
130
+ for key, (color, lbl) in section_map.items():
131
+ val: Any = data.get(key) or data.get(key.lower())
132
+ if val is None:
133
+ val = "—"
134
+ elif isinstance(val, list):
135
+ parts: list[str] = []
136
+ for item in val:
137
+ if isinstance(item, dict):
138
+ sev = item.get("severity", "")
139
+ desc = item.get(
140
+ "description",
141
+ item.get("name", str(item)),
142
+ )
143
+ parts.append(f"[{sev.upper()}] {desc}" if sev else str(desc))
144
+ else:
145
+ parts.append(str(item))
146
+ val = " │ ".join(parts) if parts else "—"
147
+ elif str(val).lower() in ("null", "none", ""):
148
+ val = "—"
149
+ lines_out.append((color, lbl, str(val)))
150
+ except (json.JSONDecodeError, TypeError):
151
+ for line in text.splitlines():
152
+ line = line.strip()
153
+ if not line:
154
+ continue
155
+ matched = False
156
+ for key, (color, lbl) in section_map.items():
157
+ if line.upper().startswith(key + ":"):
158
+ val_str = line[len(key) + 1 :].strip() or "—"
159
+ lines_out.append((color, lbl, val_str))
160
+ matched = True
161
+ break
162
+ if not matched and lines_out:
163
+ c, lb, v = lines_out[-1]
164
+ lines_out[-1] = (c, lb, f"{v} {line}")
165
+ if not lines_out:
166
+ return
167
+ console.print()
168
+ console.print(f" [{P.DIM}]┌──[/] [{P.MUTED}]intelligence[/]")
169
+ for color, lbl, val in lines_out:
170
+ if len(val) > 92:
171
+ val = val[:89] + "…"
172
+ console.print(f" [{P.DIM}]│[/] [{P.MUTED}]{lbl}[/] [{color}]{val}[/]")
173
+ console.print(f" [{P.DIM}]└────────────────────────────────[/]")
174
+
175
+
176
+ # ── raw output preview ────────────────────────────────────────────────────────
177
+
178
+
179
+ def raw_output(output: str, verbosity: int) -> None:
180
+ if verbosity >= 2 and output:
181
+ preview = output[:1200] + ("…" if len(output) > 1200 else "")
182
+ console.print()
183
+ console.print(f" [{P.DIM}]┌──[/] [{P.MUTED}]raw output[/]")
184
+ for line in preview.splitlines()[:35]:
185
+ console.print(f" [{P.DIM}]│[/] [{P.MUTED}]{line}[/]")
186
+ if len(output) > 1200:
187
+ console.print(f" [{P.DIM}]│ … truncated[/]")
188
+ console.print(f" [{P.DIM}]└────────────────────────────────[/]")
189
+
190
+
191
+ # ── stats line ────────────────────────────────────────────────────────────────
192
+
193
+
194
+ def stats_line(findings: "Findings") -> None:
195
+ rc = findings.overall_risk
196
+ risk_color = {
197
+ "CRITICAL": P.RED,
198
+ "HIGH": P.AMBER,
199
+ "MEDIUM": P.YELLOW,
200
+ "LOW": P.BLUE,
201
+ "NONE": P.DIM,
202
+ }.get(rc, P.DIM)
203
+ console.print(
204
+ f"\n [{P.DIM}]│[/] [{P.MUTED}]ports[/] "
205
+ f"[{P.TEXT}]{len(findings.ports):>3}[/]"
206
+ f" [{P.DIM}]│[/] [{P.MUTED}]vulns[/] "
207
+ f"[{P.AMBER}]{len(findings.vulns):>3}[/]"
208
+ f" [{P.DIM}]│[/] [{P.MUTED}]crit[/] "
209
+ f"[{P.RED}]{findings.critical_count:>3}[/]"
210
+ f" [{P.DIM}]│[/] [{P.MUTED}]creds[/] "
211
+ f"[{P.BRIGHT}]{len(findings.credentials):>2}[/]"
212
+ f" [{P.DIM}]│[/] [{P.MUTED}]sess[/] "
213
+ f"[{P.BRIGHT}]{len(findings.sessions):>2}[/]"
214
+ f" [{P.DIM}]│[/] [{P.MUTED}]risk[/] "
215
+ f"[{risk_color}]{rc}[/]"
216
+ f" [{P.DIM}]│[/]"
217
+ )
218
+
219
+
220
+ # ── findings table helper ─────────────────────────────────────────────────────
221
+
222
+
223
+ def make_table(*cols: tuple) -> Table:
224
+ t = Table(
225
+ box=box.SIMPLE,
226
+ show_header=True,
227
+ header_style=P.MUTED,
228
+ border_style=P.DIM,
229
+ padding=(0, 2),
230
+ )
231
+ for name, style, width in cols:
232
+ kw: dict[str, Any] = {"style": style}
233
+ if width:
234
+ kw["width"] = width
235
+ t.add_column(name, **kw)
236
+ return t