openhack-cli 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenHack
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: openhack-cli
3
+ Version: 0.1.0
4
+ Summary: Command-line interface for the OpenHack security platform.
5
+ Author: OpenHack
6
+ License: MIT
7
+ Project-URL: Homepage, https://openhack.com
8
+ Project-URL: Repository, https://github.com/openhackai/cli
9
+ Project-URL: Issues, https://github.com/openhackai/cli/issues
10
+ Keywords: openhack,security,pentest,cli,sast,vulnerability
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Security
24
+ Classifier: Topic :: Software Development :: Quality Assurance
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: click>=8.1
29
+ Requires-Dist: requests>=2.31
30
+ Requires-Dist: rich>=13.7
31
+ Dynamic: license-file
32
+
33
+ # OpenHack CLI
34
+
35
+ `openhack-cli` is the command-line interface for the [OpenHack](https://openhack.com)
36
+ security platform. It does one thing: talk to the OpenHack app in an
37
+ authenticated way, so that **humans and agents** can drive the platform from a
38
+ terminal or a script. No scanning happens in the CLI itself — it's a thin,
39
+ authenticated client over the OpenHack API.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pipx install openhack-cli # recommended (isolated)
45
+ # or
46
+ pip install openhack-cli
47
+ ```
48
+
49
+ From source:
50
+
51
+ ```bash
52
+ git clone <this repo> && cd openhack-cli
53
+ pip install -e .
54
+ ```
55
+
56
+ Requires Python 3.9+.
57
+
58
+ > **Driving the CLI from an automated agent?** See [`AGENT.md`](./AGENT.md) — an
59
+ > exhaustive, copy-pasteable command + field reference covering every command
60
+ > group, with allowed enum values and `--json` output for automation.
61
+
62
+ ## Quick start
63
+
64
+ ```bash
65
+ openhack-cli auth login # device-code login in your browser
66
+ openhack-cli orgs list # list your organizations
67
+ openhack-cli orgs use acme # pick the active org
68
+ openhack-cli projects list # list projects
69
+ openhack-cli projects use web # pick the active project
70
+ openhack-cli scans list # scans for the active project
71
+ openhack-cli vulns list # vulnerabilities across recent scans
72
+ ```
73
+
74
+ ## Authentication
75
+
76
+ Login uses the same **device-code flow** as the OpenHack web app:
77
+
78
+ 1. `auth login` calls `POST /api/cli/auth` and prints a short code + URL.
79
+ 2. Your browser opens; you sign in, choose an organization, and approve.
80
+ 3. The CLI polls `POST /api/cli/auth/poll` until approval, then stores the
81
+ issued token.
82
+
83
+ The token is a long-lived, **org-scoped** API key sent as
84
+ `Authorization: Bearer openhack_…` on every request. It is stored at
85
+ `$XDG_CONFIG_HOME/openhack/config.json` (default `~/.config/openhack/config.json`)
86
+ with `0600` permissions.
87
+
88
+ ```bash
89
+ openhack-cli auth status # who am I / active context
90
+ openhack-cli auth token # print the raw token (scripting)
91
+ openhack-cli auth logout # remove local credentials
92
+ ```
93
+
94
+ ## Commands
95
+
96
+ | Group | Command | Description |
97
+ |-------|---------|-------------|
98
+ | `auth` | `login`, `logout`, `status`/`whoami`, `token` | Manage credentials |
99
+ | `orgs` | `list`, `use <id\|slug>` | List / select organizations |
100
+ | `projects` | `list`, `get`, `use`, `create` | Manage projects |
101
+ | `scans` | `list`, `get <scan_id>`, `trigger-full` | View / trigger scans |
102
+ | `vulns` | `list`, `groups`, `report`, `get`, `edit` | View, report, and edit project vulnerabilities |
103
+ | `pentest` | `list`, `get`, `create` | Pentesting engagements |
104
+ | `pentest findings` | `[engagement]` | Findings in an engagement (defaults to latest) |
105
+ | `pentest finding` | `get`, `create`, `update`, `delete`, `link`, `unlink` | Single finding: view / create / patch / delete / cross-reference |
106
+ | `config` | `show`, `set`, `path` | CLI configuration |
107
+
108
+ For an exhaustive, example-driven command reference (aimed at automation and
109
+ agents), see [`AGENT.md`](./AGENT.md).
110
+
111
+ ## Scripting & agents
112
+
113
+ Pass `--json` (global flag) to get machine-readable output from any command:
114
+
115
+ ```bash
116
+ openhack-cli --json scans list
117
+ openhack-cli --json vulns list --severity critical
118
+ ```
119
+
120
+ Configuration can be driven entirely by environment variables (handy in CI):
121
+
122
+ | Variable | Purpose |
123
+ |----------|---------|
124
+ | `OPENHACK_TOKEN` | API token (overrides stored credentials) |
125
+ | `OPENHACK_APP_URL` | App base URL (default `https://app.openhack.com`) |
126
+ | `OPENHACK_DEV` | Set to `1` to target the local dev server (`http://localhost:9080`) |
127
+ | `XDG_CONFIG_HOME` | Where the config file lives |
128
+
129
+ ### Targeting an environment
130
+
131
+ Production (`https://app.openhack.com`) is the default. For local dev work
132
+ (`http://localhost:9080`), the easiest option is to export `OPENHACK_DEV` once —
133
+ then every command targets localhost with no flags:
134
+
135
+ ```bash
136
+ export OPENHACK_DEV=1 # add to your ~/.zshrc for permanent dev mode
137
+ openhack-cli auth login # now logs in against localhost:9080
138
+ openhack-cli scans list
139
+ ```
140
+
141
+ Or use the `--local` flag per-command (shorthand for `--app-url
142
+ http://localhost:9080`):
143
+
144
+ ```bash
145
+ openhack-cli --local auth login
146
+ ```
147
+
148
+ The app URL is resolved with this precedence (highest first):
149
+
150
+ 1. `--app-url` / `--local` flag
151
+ 2. `OPENHACK_APP_URL` env var
152
+ 3. `OPENHACK_DEV=1` → `http://localhost:9080`
153
+ 4. saved config (last login, or `openhack-cli config set app_url <url>`)
154
+ 5. built-in default `https://app.openhack.com`
155
+
156
+ Exit codes: `0` success, `1` API/usage error, `2` auth error, `130` interrupted.
157
+
158
+ ## Configuration
159
+
160
+ ```bash
161
+ openhack-cli config show
162
+ openhack-cli config set app_url https://your-openhack-host
163
+ openhack-cli config path
164
+ ```
@@ -0,0 +1,132 @@
1
+ # OpenHack CLI
2
+
3
+ `openhack-cli` is the command-line interface for the [OpenHack](https://openhack.com)
4
+ security platform. It does one thing: talk to the OpenHack app in an
5
+ authenticated way, so that **humans and agents** can drive the platform from a
6
+ terminal or a script. No scanning happens in the CLI itself — it's a thin,
7
+ authenticated client over the OpenHack API.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pipx install openhack-cli # recommended (isolated)
13
+ # or
14
+ pip install openhack-cli
15
+ ```
16
+
17
+ From source:
18
+
19
+ ```bash
20
+ git clone <this repo> && cd openhack-cli
21
+ pip install -e .
22
+ ```
23
+
24
+ Requires Python 3.9+.
25
+
26
+ > **Driving the CLI from an automated agent?** See [`AGENT.md`](./AGENT.md) — an
27
+ > exhaustive, copy-pasteable command + field reference covering every command
28
+ > group, with allowed enum values and `--json` output for automation.
29
+
30
+ ## Quick start
31
+
32
+ ```bash
33
+ openhack-cli auth login # device-code login in your browser
34
+ openhack-cli orgs list # list your organizations
35
+ openhack-cli orgs use acme # pick the active org
36
+ openhack-cli projects list # list projects
37
+ openhack-cli projects use web # pick the active project
38
+ openhack-cli scans list # scans for the active project
39
+ openhack-cli vulns list # vulnerabilities across recent scans
40
+ ```
41
+
42
+ ## Authentication
43
+
44
+ Login uses the same **device-code flow** as the OpenHack web app:
45
+
46
+ 1. `auth login` calls `POST /api/cli/auth` and prints a short code + URL.
47
+ 2. Your browser opens; you sign in, choose an organization, and approve.
48
+ 3. The CLI polls `POST /api/cli/auth/poll` until approval, then stores the
49
+ issued token.
50
+
51
+ The token is a long-lived, **org-scoped** API key sent as
52
+ `Authorization: Bearer openhack_…` on every request. It is stored at
53
+ `$XDG_CONFIG_HOME/openhack/config.json` (default `~/.config/openhack/config.json`)
54
+ with `0600` permissions.
55
+
56
+ ```bash
57
+ openhack-cli auth status # who am I / active context
58
+ openhack-cli auth token # print the raw token (scripting)
59
+ openhack-cli auth logout # remove local credentials
60
+ ```
61
+
62
+ ## Commands
63
+
64
+ | Group | Command | Description |
65
+ |-------|---------|-------------|
66
+ | `auth` | `login`, `logout`, `status`/`whoami`, `token` | Manage credentials |
67
+ | `orgs` | `list`, `use <id\|slug>` | List / select organizations |
68
+ | `projects` | `list`, `get`, `use`, `create` | Manage projects |
69
+ | `scans` | `list`, `get <scan_id>`, `trigger-full` | View / trigger scans |
70
+ | `vulns` | `list`, `groups`, `report`, `get`, `edit` | View, report, and edit project vulnerabilities |
71
+ | `pentest` | `list`, `get`, `create` | Pentesting engagements |
72
+ | `pentest findings` | `[engagement]` | Findings in an engagement (defaults to latest) |
73
+ | `pentest finding` | `get`, `create`, `update`, `delete`, `link`, `unlink` | Single finding: view / create / patch / delete / cross-reference |
74
+ | `config` | `show`, `set`, `path` | CLI configuration |
75
+
76
+ For an exhaustive, example-driven command reference (aimed at automation and
77
+ agents), see [`AGENT.md`](./AGENT.md).
78
+
79
+ ## Scripting & agents
80
+
81
+ Pass `--json` (global flag) to get machine-readable output from any command:
82
+
83
+ ```bash
84
+ openhack-cli --json scans list
85
+ openhack-cli --json vulns list --severity critical
86
+ ```
87
+
88
+ Configuration can be driven entirely by environment variables (handy in CI):
89
+
90
+ | Variable | Purpose |
91
+ |----------|---------|
92
+ | `OPENHACK_TOKEN` | API token (overrides stored credentials) |
93
+ | `OPENHACK_APP_URL` | App base URL (default `https://app.openhack.com`) |
94
+ | `OPENHACK_DEV` | Set to `1` to target the local dev server (`http://localhost:9080`) |
95
+ | `XDG_CONFIG_HOME` | Where the config file lives |
96
+
97
+ ### Targeting an environment
98
+
99
+ Production (`https://app.openhack.com`) is the default. For local dev work
100
+ (`http://localhost:9080`), the easiest option is to export `OPENHACK_DEV` once —
101
+ then every command targets localhost with no flags:
102
+
103
+ ```bash
104
+ export OPENHACK_DEV=1 # add to your ~/.zshrc for permanent dev mode
105
+ openhack-cli auth login # now logs in against localhost:9080
106
+ openhack-cli scans list
107
+ ```
108
+
109
+ Or use the `--local` flag per-command (shorthand for `--app-url
110
+ http://localhost:9080`):
111
+
112
+ ```bash
113
+ openhack-cli --local auth login
114
+ ```
115
+
116
+ The app URL is resolved with this precedence (highest first):
117
+
118
+ 1. `--app-url` / `--local` flag
119
+ 2. `OPENHACK_APP_URL` env var
120
+ 3. `OPENHACK_DEV=1` → `http://localhost:9080`
121
+ 4. saved config (last login, or `openhack-cli config set app_url <url>`)
122
+ 5. built-in default `https://app.openhack.com`
123
+
124
+ Exit codes: `0` success, `1` API/usage error, `2` auth error, `130` interrupted.
125
+
126
+ ## Configuration
127
+
128
+ ```bash
129
+ openhack-cli config show
130
+ openhack-cli config set app_url https://your-openhack-host
131
+ openhack-cli config path
132
+ ```
@@ -0,0 +1,3 @@
1
+ """OpenHack CLI — programmatic, authenticated access to the OpenHack platform."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,88 @@
1
+ """OpenHack CLI entrypoint.
2
+
3
+ Wires the command groups together, sets up the shared Click context (config +
4
+ output mode), and provides uniform error handling for API/auth failures.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+
11
+ import click
12
+
13
+ from . import __version__, output
14
+ from .client import APIError, AuthError
15
+ from .commands.auth import auth
16
+ from .commands.config_cmd import config
17
+ from .commands.orgs import orgs
18
+ from .commands.pentest import pentest
19
+ from .commands.projects import projects
20
+ from .commands.scans import scans
21
+ from .commands.vulns import vulns
22
+ from .config import Config, LOCAL_APP_URL
23
+
24
+ CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
25
+
26
+
27
+ @click.group(context_settings=CONTEXT_SETTINGS)
28
+ @click.version_option(__version__, "-v", "--version", prog_name="openhack-cli")
29
+ @click.option("--json", "json_output", is_flag=True,
30
+ help="Output raw JSON (for scripts and agents).")
31
+ @click.option("--app-url", default=None, help="Override the OpenHack app URL.")
32
+ @click.option("--local", "use_local", is_flag=True,
33
+ help=f"Target the local dev server ({LOCAL_APP_URL}).")
34
+ @click.option("--token", default=None,
35
+ help="Override the API token (else uses stored credentials).")
36
+ @click.pass_context
37
+ def cli(ctx: click.Context, json_output: bool, app_url: str | None,
38
+ use_local: bool, token: str | None) -> None:
39
+ """OpenHack CLI — authenticated, programmatic access to OpenHack.
40
+
41
+ Start with `openhack-cli auth login`, then explore `orgs`, `projects`,
42
+ `scans`, `vulns`, and `pentest`.
43
+ """
44
+ cfg = Config.load()
45
+ # --local is a shorthand for the dev server; --app-url wins if both given.
46
+ # These are runtime overrides (beat OPENHACK_DEV / OPENHACK_APP_URL / config).
47
+ if use_local:
48
+ cfg.set_override_app_url(LOCAL_APP_URL)
49
+ if app_url:
50
+ cfg.set_override_app_url(app_url.rstrip("/"))
51
+ if token:
52
+ cfg.set("token", token)
53
+ ctx.obj = {"config": cfg, "json": json_output}
54
+
55
+
56
+ cli.add_command(auth)
57
+ cli.add_command(orgs)
58
+ cli.add_command(projects)
59
+ cli.add_command(scans)
60
+ cli.add_command(vulns)
61
+ cli.add_command(pentest)
62
+ cli.add_command(config)
63
+
64
+
65
+ def main() -> None:
66
+ try:
67
+ cli(standalone_mode=False)
68
+ except AuthError as exc:
69
+ output.error(f"Authentication failed ({exc.status}): {exc.message}")
70
+ output.info("Run `openhack-cli auth login` to (re)authorize, or check "
71
+ "that this endpoint accepts CLI tokens.")
72
+ sys.exit(2)
73
+ except APIError as exc:
74
+ output.error(exc.message if exc.status else str(exc))
75
+ sys.exit(1)
76
+ except click.ClickException as exc:
77
+ exc.show()
78
+ sys.exit(exc.exit_code)
79
+ except click.Abort:
80
+ output.error("Aborted.")
81
+ sys.exit(130)
82
+ except KeyboardInterrupt:
83
+ output.error("Interrupted.")
84
+ sys.exit(130)
85
+
86
+
87
+ if __name__ == "__main__":
88
+ main()
@@ -0,0 +1,124 @@
1
+ """HTTP client for the OpenHack API.
2
+
3
+ Wraps ``requests`` with the CLI's Bearer-token auth and uniform error
4
+ handling. Every authenticated endpoint expects::
5
+
6
+ Authorization: Bearer openhack_<hex>
7
+
8
+ which is the token issued by the device-code login flow.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, Optional
14
+
15
+ import requests
16
+
17
+ from . import __version__
18
+
19
+ # Endpoints that the CLI talks to are JSON unless noted (e.g. PDF reports).
20
+ USER_AGENT = f"openhack-cli/{__version__}"
21
+ DEFAULT_TIMEOUT = 30
22
+
23
+
24
+ class APIError(Exception):
25
+ """Raised when the API returns a non-2xx response."""
26
+
27
+ def __init__(self, status: int, message: str, body: Any = None):
28
+ self.status = status
29
+ self.message = message
30
+ self.body = body
31
+ super().__init__(f"[{status}] {message}")
32
+
33
+
34
+ class AuthError(APIError):
35
+ """Raised on 401/403 so the CLI can prompt the user to re-login."""
36
+
37
+
38
+ class Client:
39
+ def __init__(self, app_url: str, token: Optional[str] = None,
40
+ timeout: int = DEFAULT_TIMEOUT):
41
+ self.app_url = app_url.rstrip("/")
42
+ self.token = token
43
+ self.timeout = timeout
44
+ self._session = requests.Session()
45
+
46
+ # ----- low-level --------------------------------------------------------
47
+ def _headers(self, extra: Optional[dict] = None, auth: bool = True) -> dict:
48
+ headers = {"User-Agent": USER_AGENT, "Accept": "application/json"}
49
+ if auth and self.token:
50
+ headers["Authorization"] = f"Bearer {self.token}"
51
+ if extra:
52
+ headers.update(extra)
53
+ return headers
54
+
55
+ def request(
56
+ self,
57
+ method: str,
58
+ path: str,
59
+ *,
60
+ json: Any = None,
61
+ params: Optional[dict] = None,
62
+ auth: bool = True,
63
+ raw: bool = False,
64
+ ) -> Any:
65
+ """Perform a request and return parsed JSON (or the raw Response).
66
+
67
+ ``raw=True`` returns the underlying ``requests.Response`` so callers can
68
+ stream binary payloads (e.g. PDF reports).
69
+ """
70
+ url = path if path.startswith("http") else f"{self.app_url}{path}"
71
+ try:
72
+ resp = self._session.request(
73
+ method,
74
+ url,
75
+ json=json,
76
+ params=params,
77
+ headers=self._headers(auth=auth),
78
+ timeout=self.timeout,
79
+ )
80
+ except requests.RequestException as exc:
81
+ raise APIError(0, f"Network error: {exc}") from exc
82
+
83
+ if resp.status_code >= 400:
84
+ self._raise_for_status(resp)
85
+
86
+ if raw:
87
+ return resp
88
+ if not resp.content:
89
+ return None
90
+ ctype = resp.headers.get("Content-Type", "")
91
+ if "application/json" in ctype:
92
+ return resp.json()
93
+ return resp.content
94
+
95
+ @staticmethod
96
+ def _raise_for_status(resp: requests.Response) -> None:
97
+ message = resp.reason or "Request failed"
98
+ body: Any = None
99
+ try:
100
+ body = resp.json()
101
+ if isinstance(body, dict):
102
+ message = body.get("error") or body.get("message") or message
103
+ if body.get("message") and body.get("error"):
104
+ message = f"{body['error']}: {body['message']}"
105
+ except ValueError:
106
+ text = resp.text.strip()
107
+ if text:
108
+ message = text[:300]
109
+ if resp.status_code in (401, 403):
110
+ raise AuthError(resp.status_code, message, body)
111
+ raise APIError(resp.status_code, message, body)
112
+
113
+ # ----- verb helpers -----------------------------------------------------
114
+ def get(self, path: str, **kw) -> Any:
115
+ return self.request("GET", path, **kw)
116
+
117
+ def post(self, path: str, **kw) -> Any:
118
+ return self.request("POST", path, **kw)
119
+
120
+ def patch(self, path: str, **kw) -> Any:
121
+ return self.request("PATCH", path, **kw)
122
+
123
+ def delete(self, path: str, **kw) -> Any:
124
+ return self.request("DELETE", path, **kw)
@@ -0,0 +1 @@
1
+ """Command groups for the OpenHack CLI."""