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
openhack/updates.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Startup update check + announcements fetcher.
|
|
3
|
+
|
|
4
|
+
Calls GET /updates on the inference worker. Never blocks the TUI or
|
|
5
|
+
raises to the user on failure — network errors are silently swallowed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
import httpx
|
|
18
|
+
|
|
19
|
+
from openhack import __version__
|
|
20
|
+
from openhack.config import CONFIG_DIR
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
_DISMISSED_FILE = CONFIG_DIR / "dismissed_announcements.json"
|
|
25
|
+
_LAST_CHECK_FILE = CONFIG_DIR / ".last_update_check"
|
|
26
|
+
_RECHECK_INTERVAL = 3600 # 1 hour
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class LatestRelease:
|
|
31
|
+
version: str
|
|
32
|
+
published_at: str = ""
|
|
33
|
+
download_url: str = ""
|
|
34
|
+
release_notes: str = ""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class Announcement:
|
|
39
|
+
id: str
|
|
40
|
+
level: str # "info" | "warning" | "critical"
|
|
41
|
+
title: str
|
|
42
|
+
body: str = ""
|
|
43
|
+
placement: list[str] = field(default_factory=list)
|
|
44
|
+
published_at: str = ""
|
|
45
|
+
expires_at: Optional[str] = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class UpdateInfo:
|
|
50
|
+
latest: Optional[LatestRelease] = None
|
|
51
|
+
announcements: list[Announcement] = field(default_factory=list)
|
|
52
|
+
has_update: bool = False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _get_updates_url() -> str:
|
|
56
|
+
if os.environ.get("OPENHACK_DEV", "0") == "1":
|
|
57
|
+
return "http://localhost:8787/updates"
|
|
58
|
+
return "https://api.openhack.com/updates"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _semver_gt(a: str, b: str) -> bool:
|
|
62
|
+
"""Return True if version `a` is strictly greater than `b` (semver major.minor.patch)."""
|
|
63
|
+
def _parse(v: str) -> tuple[int, ...]:
|
|
64
|
+
v = v.lstrip("v")
|
|
65
|
+
parts = v.split("-")[0].split("+")[0] # strip pre-release/build
|
|
66
|
+
return tuple(int(x) for x in parts.split(".") if x.isdigit())
|
|
67
|
+
try:
|
|
68
|
+
return _parse(a) > _parse(b)
|
|
69
|
+
except (ValueError, TypeError):
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _is_expired(expires_at: Optional[str]) -> bool:
|
|
74
|
+
if not expires_at:
|
|
75
|
+
return False
|
|
76
|
+
try:
|
|
77
|
+
exp = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
|
|
78
|
+
return datetime.now(timezone.utc) > exp
|
|
79
|
+
except (ValueError, TypeError):
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _load_dismissed() -> set[str]:
|
|
84
|
+
try:
|
|
85
|
+
data = json.loads(_DISMISSED_FILE.read_text())
|
|
86
|
+
return set(data) if isinstance(data, list) else set()
|
|
87
|
+
except Exception:
|
|
88
|
+
return set()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def save_dismissed(ann_id: str) -> None:
|
|
92
|
+
"""Persist an announcement ID as dismissed so it won't re-appear."""
|
|
93
|
+
dismissed = _load_dismissed()
|
|
94
|
+
dismissed.add(ann_id)
|
|
95
|
+
try:
|
|
96
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
_DISMISSED_FILE.write_text(json.dumps(sorted(dismissed)))
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _should_check() -> bool:
|
|
103
|
+
"""Don't re-check if we already checked within this hour."""
|
|
104
|
+
try:
|
|
105
|
+
ts = float(_LAST_CHECK_FILE.read_text().strip())
|
|
106
|
+
return (time.time() - ts) > _RECHECK_INTERVAL
|
|
107
|
+
except Exception:
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _mark_checked() -> None:
|
|
112
|
+
try:
|
|
113
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
_LAST_CHECK_FILE.write_text(str(time.time()))
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def fetch_updates(force: bool = False) -> Optional[UpdateInfo]:
|
|
120
|
+
"""Fetch update info from /updates. Returns None on any failure."""
|
|
121
|
+
if not force and not _should_check():
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
url = _get_updates_url()
|
|
125
|
+
try:
|
|
126
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
127
|
+
resp = await client.get(url, params={"current": __version__})
|
|
128
|
+
if resp.status_code != 200:
|
|
129
|
+
return None
|
|
130
|
+
data = resp.json()
|
|
131
|
+
except Exception:
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
_mark_checked()
|
|
135
|
+
|
|
136
|
+
info = UpdateInfo()
|
|
137
|
+
|
|
138
|
+
# Parse latest release
|
|
139
|
+
latest_raw = data.get("latest")
|
|
140
|
+
if latest_raw and isinstance(latest_raw, dict):
|
|
141
|
+
info.latest = LatestRelease(
|
|
142
|
+
version=latest_raw.get("version", ""),
|
|
143
|
+
published_at=latest_raw.get("publishedAt", ""),
|
|
144
|
+
download_url=latest_raw.get("downloadUrl", ""),
|
|
145
|
+
release_notes=latest_raw.get("releaseNotes", ""),
|
|
146
|
+
)
|
|
147
|
+
if info.latest.version and _semver_gt(info.latest.version, __version__):
|
|
148
|
+
info.has_update = True
|
|
149
|
+
|
|
150
|
+
# Parse announcements
|
|
151
|
+
dismissed = _load_dismissed()
|
|
152
|
+
for ann_raw in data.get("announcements") or []:
|
|
153
|
+
if not isinstance(ann_raw, dict):
|
|
154
|
+
continue
|
|
155
|
+
ann_id = ann_raw.get("id", "")
|
|
156
|
+
if ann_id in dismissed:
|
|
157
|
+
continue
|
|
158
|
+
if _is_expired(ann_raw.get("expiresAt")):
|
|
159
|
+
continue
|
|
160
|
+
info.announcements.append(Announcement(
|
|
161
|
+
id=ann_id,
|
|
162
|
+
level=ann_raw.get("level", "info"),
|
|
163
|
+
title=ann_raw.get("title", ""),
|
|
164
|
+
body=ann_raw.get("body", ""),
|
|
165
|
+
placement=ann_raw.get("placement") or [],
|
|
166
|
+
published_at=ann_raw.get("publishedAt", ""),
|
|
167
|
+
expires_at=ann_raw.get("expiresAt"),
|
|
168
|
+
))
|
|
169
|
+
|
|
170
|
+
return info
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openhack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-powered security scanner for your codebase. Find SQL injection, XSS, IDOR, auth bypass, and more — straight from your terminal.
|
|
5
|
+
Project-URL: Homepage, https://openhack.com
|
|
6
|
+
Project-URL: Documentation, https://github.com/openhackai/openhack
|
|
7
|
+
Project-URL: Repository, https://github.com/openhackai/openhack
|
|
8
|
+
Project-URL: Issues, https://github.com/openhackai/openhack/issues
|
|
9
|
+
Author: OpenHack
|
|
10
|
+
License-Expression: AGPL-3.0-only
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai-security,appsec,code-review,llm,sast,security,static-analysis,vulnerability-scanner
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Information Technology
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Topic :: Software Development :: Bug Tracking
|
|
24
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
27
|
+
Requires-Dist: httpx>=0.25.0
|
|
28
|
+
Requires-Dist: openai>=1.0.0
|
|
29
|
+
Requires-Dist: prompt-toolkit>=3.0.0
|
|
30
|
+
Requires-Dist: pydantic-settings>=2.6.0
|
|
31
|
+
Requires-Dist: pydantic>=2.10.0
|
|
32
|
+
Requires-Dist: pygments>=2.19.0
|
|
33
|
+
Requires-Dist: rich>=13.0.0
|
|
34
|
+
Requires-Dist: tree-sitter-javascript>=0.25.0
|
|
35
|
+
Requires-Dist: tree-sitter-python>=0.25.0
|
|
36
|
+
Requires-Dist: tree-sitter-typescript>=0.23.2
|
|
37
|
+
Requires-Dist: tree-sitter>=0.25.2
|
|
38
|
+
Provides-Extra: browser
|
|
39
|
+
Requires-Dist: playwright>=1.40.0; extra == 'browser'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# ⏚ [OpenHack](https://openhack.com)
|
|
43
|
+
|
|
44
|
+
**Open Source Agentic Security Scanner for your codebase.**
|
|
45
|
+
|
|
46
|
+
Like Claude Code Security / Codex Security but open source. OpenHack does recon -> hunting -> validation -> verification all in one pipeline to find high quality verified vulnerabilities. OpenHack exclusively uses open source models and specializes in web app vulnerabilities.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pipx install openhack
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or with pip:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install openhack
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
openhack
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
On first run you'll go through a one-time setup:
|
|
67
|
+
|
|
68
|
+
1. Pick **Login with OpenHack account** (recommended) — opens a browser, you log in, get **$20 in free credits**, and the CLI gets a token automatically.
|
|
69
|
+
2. Type `/scan .` to scan the current directory, or `/scan path/to/repo` for somewhere else.
|
|
70
|
+
3. While scanning, the **Trace tab** shows live agent activity (recon → hunters → validators). When the scan finishes, the **Findings tab** shows everything that was found.
|
|
71
|
+
|
|
72
|
+
## What it does
|
|
73
|
+
|
|
74
|
+
OpenHack runs a multi-agent pipeline against your codebase:
|
|
75
|
+
|
|
76
|
+
- **Recon** — reads the code, builds a project model
|
|
77
|
+
- **Hunters** — multiple specialized agents look for different vulnerability classes (input validation, access control, data handling, …)
|
|
78
|
+
- **Feature hunters** — deeper passes on specific risk categories (XSS rendering, raw SQL, command exec, etc.)
|
|
79
|
+
- **Validators** — re-read the suspect code to confirm or reject each candidate finding
|
|
80
|
+
- **Sandbox verification** (`/verify sandbox`) *(Beta — requires Docker)* — spins up your app in a Docker container and attempts to exploit each finding with live HTTP requests. Findings that are successfully exploited get a ✓ mark.
|
|
81
|
+
- **Browser verification** (`/verify browser`) *(Beta — requires Docker when combined with sandbox)* — launches a headless browser against the sandboxed app to verify client-side vulnerabilities (XSS, CSRF, DOM-based issues) with real browser execution.
|
|
82
|
+
|
|
83
|
+
> **Docker prerequisite.** Sandbox verification requires Docker Desktop (or any working Docker daemon) installed and running on the machine where the scan runs. Browser verification inherits this when used with sandbox. If Docker isn't running, `/verify sandbox` will fail with a clear error before the scan starts.
|
|
84
|
+
|
|
85
|
+
For every confirmed finding you get: severity, CVSS score, file location, full description, the vulnerable code snippet, and a recommended fix — all rendered with syntax highlighting in the TUI.
|
|
86
|
+
|
|
87
|
+
## Slash commands
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
| Command | Description |
|
|
91
|
+
| -------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
92
|
+
| `/scan <path>` | Full scan on a directory (defaults to current dir) |
|
|
93
|
+
| `/pause` · `/resume` | Pause and resume a running scan (Ctrl+C also pauses) |
|
|
94
|
+
| `/cancel` | Permanently cancel a running scan |
|
|
95
|
+
| `/sessions` | Browse and re-load past scans (also supports re-running an aborted scan with `r`) |
|
|
96
|
+
| `/findings` | Re-display findings from last scan |
|
|
97
|
+
| `/copy` | Copy the selected finding (description + vulnerable code + fix) for Codex / Claude Code / OpenCode |
|
|
98
|
+
| `/verify sandbox` *(Beta)* | Spin up a Docker sandbox and exploit-test each finding with live requests |
|
|
99
|
+
| `/verify browser` *(Beta)* | Launch a headless browser to verify client-side vulns (XSS, CSRF, etc.) |
|
|
100
|
+
| `/login` | Re-login to your OpenHack account |
|
|
101
|
+
| `/setup` | Run the setup wizard again |
|
|
102
|
+
| `/config` | Show current config; `/config <key> <value>` to set |
|
|
103
|
+
| `/sidebar` | Show/hide the Findings list sidebar (`Ctrl+B`) |
|
|
104
|
+
| `/cost` | Cost breakdown for the last scan |
|
|
105
|
+
| `/clear` | Clear scan state and return to landing |
|
|
106
|
+
| `/discord` | Open the OpenHack Discord |
|
|
107
|
+
| `/mouse` | Toggle mouse capture (off = native text selection) |
|
|
108
|
+
| `/help` | List commands |
|
|
109
|
+
| `/quit` | Exit |
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## Keyboard shortcuts (Findings tab)
|
|
113
|
+
|
|
114
|
+
- `↑` / `↓` — switch finding
|
|
115
|
+
- `[` · `]` — alternate prev / next
|
|
116
|
+
- Mouse wheel or `PgUp` / `PgDn` — scroll the details pane
|
|
117
|
+
- `y` — yank (copy) selected finding for an AI agent
|
|
118
|
+
- `<` · `>` — resize the sidebar
|
|
119
|
+
- `Ctrl+B` — toggle sidebar
|
|
120
|
+
|
|
121
|
+
## Keyboard shortcuts (Trace tab)
|
|
122
|
+
|
|
123
|
+
- `↑` / `↓` — switch agent in the sidebar tree
|
|
124
|
+
- `[` · `]` — alternate prev / next agent
|
|
125
|
+
- Mouse wheel or `PgUp` / `PgDn` — scroll the trace
|
|
126
|
+
- `Home` — jump to "All" (full trace)
|
|
127
|
+
- `End` — resume auto-follow-to-bottom
|
|
128
|
+
|
|
129
|
+
## Selecting text
|
|
130
|
+
|
|
131
|
+
The TUI captures mouse events by default (for scrolling and clicking). To select and copy text natively:
|
|
132
|
+
|
|
133
|
+
- **macOS**: Hold `Option` (⌥) and drag to select, then `Cmd+C` to copy.
|
|
134
|
+
- **Linux / Windows**: Hold `Shift` and drag to select.
|
|
135
|
+
- **Or**: Run `/mouse` to disable mouse capture entirely — the terminal's native selection works normally until you toggle it back on.
|
|
136
|
+
|
|
137
|
+
## CLI commands (headless)
|
|
138
|
+
|
|
139
|
+
For CI, scripts, or one-off scans where you don't want the TUI:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
openhack scan /path/to/repo
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
OpenHack runs the same pipeline as the TUI, prints progress to stdout, writes a JSON report to `~/.openhack/scans/<session-id>.json`, and exits.
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
| Command | Description |
|
|
149
|
+
| -------------------------- | -------------------------------------------------------- |
|
|
150
|
+
| `openhack` | Launch interactive TUI |
|
|
151
|
+
| `openhack scan [path]` | Full scan, headless (defaults to `.`) |
|
|
152
|
+
| `openhack sessions` | List all saved scans |
|
|
153
|
+
| `openhack resume <id>` | Resume a scan from its last checkpoint |
|
|
154
|
+
| `openhack classify [path]` | Classify frameworks + detect entry points (no LLM calls) |
|
|
155
|
+
| `openhack login` | Log in to your OpenHack account |
|
|
156
|
+
| `openhack setup` | Run the setup wizard |
|
|
157
|
+
| `openhack --help` | Show usage |
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
Scans are checkpointed after each pipeline stage. If a scan is interrupted or fails, resume it:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
openhack resume <session-id>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
Configuration is stored in `~/.openhack/config` (mode `0600` since it contains a bearer token) and persists across sessions.
|
|
169
|
+
|
|
170
|
+
You can override at runtime via environment variables:
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
| Variable | Effect |
|
|
174
|
+
| ------------------ | ------------------------------------------------------------------------------------------------ |
|
|
175
|
+
| `OPENHACK_API_KEY` | Bearer token for the OpenHack inference API |
|
|
176
|
+
| `OPENHACK_DEV=1` | Point the CLI at local dev servers (app on `:9080`, inference on `:8787`) for self-hosted setups |
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
## Privacy
|
|
180
|
+
|
|
181
|
+
OpenHack reads and processes your source code **locally** — prompts are built on your machine. Only LLM tokens (not raw source files) are forwarded to the OpenHack inference API. No source code is uploaded or retained.
|
|
182
|
+
|
|
183
|
+
## Contributing
|
|
184
|
+
|
|
185
|
+
OpenHack is open source. Issues and PRs welcome on [GitHub](https://github.com/openhackai/openhack).
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
AGPL-3.0 — see [LICENSE](LICENSE). Free for personal, educational, and open-source use. For commercial licensing without AGPL obligations, contact [team@openhack.com](mailto:team@openhack.com).
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
openhack/__init__.py,sha256=9XWDrE_f_hDez7_120gBGOmVxjQTnJ8iuhmhpkzYJqY,89
|
|
2
|
+
openhack/__main__.py,sha256=9TnnjjxU3hi3COw4qe37d-VugxNCyJj2IYZgpeexsGo,6758
|
|
3
|
+
openhack/auth.py,sha256=BHC-21Fi45NKXtb2wpZEcKbcvR8NKncjYoVWLhVyqDI,5941
|
|
4
|
+
openhack/categories.py,sha256=8j9aXdTY0ekByZZ0_QpR5gbzZOc-6zMqV6w7zRTbp4k,5291
|
|
5
|
+
openhack/config.py,sha256=xLejsgw6B5wM_lZvoMAhkpC4wyWILZbscD2mrpoZriw,6539
|
|
6
|
+
openhack/deterministic_recon.py,sha256=gtZUFnJikpO8T_PpuDe_5ZowttvYZQgJ4X-J3x4rl8A,18867
|
|
7
|
+
openhack/entry_points.py,sha256=j8pyEbRnoYr9uhiq10Tl0Lsipis6lh51FRH_kssJGyw,29625
|
|
8
|
+
openhack/framework_classifier.py,sha256=TB7ogAh3HYf3-YiIAZcTgN0Tke8kgQshi-BYDkWcyQ0,16829
|
|
9
|
+
openhack/framework_detection.py,sha256=_Q_sS-7WYrTSUIzItm61JXbrgBlHR0waRoyVpT9hdKo,9110
|
|
10
|
+
openhack/headless_scan.py,sha256=3HI7agE-E9DzvVZ78HWDGX3EXQdtgqO2w8-_-jyiiWg,5955
|
|
11
|
+
openhack/quality.py,sha256=NF0qSqAb41-Npq9lf0kVYs8GXW8tQDvuuySATMId5y0,7512
|
|
12
|
+
openhack/scan_session.py,sha256=DhST3BBB7ombHqcxguOWq7_5aOoKmuI0frNq_-FrUSw,9759
|
|
13
|
+
openhack/setup.py,sha256=EaKEodlfQViuz6cw5K_JpWKtwTrw269b1ffWLSb6SIM,15844
|
|
14
|
+
openhack/static_validator.py,sha256=ZiICE4CoTj1beRTheMcavF9UAT8fcRC0iYksuEqp8zU,24723
|
|
15
|
+
openhack/tui.py,sha256=kWcXByLdrBkUSwpOhgVzwMPt6iJkGnFd5fa9xmynV2w,144968
|
|
16
|
+
openhack/updates.py,sha256=m6xIyGSeYUtalxr4oVOdjpvTc0szXKsr560Gv-i_Uoo,4918
|
|
17
|
+
openhack/agents/__init__.py,sha256=nxt3eCGM4Pw-DZy7_2dbOh7AUc0xJXh15X-iT98pmGY,742
|
|
18
|
+
openhack/agents/base.py,sha256=uNlJmV8PxPYP0l3xOz2wJID-u_d9XY1hQkyq3YNRsHI,9600
|
|
19
|
+
openhack/agents/browser_verifier.py,sha256=NU9AFFNRLViSAvfpiO_6LR59oLdppQhTQ6w8GUSjE78,27355
|
|
20
|
+
openhack/agents/browser_verifier_swarm.py,sha256=LSzuSaF1WW-s072mFU8otbmBzjEnYwajWR_efdqqlRA,11247
|
|
21
|
+
openhack/agents/checkpoint.py,sha256=tpcfDK2WuD54eAX-yOXLkEG4KkDjlxXeX2_Liv9hwCU,3466
|
|
22
|
+
openhack/agents/context_manager.py,sha256=UsC2dfqj8Urj_o3BThBPRUXdplsoEHYqbUaWpY-whO4,14669
|
|
23
|
+
openhack/agents/coordinator.py,sha256=IZEpmHU0VSWAmLhQ1A3-YBUPBNPqxlV-sFq4ffCUzrc,55761
|
|
24
|
+
openhack/agents/endpoint_analyst.py,sha256=QniEiy92IpRUpwGY7bThOnWcOXa7jlvrAhXnS3siHY0,10745
|
|
25
|
+
openhack/agents/feature_hunter.py,sha256=etp7_LN6Dp8qgX0TRoMY_vIp5w3pe9HxcqsNTjf2XMM,3399
|
|
26
|
+
openhack/agents/hunter.py,sha256=9uOwqhK5UTnEPTW6eDzs9ZebIfqqIKAAPV5JgFpmGFY,21545
|
|
27
|
+
openhack/agents/hunter_swarm.py,sha256=g0hSu5dJouM5iutHbA7JLrvONFlSJ5TnuCCscgxoXGg,17088
|
|
28
|
+
openhack/agents/llm.py,sha256=vyObGgbEwZF6mljV9h4RzyVVjtIm9uBWEagvInv8oBI,12424
|
|
29
|
+
openhack/agents/recon.py,sha256=KvZZhSq3XNmzeM0nwxTDA8HlC7XbuCYnCPZcj8MrTC8,657
|
|
30
|
+
openhack/agents/sandbox_verifier.py,sha256=llPTD8YyJWXszgcd9ucd9-IpRBzmKlyZZxj8D2IDPQ4,15054
|
|
31
|
+
openhack/agents/sandbox_verifier_swarm.py,sha256=2kFlUxPV3wk5DMB8TUYekKQFdh8h11ThY40fJ6HSLg8,10994
|
|
32
|
+
openhack/agents/session.py,sha256=Oawl3ZVm9ZpVZfrGuJmqGef9fNiqjFjzlX8tclxruHM,10552
|
|
33
|
+
openhack/agents/validator.py,sha256=PwWLGe4dJYtx8dfbPQwL3DDZI4C0a6slRSBjZLqnMB8,8487
|
|
34
|
+
openhack/agents/validator_swarm.py,sha256=3hyFqftXDPRplF0P6aRYpDx8OwR0_M9fKaPe2Mi6sxk,4485
|
|
35
|
+
openhack/browser/__init__.py,sha256=X7B_4QGBCatIykwfRJnoETatfvDNrCZ1vVOYnN7bTQM,391
|
|
36
|
+
openhack/browser/runner.py,sha256=opspL8qBpsl_drNdkVmXVH-YiG9iOa6od-6VEb_ynT4,13820
|
|
37
|
+
openhack/prompts/__init__.py,sha256=5zILgAcRLau2sOdfiQQ6LFkGxNPxUL257B6y-XxidF4,3726
|
|
38
|
+
openhack/prompts/browser_verifier.py,sha256=vv_zxzJJpLJ4vzYeu-IjDYBuqhPUjJYYQNaSfxI0siQ,8366
|
|
39
|
+
openhack/prompts/coordinator.py,sha256=2tXBXoYXi8GoOOScD8LR4H-jyBz2Q7BBKgQgEMnJ9gs,1067
|
|
40
|
+
openhack/prompts/endpoint_analyst.py,sha256=4EKFDckaw3l8RxvLMBo0WFnE0Y2E7JaXFTM8u4Ki3Ig,6141
|
|
41
|
+
openhack/prompts/feature_hunter.py,sha256=WiBOfdKmdBIJd-qqm06dSUIYpVAYx09Bu8bWML9yGzs,7852
|
|
42
|
+
openhack/prompts/hunter.py,sha256=JgzahwK1Yb3D0zfENKAilnOh9RgQH1RS-PF-nWl-1u0,26015
|
|
43
|
+
openhack/prompts/hunter_continuation_loop.py,sha256=f8zXIYd_F0-NYxKDIXwxoVDl8E0N-hSxfm5wMxsCCoc,502
|
|
44
|
+
openhack/prompts/hunter_continuation_no_findings.py,sha256=NE_zYkcEF7z9MKYPP4t5HBgYjvuGCpfzJAk9TDjr8L0,1126
|
|
45
|
+
openhack/prompts/hunter_continuation_no_progress.py,sha256=5OFKMgE_XnnDoxWShZAWJTAL4DpJdW1FplxtBKgIrhk,1325
|
|
46
|
+
openhack/prompts/hunter_tool_instructions.py,sha256=2iRhwLIUymmeZIPNNI-F0EsYBf41y_2rv-ya9i0sO9g,1791
|
|
47
|
+
openhack/prompts/pr_analysis_system.py,sha256=4xEc_okSgK8pw5O_w0rWLGVVcLp17jMUaTB9dy_Lzcc,4851
|
|
48
|
+
openhack/prompts/pr_analysis_user.py,sha256=VbCYO1aLLS9y2FL8N45LFCM432mMV3-kf9VCa0lnyDo,241
|
|
49
|
+
openhack/prompts/project_context.py,sha256=1N_-_0pwnrdvkLAyPOnARsUB10D54nosewM1Gh1IxPc,3203
|
|
50
|
+
openhack/prompts/recon.py,sha256=W4iqgBefhwJuCIfAWY_THWVs0UVbd1MO-pRmiFQbfkE,9605
|
|
51
|
+
openhack/prompts/reporter.py,sha256=3ZgYPq509f4beXBHV-Q_KkgWWZAGWuK_Nm3wK9GkPk4,1527
|
|
52
|
+
openhack/prompts/researchers.py,sha256=vjwX7txBjNu6BlGXKRiGsPauoVpVBKeVLBRwsFXo6fQ,28137
|
|
53
|
+
openhack/prompts/sandbox_verifier.py,sha256=LBQ18zGXSYztCUezbZUmHkskhPhmDwW-zavglCijTkc,5566
|
|
54
|
+
openhack/prompts/validator.py,sha256=zOfGza8uPAztHrRxduwHM_14x7diFmxZiW8aAH2MXKM,20227
|
|
55
|
+
openhack/prompts/validator_continuation_incomplete.py,sha256=HdWEHtZSg25ZLWrTPm_DR1JY3mTNTRl46p4PJkmLvW4,545
|
|
56
|
+
openhack/prompts/validator_tool_instructions.py,sha256=wS2JOf7_3Ii9EWJ-5Qu33ao3H3Se6I9ZRVLaYNHiw2g,1059
|
|
57
|
+
openhack/prompts/django/__init__.py,sha256=raxFuUWLb5i_v0acAkWVWVnOz5wU0cJ_yOcEImctzDU,964
|
|
58
|
+
openhack/prompts/django/auth_bypass.py,sha256=jNk_TCuWNT63f11VF_7OyDQ82BaDJScrmJZrNvQZ42Y,2382
|
|
59
|
+
openhack/prompts/django/csrf.py,sha256=tc2zefCijE-UVAS9UZiGPN4ewcVnesV10Vvg_Qn-HUc,1866
|
|
60
|
+
openhack/prompts/django/data_exposure.py,sha256=wKw2sQDxoqcRuEZD319aw1_VvIvL8ja57hcxSlOhXYU,2101
|
|
61
|
+
openhack/prompts/django/idor.py,sha256=Sa8aW4asrIPHyEOpJHW_nlkTMbTR1lfm77R-dZ2WqzQ,2393
|
|
62
|
+
openhack/prompts/django/injection.py,sha256=5Pz1_rU0Vz4OtQuS7KMOoxQ3Kw-O5Btn6aOwtxAUCKQ,2007
|
|
63
|
+
openhack/prompts/django/misconfiguration.py,sha256=wpfsD05DlSdwPnbInznFNxgfbvEQw7-XKGe9Khbu5q8,1965
|
|
64
|
+
openhack/prompts/django/ssrf.py,sha256=kQkmQk5jJsH5bKazp5opqG_jZNYHMRrpzxB_IIxIr2g,1914
|
|
65
|
+
openhack/prompts/express/__init__.py,sha256=O5OoyjBo02tmpJtMFTNqfq-RqjVCQW4_liiA7AQ7PSk,893
|
|
66
|
+
openhack/prompts/express/auth_bypass.py,sha256=KEVvvXhUbwjM6BBzV8BBePgYIswitHX9gEOHkRVquzY,2120
|
|
67
|
+
openhack/prompts/express/data_exposure.py,sha256=Yy0hc0LUcQGCl3p-GgUwlTS75hA4tqVdBlMdWdmGpj4,2321
|
|
68
|
+
openhack/prompts/express/idor.py,sha256=fcokHAcVEUhs1eu4X92PfRkMUBG2D_80CgRQax0s7mA,2106
|
|
69
|
+
openhack/prompts/express/injection.py,sha256=FKrWVMQUb-StswRoZoXMnqatIS1izuDSLc-jFcerytg,2299
|
|
70
|
+
openhack/prompts/express/misconfiguration.py,sha256=eYMDaWY5cUDNYbpFk7BTDo7-m3ok_Dr8HVnMbJ9jkeI,2155
|
|
71
|
+
openhack/prompts/express/ssrf.py,sha256=OBlbDGWtRfCvm5hkpMN6PcMir_cGPZUCTHXsY7tNNAc,1992
|
|
72
|
+
openhack/prompts/flask/__init__.py,sha256=h7pWUZ1HUKDpdpAEmfSbgQ1ZJlfWBET8GJG-j6bWAC0,848
|
|
73
|
+
openhack/prompts/flask/auth_bypass.py,sha256=Xgg6db8sN-Z0tUNFyeWKTPHjybmAKngtLrCLMxcnCog,2390
|
|
74
|
+
openhack/prompts/flask/data_exposure.py,sha256=EbVbRO2Ye1w1HNGGoThoEkn71RqvRQtqiFFTX8xaekU,2325
|
|
75
|
+
openhack/prompts/flask/idor.py,sha256=Xgs2FKs3eNxCVHO8xN-dDJDsAnjxjfCpduYcrXmNKMg,2574
|
|
76
|
+
openhack/prompts/flask/injection.py,sha256=c2VsWiiDT963u9s-WvZOpigEPQq5JJGJ5LbGPw8e4XA,2323
|
|
77
|
+
openhack/prompts/flask/misconfiguration.py,sha256=CWMglLuilfg4PErr34WqNOMi9l7cC7XI5b7zSA1ZWyA,2207
|
|
78
|
+
openhack/prompts/flask/ssrf.py,sha256=h8f_nsJN6hDKTXA7TVqPkvOWfBKMEAgmLeCc_2czHKM,1894
|
|
79
|
+
openhack/prompts/nextjs/__init__.py,sha256=8avE5rNmLpnl4kOJ78AVwlezQvFMDBM0bUrWbxXDyGI,1430
|
|
80
|
+
openhack/prompts/nextjs/auth_bypass.py,sha256=xkdoBqgeEjmXWG6kT7srTFxI6jwP12U8o4sFJCQBdig,1997
|
|
81
|
+
openhack/prompts/nextjs/csrf.py,sha256=y-66G7_tk3hdlBi0_jEx8ptkD60mtVR3kkq1Vdh-9Cw,2110
|
|
82
|
+
openhack/prompts/nextjs/data_exposure.py,sha256=Fe6tsDxq_VCYsxxaOg0k7BLkoUdkp73UxciS36lSWIE,2125
|
|
83
|
+
openhack/prompts/nextjs/idor.py,sha256=p724eOB8An8LMqcs-GjAtAYkQkUG_zH2V82PqD7wcEA,1938
|
|
84
|
+
openhack/prompts/nextjs/injection.py,sha256=4tEruIYwewewjOgxMuX0LHhREzV3zldXSvXgZd1FgjM,1888
|
|
85
|
+
openhack/prompts/nextjs/middleware_bypass.py,sha256=eBNpBOucP7BZ1nWyF0zsf1u_ojOhKlxMB4tUzn_tvsQ,1808
|
|
86
|
+
openhack/prompts/nextjs/misconfiguration.py,sha256=pN8HxYzk5Tzs272x_-rS34TIZpy9KBFDzXPhuanu824,2132
|
|
87
|
+
openhack/prompts/nextjs/server_actions.py,sha256=tnXxv6CFdPdpfJXyAlrDFluhE0hFMlYOj3FoTakwezo,2317
|
|
88
|
+
openhack/prompts/nextjs/ssrf.py,sha256=k74-EiJCS41cIUpgiLerpFBsmzxKR5us5-qCRon5Vek,1839
|
|
89
|
+
openhack/prompts/nextjs/xss.py,sha256=CpufRzgN2O-06pFndgRY7bDSo4Rtje60_iF9xqZc6ZU,1953
|
|
90
|
+
openhack/prompts/supabase/__init__.py,sha256=zB4GN5VxH93_sd3AT-tgb8LsvShnBLmXSQW42pAbcCI,1404
|
|
91
|
+
openhack/prompts/supabase/auth_tokens.py,sha256=Ki70mhantnTd21ceZ2Xca658GKc6XYRESdeMi-oVt2I,5720
|
|
92
|
+
openhack/prompts/supabase/edge_functions.py,sha256=suLWadQeXRyL-1BBI9cwGL8NUYFU6r_UHzwVPPnPVwg,5526
|
|
93
|
+
openhack/prompts/supabase/graphql.py,sha256=gEKauXouuNo7pz4rlxu5mXWA_1VZE9qgE4pZ_jdcD-g,3473
|
|
94
|
+
openhack/prompts/supabase/postgrest.py,sha256=ngob-RKt_voSiBa3b-BObC9ri-iQgP5cuncmSbfqlIk,4164
|
|
95
|
+
openhack/prompts/supabase/realtime.py,sha256=4raTILryluvvRuLLfVJrkm4rxbbEImLQ5lmGQkyVHoM,3634
|
|
96
|
+
openhack/prompts/supabase/rls.py,sha256=xTYqcjqKTCV4WpOTO8QyGcdv-Jtg258i61XEpsRAdas,4754
|
|
97
|
+
openhack/prompts/supabase/rpc_functions.py,sha256=A9UMBd8fw9tZlo4n6TRrb9cea4Ji3DV0vFqGyyZO9NM,4262
|
|
98
|
+
openhack/prompts/supabase/storage.py,sha256=wwjhTdbgR-KS6wqC3hydzuA91VBS6hdcVTE2rCj4HmU,4586
|
|
99
|
+
openhack/prompts/supabase/tenant_isolation.py,sha256=UAwdw-9jWU48IDQ2pwrniNL6p9Y-8gEChz4VhvUp-0Y,4561
|
|
100
|
+
openhack/sandbox/__init__.py,sha256=9zG2LlE-B16NcSD8aDeOqmwANKfpm8ILqg4ZMDQT7fk,325
|
|
101
|
+
openhack/sandbox/orchestrator.py,sha256=khHYkOfRUo1ZpXeuBCjIsBkY86Ikxnp6Q7VaFg8RKKQ,19297
|
|
102
|
+
openhack/sandbox/runner.py,sha256=-gkBi9qNW8HgU4aNOBBJYFgtMW_6Pf7yPztCikv52OA,5921
|
|
103
|
+
openhack/tools/__init__.py,sha256=jRUmIPA2pVJjI0ZlPFOotF9Jb6qzdbxIfokmHMxoEBM,22
|
|
104
|
+
openhack/tools/ast_tools.py,sha256=zEXZpA0VFJw_5QNTpEMstkjR6s1PHOu6A-vLBJXAcZw,12655
|
|
105
|
+
openhack/tools/coverage.py,sha256=97AC7Oo3GV1YMQ3Cm9-4g0nHuV-mI_fECMp4M648qHs,43419
|
|
106
|
+
openhack/tools/filesystem.py,sha256=Yj8Tc_90yiGm4I217Ck5klv7kAzzhJ-RlhtbXXBkODA,16410
|
|
107
|
+
openhack/tools/nextjs.py,sha256=_bYfLbAatuQe2Ae3K_CJN3Dk-h8WDYWReexBpvPsI4A,10368
|
|
108
|
+
openhack/tools/registry.py,sha256=48lIpGeKf-W1C81UwrcY7Pb9RFJi6HBu7vS0753t51c,1790
|
|
109
|
+
openhack-0.1.0.dist-info/METADATA,sha256=pqK5ASkNCHXl1bTxaaZ2viskf86eBmvC08EmKZHZrYY,10413
|
|
110
|
+
openhack-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
111
|
+
openhack-0.1.0.dist-info/entry_points.txt,sha256=89qyBLVygik6q5v0jcYfaUty36c8Ne1Ici9n48yvpcM,52
|
|
112
|
+
openhack-0.1.0.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
|
113
|
+
openhack-0.1.0.dist-info/RECORD,,
|