openhack 0.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 (113) hide show
  1. openhack/__init__.py +2 -0
  2. openhack/__main__.py +225 -0
  3. openhack/agents/__init__.py +30 -0
  4. openhack/agents/base.py +230 -0
  5. openhack/agents/browser_verifier.py +679 -0
  6. openhack/agents/browser_verifier_swarm.py +256 -0
  7. openhack/agents/checkpoint.py +89 -0
  8. openhack/agents/context_manager.py +356 -0
  9. openhack/agents/coordinator.py +1105 -0
  10. openhack/agents/endpoint_analyst.py +307 -0
  11. openhack/agents/feature_hunter.py +93 -0
  12. openhack/agents/hunter.py +481 -0
  13. openhack/agents/hunter_swarm.py +385 -0
  14. openhack/agents/llm.py +334 -0
  15. openhack/agents/recon.py +19 -0
  16. openhack/agents/sandbox_verifier.py +396 -0
  17. openhack/agents/sandbox_verifier_swarm.py +250 -0
  18. openhack/agents/session.py +286 -0
  19. openhack/agents/validator.py +217 -0
  20. openhack/agents/validator_swarm.py +106 -0
  21. openhack/auth.py +175 -0
  22. openhack/browser/__init__.py +12 -0
  23. openhack/browser/runner.py +385 -0
  24. openhack/categories.py +130 -0
  25. openhack/config.py +201 -0
  26. openhack/deterministic_recon.py +464 -0
  27. openhack/entry_points.py +745 -0
  28. openhack/framework_classifier.py +515 -0
  29. openhack/framework_detection.py +269 -0
  30. openhack/headless_scan.py +179 -0
  31. openhack/prompts/__init__.py +108 -0
  32. openhack/prompts/browser_verifier.py +171 -0
  33. openhack/prompts/coordinator.py +31 -0
  34. openhack/prompts/django/__init__.py +32 -0
  35. openhack/prompts/django/auth_bypass.py +76 -0
  36. openhack/prompts/django/csrf.py +62 -0
  37. openhack/prompts/django/data_exposure.py +67 -0
  38. openhack/prompts/django/idor.py +74 -0
  39. openhack/prompts/django/injection.py +67 -0
  40. openhack/prompts/django/misconfiguration.py +70 -0
  41. openhack/prompts/django/ssrf.py +64 -0
  42. openhack/prompts/endpoint_analyst.py +122 -0
  43. openhack/prompts/express/__init__.py +29 -0
  44. openhack/prompts/express/auth_bypass.py +71 -0
  45. openhack/prompts/express/data_exposure.py +77 -0
  46. openhack/prompts/express/idor.py +69 -0
  47. openhack/prompts/express/injection.py +75 -0
  48. openhack/prompts/express/misconfiguration.py +72 -0
  49. openhack/prompts/express/ssrf.py +63 -0
  50. openhack/prompts/feature_hunter.py +140 -0
  51. openhack/prompts/flask/__init__.py +29 -0
  52. openhack/prompts/flask/auth_bypass.py +86 -0
  53. openhack/prompts/flask/data_exposure.py +78 -0
  54. openhack/prompts/flask/idor.py +83 -0
  55. openhack/prompts/flask/injection.py +77 -0
  56. openhack/prompts/flask/misconfiguration.py +73 -0
  57. openhack/prompts/flask/ssrf.py +65 -0
  58. openhack/prompts/hunter.py +362 -0
  59. openhack/prompts/hunter_continuation_loop.py +12 -0
  60. openhack/prompts/hunter_continuation_no_findings.py +19 -0
  61. openhack/prompts/hunter_continuation_no_progress.py +22 -0
  62. openhack/prompts/hunter_tool_instructions.py +55 -0
  63. openhack/prompts/nextjs/__init__.py +42 -0
  64. openhack/prompts/nextjs/auth_bypass.py +80 -0
  65. openhack/prompts/nextjs/csrf.py +71 -0
  66. openhack/prompts/nextjs/data_exposure.py +88 -0
  67. openhack/prompts/nextjs/idor.py +64 -0
  68. openhack/prompts/nextjs/injection.py +65 -0
  69. openhack/prompts/nextjs/middleware_bypass.py +75 -0
  70. openhack/prompts/nextjs/misconfiguration.py +92 -0
  71. openhack/prompts/nextjs/server_actions.py +97 -0
  72. openhack/prompts/nextjs/ssrf.py +66 -0
  73. openhack/prompts/nextjs/xss.py +69 -0
  74. openhack/prompts/pr_analysis_system.py +80 -0
  75. openhack/prompts/pr_analysis_user.py +11 -0
  76. openhack/prompts/project_context.py +89 -0
  77. openhack/prompts/recon.py +199 -0
  78. openhack/prompts/reporter.py +88 -0
  79. openhack/prompts/researchers.py +434 -0
  80. openhack/prompts/sandbox_verifier.py +128 -0
  81. openhack/prompts/supabase/__init__.py +39 -0
  82. openhack/prompts/supabase/auth_tokens.py +131 -0
  83. openhack/prompts/supabase/edge_functions.py +150 -0
  84. openhack/prompts/supabase/graphql.py +102 -0
  85. openhack/prompts/supabase/postgrest.py +99 -0
  86. openhack/prompts/supabase/realtime.py +93 -0
  87. openhack/prompts/supabase/rls.py +110 -0
  88. openhack/prompts/supabase/rpc_functions.py +127 -0
  89. openhack/prompts/supabase/storage.py +110 -0
  90. openhack/prompts/supabase/tenant_isolation.py +118 -0
  91. openhack/prompts/validator.py +319 -0
  92. openhack/prompts/validator_continuation_incomplete.py +12 -0
  93. openhack/prompts/validator_tool_instructions.py +29 -0
  94. openhack/quality.py +231 -0
  95. openhack/sandbox/__init__.py +12 -0
  96. openhack/sandbox/orchestrator.py +517 -0
  97. openhack/sandbox/runner.py +177 -0
  98. openhack/scan_session.py +245 -0
  99. openhack/setup.py +452 -0
  100. openhack/static_validator.py +612 -0
  101. openhack/tools/__init__.py +1 -0
  102. openhack/tools/ast_tools.py +307 -0
  103. openhack/tools/coverage.py +1078 -0
  104. openhack/tools/filesystem.py +404 -0
  105. openhack/tools/nextjs.py +258 -0
  106. openhack/tools/registry.py +52 -0
  107. openhack/tui.py +3450 -0
  108. openhack/updates.py +170 -0
  109. openhack-0.1.0.dist-info/METADATA +189 -0
  110. openhack-0.1.0.dist-info/RECORD +113 -0
  111. openhack-0.1.0.dist-info/WHEEL +4 -0
  112. openhack-0.1.0.dist-info/entry_points.txt +2 -0
  113. openhack-0.1.0.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,12 @@
1
+ """
2
+ Continuation prompt for the Validator agent when validation is incomplete.
3
+
4
+ Use .format(total_validated=..., total_findings=..., unvalidated_count=...) to fill in the dynamic values.
5
+ """
6
+
7
+ VALIDATOR_CONTINUATION_INCOMPLETE = (
8
+ "You stopped early. You've only validated {total_validated} of {total_findings} findings. "
9
+ "There are still {unvalidated_count} findings that need validation. "
10
+ "Please continue validating the remaining findings using validate_finding for each one, "
11
+ "then call finish_validation when complete."
12
+ )
@@ -0,0 +1,29 @@
1
+ """
2
+ Tool usage instructions appended to the Validator agent's system prompt.
3
+ """
4
+
5
+ VALIDATOR_TOOL_INSTRUCTIONS = """
6
+
7
+ ## CRITICAL: You MUST Use Tools to Report Results
8
+
9
+ You have two special tools you MUST use:
10
+
11
+ ### 1. `validate_finding` - Call for EACH finding
12
+ For EVERY finding (Finding 1, Finding 2, etc.), you MUST call `validate_finding` with:
13
+ - `finding_index`: The finding number (1, 2, 3, etc.)
14
+ - `status`: "confirmed", "false_positive", or "needs_more_info"
15
+ - `confidence`: "high", "medium", or "low"
16
+ - For confirmed findings, also include: `cvss_score`, `evidence`, `poc`, `fix`
17
+
18
+ ### 2. `finish_validation` - Call when ALL findings are validated
19
+ After you have called `validate_finding` for ALL findings, call `finish_validation` to signal completion.
20
+
21
+ ## WORKFLOW (Follow This Exactly)
22
+
23
+ 1. For each finding, read relevant code and analyze
24
+ 2. Call `validate_finding` with your assessment
25
+ 3. Repeat for ALL findings
26
+ 4. Call `finish_validation` when done
27
+
28
+ DO NOT stop without calling these tools. DO NOT output text summaries instead of tool calls.
29
+ """
openhack/quality.py ADDED
@@ -0,0 +1,231 @@
1
+ """
2
+ Deterministic quality gates for scan findings.
3
+
4
+ These run AFTER validation and BEFORE final report generation.
5
+ No LLM calls -- pure code logic.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from typing import Optional
12
+
13
+ from openhack.categories import normalize_category
14
+
15
+ SEVERITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
16
+ SEVERITY_NAMES = {v: k for k, v in SEVERITY_ORDER.items()}
17
+
18
+ _GENERIC_SEGMENTS: set[str] = {
19
+ "src", "app", "apps", "api", "v1", "v2", "v3",
20
+ "modules", "controllers", "handlers", "routes", "routers",
21
+ "lib", "libs", "utils", "helpers", "common", "shared",
22
+ "pages", "components", "services", "middleware",
23
+ "server", "client", "core", "internal", "external",
24
+ "pkg", "packages", "node_modules", "dist", "build",
25
+ "ee", "ce", "oss", "pro", "test", "tests", "spec",
26
+ "config", "configs", "types", "interfaces", "models",
27
+ "index", "main", "setup", "init",
28
+ "organizations", "org", "orgs",
29
+ "callback", "callbacks", "appstore", "extensions",
30
+ "actions", "events", "webhooks", "hooks",
31
+ "auth", "oauth", "sso", "saml", "oidc",
32
+ "store", "stores", "plugin", "plugins",
33
+ "provider", "providers", "adapter", "adapters",
34
+ "handler", "listener", "worker", "workers",
35
+ "schema", "schemas", "migration", "migrations",
36
+ }
37
+
38
+ _CHAINED_VULN_PHRASES: list[str] = [
39
+ "via xss", "requires xss", "through xss",
40
+ "via subdomain takeover", "subdomain control", "requires subdomain",
41
+ "via mitm", "man-in-the-middle", "requires mitm",
42
+ "via another vulnerability", "requires another vulnerability",
43
+ "malicious browser extension", "ability to set cookies",
44
+ "ability to set a cookie", "cookie manipulation",
45
+ "via dns rebinding", "dns rebinding",
46
+ "local malware", "via local code execution", "requires local access",
47
+ ]
48
+
49
+ _DEPRECATION_RES: list[re.Pattern] = [
50
+ re.compile(r"@deprecated", re.IGNORECASE),
51
+ re.compile(r"\bdeprecated\b", re.IGNORECASE),
52
+ re.compile(r"todo[:\s]+(remove|migrate|replace|switch)", re.IGNORECASE),
53
+ re.compile(r"will be removed", re.IGNORECASE),
54
+ re.compile(r"no longer (supported|recommended)", re.IGNORECASE),
55
+ re.compile(r"use .{3,40} instead", re.IGNORECASE),
56
+ re.compile(r"security[:\s]+warn", re.IGNORECASE),
57
+ ]
58
+
59
+
60
+ def _extract_significant_segments(file_path: str) -> set[str]:
61
+ parts = file_path.lower().replace("\\", "/").split("/")
62
+ if parts:
63
+ last = parts[-1]
64
+ dot = last.rfind(".")
65
+ if dot > 0:
66
+ last = last[:dot]
67
+ parts[-1] = last
68
+
69
+ significant: set[str] = set()
70
+ for part in parts:
71
+ cleaned = part.strip().replace("-", "").replace("_", "")
72
+ if cleaned and cleaned not in _GENERIC_SEGMENTS and len(cleaned) > 2:
73
+ significant.add(cleaned)
74
+ return significant
75
+
76
+
77
+ def _segments_share_integration(segs_a: set[str], segs_b: set[str]) -> bool:
78
+ for a in segs_a:
79
+ for b in segs_b:
80
+ if a == b:
81
+ return True
82
+ shorter, longer = (a, b) if len(a) <= len(b) else (b, a)
83
+ if len(shorter) >= 5 and shorter in longer:
84
+ return True
85
+ return False
86
+
87
+
88
+ def cross_file_dedup(
89
+ validated: list[dict],
90
+ potential_findings: list[dict],
91
+ ) -> list[dict]:
92
+ n = len(validated)
93
+ if n <= 1:
94
+ return validated
95
+
96
+ parent = list(range(n))
97
+
98
+ def find(x: int) -> int:
99
+ while parent[x] != x:
100
+ parent[x] = parent[parent[x]]
101
+ x = parent[x]
102
+ return x
103
+
104
+ def union(a: int, b: int) -> None:
105
+ pa, pb = find(a), find(b)
106
+ if pa != pb:
107
+ parent[pa] = pb
108
+
109
+ meta: list[tuple[str, set[str]]] = []
110
+ for v in validated:
111
+ idx = v.get("original_index")
112
+ if idx is not None and 0 <= idx < len(potential_findings):
113
+ orig = potential_findings[idx]
114
+ cat = normalize_category(orig.get("category", "")).lower()
115
+ segs = _extract_significant_segments(orig.get("file_path", ""))
116
+ else:
117
+ cat, segs = "", set()
118
+ meta.append((cat, segs))
119
+
120
+ for i in range(n):
121
+ for j in range(i + 1, n):
122
+ cat_i, segs_i = meta[i]
123
+ cat_j, segs_j = meta[j]
124
+ if cat_i and cat_i == cat_j and _segments_share_integration(segs_i, segs_j):
125
+ union(i, j)
126
+
127
+ groups: dict[int, list[int]] = {}
128
+ for i in range(n):
129
+ groups.setdefault(find(i), []).append(i)
130
+
131
+ result: list[dict] = []
132
+ for indices in groups.values():
133
+ if len(indices) == 1:
134
+ result.append(validated[indices[0]])
135
+ else:
136
+ best = min(indices, key=lambda i: (
137
+ SEVERITY_ORDER.get(
138
+ (potential_findings[validated[i].get("original_index", 0)].get("severity") or "info").lower(), 4
139
+ ),
140
+ -len(potential_findings[validated[i].get("original_index", 0)].get("description") or ""),
141
+ ))
142
+ result.append(validated[best])
143
+
144
+ return result
145
+
146
+
147
+ def has_chained_prerequisite(finding: dict) -> bool:
148
+ text = " ".join([
149
+ (finding.get("description") or ""),
150
+ (finding.get("code_snippet") or ""),
151
+ (finding.get("poc") or ""),
152
+ ]).lower()
153
+
154
+ for phrase in _CHAINED_VULN_PHRASES:
155
+ if phrase in text:
156
+ return True
157
+ return False
158
+
159
+
160
+ def has_deprecation_marker(
161
+ file_path: str,
162
+ line_number: Optional[int],
163
+ fs_tools,
164
+ ) -> bool:
165
+ if not fs_tools or not file_path:
166
+ return False
167
+
168
+ if file_path.startswith(("GET ", "POST ", "PUT ", "PATCH ", "DELETE ", "http")):
169
+ return False
170
+
171
+ try:
172
+ if line_number and line_number > 0:
173
+ offset = max(0, line_number - 10)
174
+ result = fs_tools.read_file(file_path, offset=offset, limit=20)
175
+ else:
176
+ result = fs_tools.read_file(file_path, offset=0, limit=30)
177
+
178
+ content = result.get("content", "")
179
+ if not content:
180
+ return False
181
+
182
+ for pattern in _DEPRECATION_RES:
183
+ if pattern.search(content):
184
+ return True
185
+ except Exception:
186
+ pass
187
+
188
+ return False
189
+
190
+
191
+ def run_quality_gates(
192
+ validated: list[dict],
193
+ potential_findings: list[dict],
194
+ fs_tools=None,
195
+ ) -> tuple[list[dict], dict]:
196
+ stats = {
197
+ "input_count": len(validated),
198
+ "cross_file_dedup_removed": 0,
199
+ "chained_prereq_downgraded": 0,
200
+ "deprecated_downgraded": 0,
201
+ }
202
+
203
+ deduped = cross_file_dedup(validated, potential_findings)
204
+ stats["cross_file_dedup_removed"] = len(validated) - len(deduped)
205
+
206
+ for v in deduped:
207
+ idx = v.get("original_index")
208
+ if idx is None or idx < 0 or idx >= len(potential_findings):
209
+ continue
210
+ orig = potential_findings[idx]
211
+ current_sev = SEVERITY_ORDER.get(
212
+ (orig.get("severity") or "info").lower(), 4
213
+ )
214
+
215
+ if current_sev < 3 and has_chained_prerequisite(orig):
216
+ orig["severity"] = "low"
217
+ orig["_quality_note"] = "Downgraded: requires pre-existing vulnerability to exploit"
218
+ stats["chained_prereq_downgraded"] += 1
219
+ continue
220
+
221
+ if current_sev < 3 and has_deprecation_marker(
222
+ orig.get("file_path", ""),
223
+ orig.get("line_number"),
224
+ fs_tools,
225
+ ):
226
+ orig["severity"] = "low"
227
+ orig["_quality_note"] = "Downgraded: code already has deprecation markers"
228
+ stats["deprecated_downgraded"] += 1
229
+
230
+ stats["output_count"] = len(deduped)
231
+ return deduped, stats
@@ -0,0 +1,12 @@
1
+ """
2
+ Sandbox verification layer for OpenHack.
3
+
4
+ Spins up target applications in Docker containers and runs
5
+ exploit PoCs against them to confirm vulnerabilities with
6
+ real execution evidence.
7
+ """
8
+
9
+ from .orchestrator import SandboxOrchestrator
10
+ from .runner import ExploitRunner
11
+
12
+ __all__ = ["SandboxOrchestrator", "ExploitRunner"]