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.
- openhack/__init__.py +2 -0
- openhack/__main__.py +225 -0
- openhack/agents/__init__.py +30 -0
- openhack/agents/base.py +230 -0
- openhack/agents/browser_verifier.py +679 -0
- openhack/agents/browser_verifier_swarm.py +256 -0
- openhack/agents/checkpoint.py +89 -0
- openhack/agents/context_manager.py +356 -0
- openhack/agents/coordinator.py +1105 -0
- openhack/agents/endpoint_analyst.py +307 -0
- openhack/agents/feature_hunter.py +93 -0
- openhack/agents/hunter.py +481 -0
- openhack/agents/hunter_swarm.py +385 -0
- openhack/agents/llm.py +334 -0
- openhack/agents/recon.py +19 -0
- openhack/agents/sandbox_verifier.py +396 -0
- openhack/agents/sandbox_verifier_swarm.py +250 -0
- openhack/agents/session.py +286 -0
- openhack/agents/validator.py +217 -0
- openhack/agents/validator_swarm.py +106 -0
- openhack/auth.py +175 -0
- openhack/browser/__init__.py +12 -0
- openhack/browser/runner.py +385 -0
- openhack/categories.py +130 -0
- openhack/config.py +201 -0
- openhack/deterministic_recon.py +464 -0
- openhack/entry_points.py +745 -0
- openhack/framework_classifier.py +515 -0
- openhack/framework_detection.py +269 -0
- openhack/headless_scan.py +179 -0
- openhack/prompts/__init__.py +108 -0
- openhack/prompts/browser_verifier.py +171 -0
- openhack/prompts/coordinator.py +31 -0
- openhack/prompts/django/__init__.py +32 -0
- openhack/prompts/django/auth_bypass.py +76 -0
- openhack/prompts/django/csrf.py +62 -0
- openhack/prompts/django/data_exposure.py +67 -0
- openhack/prompts/django/idor.py +74 -0
- openhack/prompts/django/injection.py +67 -0
- openhack/prompts/django/misconfiguration.py +70 -0
- openhack/prompts/django/ssrf.py +64 -0
- openhack/prompts/endpoint_analyst.py +122 -0
- openhack/prompts/express/__init__.py +29 -0
- openhack/prompts/express/auth_bypass.py +71 -0
- openhack/prompts/express/data_exposure.py +77 -0
- openhack/prompts/express/idor.py +69 -0
- openhack/prompts/express/injection.py +75 -0
- openhack/prompts/express/misconfiguration.py +72 -0
- openhack/prompts/express/ssrf.py +63 -0
- openhack/prompts/feature_hunter.py +140 -0
- openhack/prompts/flask/__init__.py +29 -0
- openhack/prompts/flask/auth_bypass.py +86 -0
- openhack/prompts/flask/data_exposure.py +78 -0
- openhack/prompts/flask/idor.py +83 -0
- openhack/prompts/flask/injection.py +77 -0
- openhack/prompts/flask/misconfiguration.py +73 -0
- openhack/prompts/flask/ssrf.py +65 -0
- openhack/prompts/hunter.py +362 -0
- openhack/prompts/hunter_continuation_loop.py +12 -0
- openhack/prompts/hunter_continuation_no_findings.py +19 -0
- openhack/prompts/hunter_continuation_no_progress.py +22 -0
- openhack/prompts/hunter_tool_instructions.py +55 -0
- openhack/prompts/nextjs/__init__.py +42 -0
- openhack/prompts/nextjs/auth_bypass.py +80 -0
- openhack/prompts/nextjs/csrf.py +71 -0
- openhack/prompts/nextjs/data_exposure.py +88 -0
- openhack/prompts/nextjs/idor.py +64 -0
- openhack/prompts/nextjs/injection.py +65 -0
- openhack/prompts/nextjs/middleware_bypass.py +75 -0
- openhack/prompts/nextjs/misconfiguration.py +92 -0
- openhack/prompts/nextjs/server_actions.py +97 -0
- openhack/prompts/nextjs/ssrf.py +66 -0
- openhack/prompts/nextjs/xss.py +69 -0
- openhack/prompts/pr_analysis_system.py +80 -0
- openhack/prompts/pr_analysis_user.py +11 -0
- openhack/prompts/project_context.py +89 -0
- openhack/prompts/recon.py +199 -0
- openhack/prompts/reporter.py +88 -0
- openhack/prompts/researchers.py +434 -0
- openhack/prompts/sandbox_verifier.py +128 -0
- openhack/prompts/supabase/__init__.py +39 -0
- openhack/prompts/supabase/auth_tokens.py +131 -0
- openhack/prompts/supabase/edge_functions.py +150 -0
- openhack/prompts/supabase/graphql.py +102 -0
- openhack/prompts/supabase/postgrest.py +99 -0
- openhack/prompts/supabase/realtime.py +93 -0
- openhack/prompts/supabase/rls.py +110 -0
- openhack/prompts/supabase/rpc_functions.py +127 -0
- openhack/prompts/supabase/storage.py +110 -0
- openhack/prompts/supabase/tenant_isolation.py +118 -0
- openhack/prompts/validator.py +319 -0
- openhack/prompts/validator_continuation_incomplete.py +12 -0
- openhack/prompts/validator_tool_instructions.py +29 -0
- openhack/quality.py +231 -0
- openhack/sandbox/__init__.py +12 -0
- openhack/sandbox/orchestrator.py +517 -0
- openhack/sandbox/runner.py +177 -0
- openhack/scan_session.py +245 -0
- openhack/setup.py +452 -0
- openhack/static_validator.py +612 -0
- openhack/tools/__init__.py +1 -0
- openhack/tools/ast_tools.py +307 -0
- openhack/tools/coverage.py +1078 -0
- openhack/tools/filesystem.py +404 -0
- openhack/tools/nextjs.py +258 -0
- openhack/tools/registry.py +52 -0
- openhack/tui.py +3450 -0
- openhack/updates.py +170 -0
- openhack-0.1.0.dist-info/METADATA +189 -0
- openhack-0.1.0.dist-info/RECORD +113 -0
- openhack-0.1.0.dist-info/WHEEL +4 -0
- openhack-0.1.0.dist-info/entry_points.txt +2 -0
- 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"]
|