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,269 @@
1
+ """
2
+ Deterministic framework detection for vulnerability scanning.
3
+
4
+ Detects the tech stack of a target repository by reading indicator files
5
+ (package.json, manage.py, requirements.txt, etc.) and returns a list of
6
+ detected frameworks with their root directories. Supports monorepos where
7
+ multiple frameworks live in different subdirectories.
8
+
9
+ No LLM calls -- pure file existence + content checks.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import re
16
+ from pathlib import Path
17
+ from typing import TYPE_CHECKING
18
+
19
+ if TYPE_CHECKING:
20
+ from openhack.tools.filesystem import FileSystemTools
21
+
22
+
23
+ SKIP_DIRS = {
24
+ "node_modules", ".git", "__pycache__", "venv", ".venv",
25
+ "dist", "build", ".next", "coverage", ".nyc_output",
26
+ "vendor", ".tox", "egg-info", ".eggs",
27
+ }
28
+
29
+
30
+ def _read_raw(fs: FileSystemTools, path: str) -> str | None:
31
+ """Read a file via FileSystemTools and return raw content (no line numbers)."""
32
+ result = fs.read_file(path)
33
+ if "error" in result:
34
+ return None
35
+ lines = result["content"].split("\n")
36
+ return "\n".join(
37
+ line.split("\t", 1)[1] if "\t" in line else line for line in lines
38
+ )
39
+
40
+
41
+ def _parse_json(fs: FileSystemTools, path: str) -> dict | None:
42
+ raw = _read_raw(fs, path)
43
+ if raw is None:
44
+ return None
45
+ try:
46
+ return json.loads(raw)
47
+ except (json.JSONDecodeError, ValueError):
48
+ return None
49
+
50
+
51
+ def _dir_of(path: str) -> str:
52
+ """Return the parent directory of a file path, or '.' for root-level files."""
53
+ parent = str(Path(path).parent)
54
+ return "." if parent == "." or parent == "" else parent
55
+
56
+
57
+ def _glob_no_skip(fs: FileSystemTools, pattern: str) -> list[str]:
58
+ """Glob for files, filtering out matches inside SKIP_DIRS."""
59
+ result = fs.glob(pattern)
60
+ if "error" in result:
61
+ return []
62
+ matches = []
63
+ for m in result.get("matches", []):
64
+ parts = Path(m).parts
65
+ if not SKIP_DIRS.intersection(parts):
66
+ matches.append(m)
67
+ return matches
68
+
69
+
70
+ def _detect_nextjs(fs: FileSystemTools) -> list[dict]:
71
+ """Detect Next.js projects by next.config.* or 'next' in package.json deps."""
72
+ found: list[dict] = []
73
+ seen_roots: set[str] = set()
74
+
75
+ for config_name in ("next.config.js", "next.config.ts", "next.config.mjs"):
76
+ for match in _glob_no_skip(fs, f"**/{config_name}"):
77
+ root = _dir_of(match)
78
+ if root not in seen_roots:
79
+ seen_roots.add(root)
80
+ found.append({"framework": "nextjs", "root": root})
81
+
82
+ for pkg_path in _glob_no_skip(fs, "**/package.json"):
83
+ root = _dir_of(pkg_path)
84
+ if root in seen_roots:
85
+ continue
86
+ pkg = _parse_json(fs, pkg_path)
87
+ if pkg is None:
88
+ continue
89
+ deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
90
+ if "next" in deps:
91
+ seen_roots.add(root)
92
+ found.append({"framework": "nextjs", "root": root})
93
+
94
+ return found
95
+
96
+
97
+ def _detect_express(fs: FileSystemTools) -> list[dict]:
98
+ """Detect Express.js projects by 'express' in package.json deps."""
99
+ found: list[dict] = []
100
+ seen_roots: set[str] = set()
101
+
102
+ for pkg_path in _glob_no_skip(fs, "**/package.json"):
103
+ root = _dir_of(pkg_path)
104
+ if root in seen_roots:
105
+ continue
106
+ pkg = _parse_json(fs, pkg_path)
107
+ if pkg is None:
108
+ continue
109
+ deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
110
+ if "express" in deps:
111
+ seen_roots.add(root)
112
+ found.append({"framework": "express", "root": root})
113
+
114
+ return found
115
+
116
+
117
+ def _detect_django(fs: FileSystemTools) -> list[dict]:
118
+ """Detect Django by manage.py + django in Python dependency files."""
119
+ found: list[dict] = []
120
+ seen_roots: set[str] = set()
121
+
122
+ for manage_path in _glob_no_skip(fs, "**/manage.py"):
123
+ root = _dir_of(manage_path)
124
+ if root in seen_roots:
125
+ continue
126
+
127
+ content = _read_raw(fs, manage_path)
128
+ if content and "django" not in content.lower():
129
+ continue
130
+
131
+ has_django_dep = False
132
+ for dep_file in ("requirements.txt", "Pipfile", "pyproject.toml", "setup.cfg"):
133
+ dep_path = f"{root}/{dep_file}" if root != "." else dep_file
134
+ dep_content = _read_raw(fs, dep_path)
135
+ if dep_content and re.search(r"django", dep_content, re.IGNORECASE):
136
+ has_django_dep = True
137
+ break
138
+
139
+ if not has_django_dep:
140
+ settings_matches = _glob_no_skip(fs, f"{'**/' if root == '.' else root + '/'}**/settings.py")
141
+ if settings_matches:
142
+ has_django_dep = True
143
+
144
+ if has_django_dep:
145
+ seen_roots.add(root)
146
+ found.append({"framework": "django", "root": root})
147
+
148
+ return found
149
+
150
+
151
+ def _detect_flask(fs: FileSystemTools) -> list[dict]:
152
+ """Detect Flask by dependency files or app = Flask( pattern."""
153
+ found: list[dict] = []
154
+ seen_roots: set[str] = set()
155
+
156
+ for dep_file in ("requirements.txt", "Pipfile", "pyproject.toml", "setup.cfg"):
157
+ for dep_path in _glob_no_skip(fs, f"**/{dep_file}"):
158
+ root = _dir_of(dep_path)
159
+ if root in seen_roots:
160
+ continue
161
+ content = _read_raw(fs, dep_path)
162
+ if content and re.search(r"(?:^|\s|['\"])flask(?:\s|['\"><=,\[]|$)", content, re.IGNORECASE | re.MULTILINE):
163
+ seen_roots.add(root)
164
+ found.append({"framework": "flask", "root": root})
165
+
166
+ return found
167
+
168
+
169
+ def _detect_rails(fs: FileSystemTools) -> list[dict]:
170
+ """Detect Rails by Gemfile with 'rails' or config/routes.rb."""
171
+ found: list[dict] = []
172
+ seen_roots: set[str] = set()
173
+
174
+ for gemfile_path in _glob_no_skip(fs, "**/Gemfile"):
175
+ root = _dir_of(gemfile_path)
176
+ if root in seen_roots:
177
+ continue
178
+ content = _read_raw(fs, gemfile_path)
179
+ if content and re.search(r"['\"]rails['\"]", content):
180
+ seen_roots.add(root)
181
+ found.append({"framework": "rails", "root": root})
182
+
183
+ for routes_path in _glob_no_skip(fs, "**/config/routes.rb"):
184
+ root = str(Path(_dir_of(routes_path)).parent)
185
+ if root == "":
186
+ root = "."
187
+ if root not in seen_roots:
188
+ seen_roots.add(root)
189
+ found.append({"framework": "rails", "root": root})
190
+
191
+ return found
192
+
193
+
194
+ def _detect_spring(fs: FileSystemTools) -> list[dict]:
195
+ """Detect Spring Boot by pom.xml or build.gradle with spring-boot."""
196
+ found: list[dict] = []
197
+ seen_roots: set[str] = set()
198
+
199
+ for pom_path in _glob_no_skip(fs, "**/pom.xml"):
200
+ root = _dir_of(pom_path)
201
+ if root in seen_roots:
202
+ continue
203
+ content = _read_raw(fs, pom_path)
204
+ if content and "spring-boot" in content:
205
+ seen_roots.add(root)
206
+ found.append({"framework": "spring", "root": root})
207
+
208
+ for gradle_path in _glob_no_skip(fs, "**/build.gradle"):
209
+ root = _dir_of(gradle_path)
210
+ if root in seen_roots:
211
+ continue
212
+ content = _read_raw(fs, gradle_path)
213
+ if content and "spring" in content.lower():
214
+ seen_roots.add(root)
215
+ found.append({"framework": "spring", "root": root})
216
+
217
+ return found
218
+
219
+
220
+ def _detect_laravel(fs: FileSystemTools) -> list[dict]:
221
+ """Detect Laravel by artisan + composer.json with laravel."""
222
+ found: list[dict] = []
223
+ seen_roots: set[str] = set()
224
+
225
+ for artisan_path in _glob_no_skip(fs, "**/artisan"):
226
+ root = _dir_of(artisan_path)
227
+ if root in seen_roots:
228
+ continue
229
+ composer_path = f"{root}/composer.json" if root != "." else "composer.json"
230
+ composer = _parse_json(fs, composer_path)
231
+ if composer is None:
232
+ continue
233
+ require = {**composer.get("require", {}), **composer.get("require-dev", {})}
234
+ if any("laravel" in k for k in require):
235
+ seen_roots.add(root)
236
+ found.append({"framework": "laravel", "root": root})
237
+
238
+ return found
239
+
240
+
241
+ def detect_frameworks(fs: FileSystemTools) -> list[dict]:
242
+ """Detect all frameworks in the target repository.
243
+
244
+ Returns a list of dicts, each with:
245
+ - framework: str (e.g. "nextjs", "django", "express", "flask")
246
+ - root: str (directory path relative to repo root, or "." for root)
247
+
248
+ Supports monorepos: a single repo can yield multiple entries.
249
+ """
250
+ results: list[dict] = []
251
+
252
+ results.extend(_detect_nextjs(fs))
253
+ results.extend(_detect_django(fs))
254
+ results.extend(_detect_express(fs))
255
+ results.extend(_detect_flask(fs))
256
+ results.extend(_detect_rails(fs))
257
+ results.extend(_detect_spring(fs))
258
+ results.extend(_detect_laravel(fs))
259
+
260
+ # Deduplicate: if both nextjs and express are detected at the same root
261
+ # (common for Next.js apps that also list express as a dep), keep nextjs
262
+ # since it's more specific.
263
+ nextjs_roots = {r["root"] for r in results if r["framework"] == "nextjs"}
264
+ results = [
265
+ r for r in results
266
+ if not (r["framework"] == "express" and r["root"] in nextjs_roots)
267
+ ]
268
+
269
+ return results
@@ -0,0 +1,179 @@
1
+ """
2
+ Headless scan — runs the full pipeline without the TUI.
3
+
4
+ Uses the same coordinator pipeline as the TUI (recon → hunters →
5
+ feature deep dive → validation) with checkpoint support for resume.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import os
11
+ import time
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ from .agents.coordinator import CoordinatorAgent
17
+ from .agents.checkpoint import CheckpointManager
18
+ from .agents.llm import LLMClient
19
+ from .agents.session import Session, Finding, TraceEntry
20
+ from .tools.registry import ToolRegistry
21
+ from .config import reload_settings, settings
22
+ from .prompts.project_context import build_project_context
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ SCANS_DIR = Path.home() / ".openhack" / "scans"
27
+
28
+
29
+ def _on_trace(entry: TraceEntry):
30
+ agent = entry.agent or "?"
31
+ event = entry.event_type or ""
32
+ if event == "tool_call":
33
+ pass
34
+ elif "finding" in event.lower():
35
+ print(f" [{agent}] FINDING: {entry.content}")
36
+ elif event in ("status", "step_start", "step_complete", "resume"):
37
+ snippet = str(entry.content or "")[:120]
38
+ print(f" [{agent}] {event}: {snippet}")
39
+
40
+
41
+ def _write_report(
42
+ session: Session,
43
+ target_dir: str,
44
+ status: str,
45
+ start_time: float,
46
+ ) -> Path:
47
+ SCANS_DIR.mkdir(parents=True, exist_ok=True)
48
+ report_path = SCANS_DIR / f"{session.id}.json"
49
+ elapsed = time.time() - start_time
50
+
51
+ report = {
52
+ "version": 2,
53
+ "scan_id": session.id,
54
+ "target_dir": target_dir,
55
+ "provider": settings.llm_provider,
56
+ "status": status,
57
+ "pid": os.getpid(),
58
+ "started_at": datetime.fromtimestamp(start_time).isoformat(),
59
+ "duration_seconds": round(elapsed, 2),
60
+ "cost": session.get_cost_breakdown(),
61
+ "findings": [f.to_dict() for f in session.findings],
62
+ }
63
+
64
+ tmp_path = report_path.with_suffix(".json.tmp")
65
+ with open(tmp_path, "w") as fp:
66
+ json.dump(report, fp, indent=2, default=str, ensure_ascii=False)
67
+ tmp_path.rename(report_path)
68
+
69
+ return report_path
70
+
71
+
72
+ async def run_headless_scan(
73
+ target_dir: str,
74
+ resume_from_checkpoint: Optional[str] = None,
75
+ ):
76
+ """Run a headless scan using the same coordinator pipeline as the TUI.
77
+
78
+ If resume_from_checkpoint is a checkpoint step name (e.g. "recon",
79
+ "hunter", "feature_hunt"), the coordinator skips completed steps.
80
+ If it's a session ID, we look up the latest checkpoint for that session.
81
+ """
82
+ reload_settings()
83
+ provider = settings.llm_provider
84
+ start_time = time.time()
85
+
86
+ print(f"\n{'='*60}")
87
+ if resume_from_checkpoint:
88
+ print(f" RESUMING SCAN — {target_dir}")
89
+ else:
90
+ print(f" SCANNING — {target_dir}")
91
+ print(f" Provider: {provider}")
92
+ print(f"{'='*60}\n")
93
+
94
+ # Resolve resume: if given a session ID, find its latest checkpoint
95
+ resume_step = None
96
+ reuse_id = None
97
+ if resume_from_checkpoint:
98
+ mgr = CheckpointManager(resume_from_checkpoint)
99
+ latest = mgr.get_latest_step()
100
+ if latest:
101
+ reuse_id = resume_from_checkpoint
102
+ resume_step = latest
103
+ print(f" Resuming from checkpoint: {latest}")
104
+ elif resume_from_checkpoint in ("recon", "hunter", "feature_hunt"):
105
+ resume_step = resume_from_checkpoint
106
+ else:
107
+ reuse_id = resume_from_checkpoint
108
+ print(f" No checkpoint found — starting fresh on same session")
109
+
110
+ project_context = build_project_context(target_dir)
111
+ session = Session(
112
+ target_dir=target_dir,
113
+ on_trace=_on_trace,
114
+ project_context=project_context,
115
+ scan_id=reuse_id,
116
+ )
117
+ tools = ToolRegistry(target_dir=Path(target_dir))
118
+
119
+ if project_context and project_context.get("openhack_md"):
120
+ print(f" Loaded .openhack.md project context ({len(project_context['openhack_md'])} chars)")
121
+
122
+ # Write initial report so --list-sessions shows a running scan
123
+ _write_report(session, target_dir, "running", start_time)
124
+
125
+ llm = LLMClient(
126
+ provider=provider,
127
+ temperature=0.0,
128
+ max_tokens=8192,
129
+ prompt_cache_key=session.id,
130
+ )
131
+ coordinator = CoordinatorAgent(
132
+ llm, tools, session,
133
+ resume_from=resume_step,
134
+ )
135
+
136
+ try:
137
+ result = await coordinator.run_full_scan()
138
+
139
+ # Print results
140
+ findings = session.findings
141
+ print(f"\n{'='*60}")
142
+ print(f" RESULTS")
143
+ print(f"{'='*60}")
144
+
145
+ if not findings:
146
+ print(" No vulnerabilities confirmed.")
147
+ else:
148
+ print(f" {len(findings)} vulnerability(ies) confirmed:\n")
149
+ for i, f in enumerate(findings, 1):
150
+ sev = f.severity.upper()
151
+ print(f" {i}. [{sev}] {f.category} — {f.file_path}")
152
+ desc = f.description or ""
153
+ if desc:
154
+ print(f" {desc}")
155
+ print()
156
+
157
+ cost = session.get_cost_breakdown()
158
+ elapsed = time.time() - start_time
159
+ m, s = divmod(int(elapsed), 60)
160
+ print(f" Cost: ${session.total_cost:.4f}")
161
+ print(f" Duration: {m}m {s:02d}s")
162
+
163
+ report_path = _write_report(session, target_dir, "completed", start_time)
164
+ print(f" Report: {report_path}")
165
+ print(f" Session: {session.id}\n")
166
+
167
+ except KeyboardInterrupt:
168
+ print("\n Scan interrupted.")
169
+ _write_report(session, target_dir, "cancelled", start_time)
170
+ mgr = CheckpointManager(session.id)
171
+ if mgr.get_latest_step():
172
+ print(f" Resume from checkpoint: openhack --resume {session.id}")
173
+ else:
174
+ print(f" Retry: openhack --resume {session.id}")
175
+ except Exception as exc:
176
+ logger.debug(f"Scan failed: {exc}", exc_info=True)
177
+ _write_report(session, target_dir, "failed", start_time)
178
+ print(f"\n Scan failed: {exc}")
179
+ print(f" Retry: openhack --resume {session.id}")
@@ -0,0 +1,108 @@
1
+ """
2
+ Prompt templates for vulnerability scanning agents.
3
+
4
+ All prompts are organized as one prompt per file for easy maintenance.
5
+ Framework-specific prompts live under prompts/<framework>/<attack_type>.py.
6
+ """
7
+
8
+ from .project_context import format_project_context, load_openhack_md, build_project_context
9
+ from .coordinator import COORDINATOR_PROMPT
10
+ from .recon import RECON_PROMPT
11
+ from .hunter import HUNTER_PROMPT
12
+ from .validator import VALIDATOR_PROMPT
13
+ from .reporter import REPORTER_PROMPT
14
+ from .pr_analysis_system import PR_ANALYSIS_SYSTEM_PROMPT
15
+ from .pr_analysis_user import PR_ANALYSIS_USER_TEMPLATE
16
+ from .hunter_tool_instructions import HUNTER_TOOL_INSTRUCTIONS
17
+ from .hunter_continuation_no_findings import HUNTER_CONTINUATION_NO_FINDINGS
18
+ from .hunter_continuation_loop import HUNTER_CONTINUATION_LOOP
19
+ from .hunter_continuation_no_progress import HUNTER_CONTINUATION_NO_PROGRESS
20
+ from .validator_tool_instructions import VALIDATOR_TOOL_INSTRUCTIONS
21
+ from .validator_continuation_incomplete import VALIDATOR_CONTINUATION_INCOMPLETE
22
+ from .sandbox_verifier import SANDBOX_VERIFIER_PROMPT, SANDBOX_VERIFIER_TOOL_INSTRUCTIONS
23
+ from .browser_verifier import BROWSER_VERIFIER_PROMPT, BROWSER_VERIFIER_TOOL_INSTRUCTIONS
24
+ from .feature_hunter import FEATURE_HUNTER_PROMPT, FEATURE_EXTRACTION_PROMPT
25
+ from .nextjs import (
26
+ NEXTJS_PROMPTS,
27
+ NEXTJS_IDOR_PROMPT,
28
+ NEXTJS_XSS_PROMPT,
29
+ NEXTJS_CSRF_PROMPT,
30
+ NEXTJS_SSRF_PROMPT,
31
+ NEXTJS_INJECTION_PROMPT,
32
+ NEXTJS_AUTH_BYPASS_PROMPT,
33
+ NEXTJS_DATA_EXPOSURE_PROMPT,
34
+ NEXTJS_MIDDLEWARE_BYPASS_PROMPT,
35
+ NEXTJS_SERVER_ACTIONS_PROMPT,
36
+ NEXTJS_MISCONFIGURATION_PROMPT,
37
+ )
38
+ from .supabase import (
39
+ SUPABASE_PROMPTS,
40
+ SUPABASE_RLS_PROMPT,
41
+ SUPABASE_POSTGREST_PROMPT,
42
+ SUPABASE_RPC_PROMPT,
43
+ SUPABASE_STORAGE_PROMPT,
44
+ SUPABASE_REALTIME_PROMPT,
45
+ SUPABASE_GRAPHQL_PROMPT,
46
+ SUPABASE_AUTH_PROMPT,
47
+ SUPABASE_EDGE_FUNCTIONS_PROMPT,
48
+ SUPABASE_TENANT_ISOLATION_PROMPT,
49
+ )
50
+ from .django import DJANGO_PROMPTS
51
+ from .express import EXPRESS_PROMPTS
52
+ from .flask import FLASK_PROMPTS
53
+
54
+ # Unified registry: framework name -> prompt dict.
55
+ # Used by HunterAgent to look up the correct prompts for its assigned framework.
56
+ ALL_FRAMEWORK_PROMPTS: dict[str, dict[str, str]] = {
57
+ "nextjs": NEXTJS_PROMPTS,
58
+ "django": DJANGO_PROMPTS,
59
+ "express": EXPRESS_PROMPTS,
60
+ "flask": FLASK_PROMPTS,
61
+ "supabase": SUPABASE_PROMPTS,
62
+ }
63
+
64
+ __all__ = [
65
+ "format_project_context",
66
+ "COORDINATOR_PROMPT",
67
+ "RECON_PROMPT",
68
+ "HUNTER_PROMPT",
69
+ "VALIDATOR_PROMPT",
70
+ "REPORTER_PROMPT",
71
+ "PR_ANALYSIS_SYSTEM_PROMPT",
72
+ "PR_ANALYSIS_USER_TEMPLATE",
73
+ "HUNTER_TOOL_INSTRUCTIONS",
74
+ "HUNTER_CONTINUATION_NO_FINDINGS",
75
+ "HUNTER_CONTINUATION_LOOP",
76
+ "HUNTER_CONTINUATION_NO_PROGRESS",
77
+ "VALIDATOR_TOOL_INSTRUCTIONS",
78
+ "VALIDATOR_CONTINUATION_INCOMPLETE",
79
+ "SANDBOX_VERIFIER_PROMPT",
80
+ "SANDBOX_VERIFIER_TOOL_INSTRUCTIONS",
81
+ "BROWSER_VERIFIER_PROMPT",
82
+ "BROWSER_VERIFIER_TOOL_INSTRUCTIONS",
83
+ "ALL_FRAMEWORK_PROMPTS",
84
+ "NEXTJS_PROMPTS",
85
+ "NEXTJS_IDOR_PROMPT",
86
+ "NEXTJS_XSS_PROMPT",
87
+ "NEXTJS_CSRF_PROMPT",
88
+ "NEXTJS_SSRF_PROMPT",
89
+ "NEXTJS_INJECTION_PROMPT",
90
+ "NEXTJS_AUTH_BYPASS_PROMPT",
91
+ "NEXTJS_DATA_EXPOSURE_PROMPT",
92
+ "NEXTJS_MIDDLEWARE_BYPASS_PROMPT",
93
+ "NEXTJS_SERVER_ACTIONS_PROMPT",
94
+ "NEXTJS_MISCONFIGURATION_PROMPT",
95
+ "SUPABASE_PROMPTS",
96
+ "SUPABASE_RLS_PROMPT",
97
+ "SUPABASE_POSTGREST_PROMPT",
98
+ "SUPABASE_RPC_PROMPT",
99
+ "SUPABASE_STORAGE_PROMPT",
100
+ "SUPABASE_REALTIME_PROMPT",
101
+ "SUPABASE_GRAPHQL_PROMPT",
102
+ "SUPABASE_AUTH_PROMPT",
103
+ "SUPABASE_EDGE_FUNCTIONS_PROMPT",
104
+ "SUPABASE_TENANT_ISOLATION_PROMPT",
105
+ "DJANGO_PROMPTS",
106
+ "EXPRESS_PROMPTS",
107
+ "FLASK_PROMPTS",
108
+ ]