dryhack-mcp 2.1.3__tar.gz → 2.3.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.
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/PKG-INFO +81 -2
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/README.md +76 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/__init__.py +1 -1
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/config.py +16 -0
- dryhack_mcp-2.3.0/dryhack_mcp/server.py +189 -0
- dryhack_mcp-2.3.0/dryhack_mcp/tools/browser_tool.py +705 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp.egg-info/PKG-INFO +81 -2
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp.egg-info/SOURCES.txt +3 -1
- dryhack_mcp-2.3.0/dryhack_mcp.egg-info/requires.txt +9 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/pyproject.toml +6 -2
- dryhack_mcp-2.3.0/tests/test_browser.py +142 -0
- dryhack_mcp-2.1.3/dryhack_mcp/server.py +0 -77
- dryhack_mcp-2.1.3/dryhack_mcp.egg-info/requires.txt +0 -5
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/LICENSE +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/__main__.py +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/tools/__init__.py +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/tools/authorize.py +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/tools/curl_tool.py +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/tools/exec.py +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/tools/python_tool.py +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/tools/scope.py +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp/tools/shell_tool.py +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp.egg-info/dependency_links.txt +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp.egg-info/entry_points.txt +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/dryhack_mcp.egg-info/top_level.txt +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/setup.cfg +0 -0
- {dryhack_mcp-2.1.3 → dryhack_mcp-2.3.0}/tests/test_authorize.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dryhack-mcp
|
|
3
|
-
Version: 2.
|
|
4
|
-
Summary: DryHack-MCP - an MCP server providing offensive-security tooling (curl, python, shell) and
|
|
3
|
+
Version: 2.3.0
|
|
4
|
+
Summary: DryHack-MCP - an MCP server providing offensive-security tooling (curl, python, shell, Playwright browser) and a scope-gated authorize engine for authorized penetration testing.
|
|
5
5
|
Author: Afrizal F.A
|
|
6
6
|
License: MIT License
|
|
7
7
|
|
|
@@ -36,9 +36,12 @@ Requires-Python: >=3.9
|
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
License-File: LICENSE
|
|
38
38
|
Requires-Dist: mcp<2,>=1.2.0
|
|
39
|
+
Requires-Dist: playwright>=1.40
|
|
39
40
|
Provides-Extra: dev
|
|
40
41
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
41
42
|
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
43
|
+
Provides-Extra: browser
|
|
44
|
+
Requires-Dist: playwright>=1.40; extra == "browser"
|
|
42
45
|
Dynamic: license-file
|
|
43
46
|
|
|
44
47
|
# DryHack-MCP
|
|
@@ -51,6 +54,7 @@ tooling for **authorized** penetration testing. It exposes four tools:
|
|
|
51
54
|
| `curl` | Raw HTTP interaction for web recon/exploitation |
|
|
52
55
|
| `python` | Run ad-hoc Python snippets for scripted probing |
|
|
53
56
|
| `shell` | Run shell commands (`nmap`, `ffuf`, `nc`, `sqlmap`, …) |
|
|
57
|
+
| `browser` | Full Playwright browser automation: navigation, DOM interaction, JavaScript injection, screenshots/PDF, cookies/headers, multi-step chains |
|
|
54
58
|
| `authorize` | Checks target membership in the per-call `scope` parameter; does not verify permission (no external API, no creds) |
|
|
55
59
|
|
|
56
60
|
> ⚠️ **Legal notice.** Use this only against systems you own or are explicitly
|
|
@@ -83,6 +87,24 @@ For development:
|
|
|
83
87
|
python3 -m pip install -e ".[dev]"
|
|
84
88
|
```
|
|
85
89
|
|
|
90
|
+
### Browser tool (Playwright)
|
|
91
|
+
|
|
92
|
+
Playwright is a **core dependency**, so a plain `pip install dryhack-mcp` /
|
|
93
|
+
`uvx dryhack-mcp` already ships it — no extra needed. The browser *binaries*
|
|
94
|
+
(Chromium/Firefox/WebKit) are not distributed via pip, so the first time the
|
|
95
|
+
`browser` tool launches an engine it **auto-runs `playwright install <browser>`**
|
|
96
|
+
if the binary is missing.
|
|
97
|
+
|
|
98
|
+
To pre-install the binaries (optional, avoids the one-time first-use download):
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
playwright install # all engines
|
|
102
|
+
playwright install chromium # just chromium
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Auto-install is controlled by `DRYHACK_BROWSER_AUTO_INSTALL` (default on); set it
|
|
106
|
+
to `0` to disable and manage browsers yourself.
|
|
107
|
+
|
|
86
108
|
## Run
|
|
87
109
|
|
|
88
110
|
Two transports are supported. Select with `--transport` (argparse).
|
|
@@ -165,12 +187,69 @@ Start the server separately with `dryhack-mcp --transport http`.
|
|
|
165
187
|
| `DRYHACK_COMMAND_TIMEOUT` | `120` | Per-command timeout (seconds) |
|
|
166
188
|
| `DRYHACK_WORKDIR` | cwd | Working directory for commands |
|
|
167
189
|
| `DRYHACK_OUTPUT_LIMIT` | `65536` | Max stdout/stderr bytes captured |
|
|
190
|
+
| `DRYHACK_BROWSER_AUTO_INSTALL` | `1` | Auto-download the browser binary on first use (`browser` tool) |
|
|
191
|
+
| `DRYHACK_BROWSER_INSTALL_TIMEOUT` | `900` | Timeout (s) for the on-demand `playwright install` download |
|
|
168
192
|
| `DRYHACK_HTTP_HOST` | `127.0.0.1` | Default HTTP bind host |
|
|
169
193
|
| `DRYHACK_HTTP_PORT` | `8000` | Default HTTP bind port |
|
|
170
194
|
|
|
171
195
|
> The server stores **no credentials/API keys**. All settings above are
|
|
172
196
|
> operational only.
|
|
173
197
|
|
|
198
|
+
## browser (Playwright automation)
|
|
199
|
+
|
|
200
|
+
Drive a real browser (Chromium/Firefox/WebKit) for web recon and exploitation.
|
|
201
|
+
Run a single `action` (with its relevant params) **or** a `steps` list of
|
|
202
|
+
`{"action": ..., ...}` dicts executed in order within one browser session.
|
|
203
|
+
|
|
204
|
+
Supported actions:
|
|
205
|
+
|
|
206
|
+
- **Navigation:** `goto`/`navigate`, `reload`, `back`, `forward`
|
|
207
|
+
- **Interaction:** `click`, `dblclick`, `fill`, `type`, `press`, `hover`,
|
|
208
|
+
`focus`, `check`, `uncheck`, `select_option`, `upload`, `mouse_click`,
|
|
209
|
+
`keyboard_type`, `scroll`
|
|
210
|
+
- **Waiting:** `wait_for_selector`, `wait_for_timeout`, `wait_for_load_state`,
|
|
211
|
+
`wait_for_url`
|
|
212
|
+
- **JavaScript injection:** `evaluate`/`eval` (with `script` + optional `arg`),
|
|
213
|
+
`evaluate_handle`, `add_init_script` (runs before every document's own
|
|
214
|
+
scripts), `add_script_tag`, `add_style_tag`
|
|
215
|
+
- **Extraction:** `content`, `inner_text`, `inner_html`, `text_content`,
|
|
216
|
+
`get_attribute`, `query_all`, `title`, `url`
|
|
217
|
+
- **Capture:** `screenshot` (`path`, `full_page`), `pdf`
|
|
218
|
+
- **Session/context:** `set_viewport`, `set_extra_headers`, `get_cookies`,
|
|
219
|
+
`set_cookies`, `clear_cookies`, `storage_state`, `emulate_media`
|
|
220
|
+
|
|
221
|
+
Session-level options: `browser_type` (chromium/firefox/webkit), `headless`,
|
|
222
|
+
`user_agent`, `viewport`, `extra_http_headers`, `cookies`, `proxy`, `locale`,
|
|
223
|
+
`timezone`, `geolocation`, `device_scale_factor`, `ignore_https_errors`,
|
|
224
|
+
`bypass_csp`, `java_script_enabled`, `init_scripts`, `slow_mo`, `args`,
|
|
225
|
+
`executable_path`, `timeout`, and `stop_on_error`.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# Single action: run JavaScript in the page
|
|
229
|
+
browser(action="evaluate", url=None, script="() => document.cookie")
|
|
230
|
+
|
|
231
|
+
# Multi-step session: login, inject JS, screenshot
|
|
232
|
+
browser(steps=[
|
|
233
|
+
{"action": "goto", "url": "https://staging.lab.example.com/login"},
|
|
234
|
+
{"action": "fill", "selector": "#user", "value": "admin"},
|
|
235
|
+
{"action": "fill", "selector": "#pass", "value": "s3cret"},
|
|
236
|
+
{"action": "click", "selector": "button[type=submit]"},
|
|
237
|
+
{"action": "wait_for_load_state", "state": "networkidle"},
|
|
238
|
+
{"action": "evaluate", "script": "() => localStorage.getItem('token')"},
|
|
239
|
+
{"action": "screenshot", "path": "/tmp/after-login.png", "full_page": True},
|
|
240
|
+
], headless=True, user_agent="DryHack", extra_http_headers={"X-Test": "1"})
|
|
241
|
+
|
|
242
|
+
# Inject a payload before any page script runs
|
|
243
|
+
browser(action="goto", url="https://target.example/",
|
|
244
|
+
init_script="Object.defineProperty(navigator,'webdriver',{get:()=>false})")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Playwright ships with dryhack-mcp and runs in a dedicated worker thread (the MCP
|
|
248
|
+
server owns the asyncio loop, and Playwright's sync API cannot run inside a live
|
|
249
|
+
loop). The browser binary auto-installs on first use if missing
|
|
250
|
+
(`DRYHACK_BROWSER_AUTO_INSTALL`, default on). The same authorized-use scope
|
|
251
|
+
rules as the other tools apply.
|
|
252
|
+
|
|
174
253
|
## authorize (per-call scope)
|
|
175
254
|
|
|
176
255
|
Pass `scope` directly to the tool; no environment or native scope configuration
|
|
@@ -8,6 +8,7 @@ tooling for **authorized** penetration testing. It exposes four tools:
|
|
|
8
8
|
| `curl` | Raw HTTP interaction for web recon/exploitation |
|
|
9
9
|
| `python` | Run ad-hoc Python snippets for scripted probing |
|
|
10
10
|
| `shell` | Run shell commands (`nmap`, `ffuf`, `nc`, `sqlmap`, …) |
|
|
11
|
+
| `browser` | Full Playwright browser automation: navigation, DOM interaction, JavaScript injection, screenshots/PDF, cookies/headers, multi-step chains |
|
|
11
12
|
| `authorize` | Checks target membership in the per-call `scope` parameter; does not verify permission (no external API, no creds) |
|
|
12
13
|
|
|
13
14
|
> ⚠️ **Legal notice.** Use this only against systems you own or are explicitly
|
|
@@ -40,6 +41,24 @@ For development:
|
|
|
40
41
|
python3 -m pip install -e ".[dev]"
|
|
41
42
|
```
|
|
42
43
|
|
|
44
|
+
### Browser tool (Playwright)
|
|
45
|
+
|
|
46
|
+
Playwright is a **core dependency**, so a plain `pip install dryhack-mcp` /
|
|
47
|
+
`uvx dryhack-mcp` already ships it — no extra needed. The browser *binaries*
|
|
48
|
+
(Chromium/Firefox/WebKit) are not distributed via pip, so the first time the
|
|
49
|
+
`browser` tool launches an engine it **auto-runs `playwright install <browser>`**
|
|
50
|
+
if the binary is missing.
|
|
51
|
+
|
|
52
|
+
To pre-install the binaries (optional, avoids the one-time first-use download):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
playwright install # all engines
|
|
56
|
+
playwright install chromium # just chromium
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Auto-install is controlled by `DRYHACK_BROWSER_AUTO_INSTALL` (default on); set it
|
|
60
|
+
to `0` to disable and manage browsers yourself.
|
|
61
|
+
|
|
43
62
|
## Run
|
|
44
63
|
|
|
45
64
|
Two transports are supported. Select with `--transport` (argparse).
|
|
@@ -122,12 +141,69 @@ Start the server separately with `dryhack-mcp --transport http`.
|
|
|
122
141
|
| `DRYHACK_COMMAND_TIMEOUT` | `120` | Per-command timeout (seconds) |
|
|
123
142
|
| `DRYHACK_WORKDIR` | cwd | Working directory for commands |
|
|
124
143
|
| `DRYHACK_OUTPUT_LIMIT` | `65536` | Max stdout/stderr bytes captured |
|
|
144
|
+
| `DRYHACK_BROWSER_AUTO_INSTALL` | `1` | Auto-download the browser binary on first use (`browser` tool) |
|
|
145
|
+
| `DRYHACK_BROWSER_INSTALL_TIMEOUT` | `900` | Timeout (s) for the on-demand `playwright install` download |
|
|
125
146
|
| `DRYHACK_HTTP_HOST` | `127.0.0.1` | Default HTTP bind host |
|
|
126
147
|
| `DRYHACK_HTTP_PORT` | `8000` | Default HTTP bind port |
|
|
127
148
|
|
|
128
149
|
> The server stores **no credentials/API keys**. All settings above are
|
|
129
150
|
> operational only.
|
|
130
151
|
|
|
152
|
+
## browser (Playwright automation)
|
|
153
|
+
|
|
154
|
+
Drive a real browser (Chromium/Firefox/WebKit) for web recon and exploitation.
|
|
155
|
+
Run a single `action` (with its relevant params) **or** a `steps` list of
|
|
156
|
+
`{"action": ..., ...}` dicts executed in order within one browser session.
|
|
157
|
+
|
|
158
|
+
Supported actions:
|
|
159
|
+
|
|
160
|
+
- **Navigation:** `goto`/`navigate`, `reload`, `back`, `forward`
|
|
161
|
+
- **Interaction:** `click`, `dblclick`, `fill`, `type`, `press`, `hover`,
|
|
162
|
+
`focus`, `check`, `uncheck`, `select_option`, `upload`, `mouse_click`,
|
|
163
|
+
`keyboard_type`, `scroll`
|
|
164
|
+
- **Waiting:** `wait_for_selector`, `wait_for_timeout`, `wait_for_load_state`,
|
|
165
|
+
`wait_for_url`
|
|
166
|
+
- **JavaScript injection:** `evaluate`/`eval` (with `script` + optional `arg`),
|
|
167
|
+
`evaluate_handle`, `add_init_script` (runs before every document's own
|
|
168
|
+
scripts), `add_script_tag`, `add_style_tag`
|
|
169
|
+
- **Extraction:** `content`, `inner_text`, `inner_html`, `text_content`,
|
|
170
|
+
`get_attribute`, `query_all`, `title`, `url`
|
|
171
|
+
- **Capture:** `screenshot` (`path`, `full_page`), `pdf`
|
|
172
|
+
- **Session/context:** `set_viewport`, `set_extra_headers`, `get_cookies`,
|
|
173
|
+
`set_cookies`, `clear_cookies`, `storage_state`, `emulate_media`
|
|
174
|
+
|
|
175
|
+
Session-level options: `browser_type` (chromium/firefox/webkit), `headless`,
|
|
176
|
+
`user_agent`, `viewport`, `extra_http_headers`, `cookies`, `proxy`, `locale`,
|
|
177
|
+
`timezone`, `geolocation`, `device_scale_factor`, `ignore_https_errors`,
|
|
178
|
+
`bypass_csp`, `java_script_enabled`, `init_scripts`, `slow_mo`, `args`,
|
|
179
|
+
`executable_path`, `timeout`, and `stop_on_error`.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
# Single action: run JavaScript in the page
|
|
183
|
+
browser(action="evaluate", url=None, script="() => document.cookie")
|
|
184
|
+
|
|
185
|
+
# Multi-step session: login, inject JS, screenshot
|
|
186
|
+
browser(steps=[
|
|
187
|
+
{"action": "goto", "url": "https://staging.lab.example.com/login"},
|
|
188
|
+
{"action": "fill", "selector": "#user", "value": "admin"},
|
|
189
|
+
{"action": "fill", "selector": "#pass", "value": "s3cret"},
|
|
190
|
+
{"action": "click", "selector": "button[type=submit]"},
|
|
191
|
+
{"action": "wait_for_load_state", "state": "networkidle"},
|
|
192
|
+
{"action": "evaluate", "script": "() => localStorage.getItem('token')"},
|
|
193
|
+
{"action": "screenshot", "path": "/tmp/after-login.png", "full_page": True},
|
|
194
|
+
], headless=True, user_agent="DryHack", extra_http_headers={"X-Test": "1"})
|
|
195
|
+
|
|
196
|
+
# Inject a payload before any page script runs
|
|
197
|
+
browser(action="goto", url="https://target.example/",
|
|
198
|
+
init_script="Object.defineProperty(navigator,'webdriver',{get:()=>false})")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Playwright ships with dryhack-mcp and runs in a dedicated worker thread (the MCP
|
|
202
|
+
server owns the asyncio loop, and Playwright's sync API cannot run inside a live
|
|
203
|
+
loop). The browser binary auto-installs on first use if missing
|
|
204
|
+
(`DRYHACK_BROWSER_AUTO_INSTALL`, default on). The same authorized-use scope
|
|
205
|
+
rules as the other tools apply.
|
|
206
|
+
|
|
131
207
|
## authorize (per-call scope)
|
|
132
208
|
|
|
133
209
|
Pass `scope` directly to the tool; no environment or native scope configuration
|
|
@@ -23,6 +23,22 @@ WORKDIR = _get("DRYHACK_WORKDIR", os.getcwd())
|
|
|
23
23
|
OUTPUT_BYTE_LIMIT = int(_get("DRYHACK_OUTPUT_LIMIT", "65536") or "65536")
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
# ---- Browser tool (Playwright) -----------------------------------------------
|
|
27
|
+
# Auto-download the requested browser binary on first use if it is missing
|
|
28
|
+
# (the binaries are not shipped via pip). Disable by setting to 0/false/no.
|
|
29
|
+
BROWSER_AUTO_INSTALL = _get("DRYHACK_BROWSER_AUTO_INSTALL", "1").lower() not in (
|
|
30
|
+
"0",
|
|
31
|
+
"false",
|
|
32
|
+
"no",
|
|
33
|
+
"off",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Timeout (seconds) for the on-demand `playwright install <browser>` download.
|
|
37
|
+
BROWSER_INSTALL_TIMEOUT = int(
|
|
38
|
+
_get("DRYHACK_BROWSER_INSTALL_TIMEOUT", "900") or "900"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
26
42
|
# ---- Transport ---------------------------------------------------------------
|
|
27
43
|
# Default HTTP bind address when running in http mode.
|
|
28
44
|
HTTP_HOST = _get("DRYHACK_HTTP_HOST", "127.0.0.1")
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""DryHack-MCP server: registers the offensive-security tools over MCP."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
# mcp 1.x
|
|
8
|
+
from mcp.server.fastmcp import FastMCP
|
|
9
|
+
except ModuleNotFoundError: # pragma: no cover - mcp 2.x fallback
|
|
10
|
+
# mcp 2.x renamed FastMCP -> MCPServer
|
|
11
|
+
from mcp.server.mcpserver import MCPServer as FastMCP
|
|
12
|
+
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
|
+
|
|
15
|
+
from . import config
|
|
16
|
+
from .tools.curl_tool import curl as _curl
|
|
17
|
+
from .tools.python_tool import python_run as _python_run
|
|
18
|
+
from .tools.shell_tool import shell as _shell
|
|
19
|
+
from .tools.authorize import authorize as _authorize
|
|
20
|
+
from .tools.browser_tool import browser as _browser
|
|
21
|
+
|
|
22
|
+
mcp = FastMCP("dryhack-mcp")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@mcp.tool()
|
|
26
|
+
def curl(args: str, timeout: Optional[int] = None) -> str:
|
|
27
|
+
"""Run curl for HTTP recon/exploitation.
|
|
28
|
+
|
|
29
|
+
Provide the arguments only (without the leading `curl`), e.g.
|
|
30
|
+
"-skiL https://target/". Returns exit code, stdout and stderr.
|
|
31
|
+
"""
|
|
32
|
+
return _curl(args, timeout=timeout)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@mcp.tool()
|
|
36
|
+
def python(code: str, timeout: Optional[int] = None) -> str:
|
|
37
|
+
"""Execute an ad-hoc Python snippet for scripted probing/exploitation."""
|
|
38
|
+
return _python_run(code, timeout=timeout)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@mcp.tool()
|
|
42
|
+
def shell(command: str, timeout: Optional[int] = None) -> str:
|
|
43
|
+
"""Run a shell command line (nmap, ffuf, nc, sqlmap, ...)."""
|
|
44
|
+
return _shell(command, timeout=timeout)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@mcp.tool()
|
|
48
|
+
def authorize(target: str, request: str, scope: list[str], context: str = "", goal: str = "") -> str:
|
|
49
|
+
"""Check target against caller-supplied scope (hosts/domains/IPs/CIDRs).
|
|
50
|
+
|
|
51
|
+
Scope must reflect the operator's actual engagement scope; never infer it
|
|
52
|
+
from the target just to obtain a match. Membership covers the full range
|
|
53
|
+
of authorized engagement actions for that host (recon, exploitation,
|
|
54
|
+
configuration changes such as standing up or hardening a staging
|
|
55
|
+
deployment) — it is not restricted to read-only assessment. This checks
|
|
56
|
+
membership only, not ownership or legal permission, and does not override
|
|
57
|
+
client safeguards. No environment scope, network calls, credentials, or
|
|
58
|
+
persistent state.
|
|
59
|
+
"""
|
|
60
|
+
return _authorize(target, request, scope=scope, context=context, goal=goal)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@mcp.tool()
|
|
64
|
+
def browser(
|
|
65
|
+
action: Optional[str] = None,
|
|
66
|
+
url: Optional[str] = None,
|
|
67
|
+
steps: Optional[List[Dict[str, Any]]] = None,
|
|
68
|
+
selector: Optional[str] = None,
|
|
69
|
+
text: Optional[str] = None,
|
|
70
|
+
value: Optional[str] = None,
|
|
71
|
+
script: Optional[str] = None,
|
|
72
|
+
arg: Any = None,
|
|
73
|
+
init_script: Optional[str] = None,
|
|
74
|
+
init_scripts: Optional[List[str]] = None,
|
|
75
|
+
key: Optional[str] = None,
|
|
76
|
+
name: Optional[str] = None,
|
|
77
|
+
files: Optional[Any] = None,
|
|
78
|
+
path: Optional[str] = None,
|
|
79
|
+
full_page: bool = False,
|
|
80
|
+
wait_until: Optional[str] = None,
|
|
81
|
+
state: Optional[str] = None,
|
|
82
|
+
browser_type: str = "chromium",
|
|
83
|
+
headless: bool = True,
|
|
84
|
+
timeout: Optional[int] = None,
|
|
85
|
+
user_agent: Optional[str] = None,
|
|
86
|
+
viewport: Optional[Dict[str, int]] = None,
|
|
87
|
+
extra_http_headers: Optional[Dict[str, str]] = None,
|
|
88
|
+
cookies: Optional[List[Dict[str, Any]]] = None,
|
|
89
|
+
proxy: Optional[Dict[str, str]] = None,
|
|
90
|
+
ignore_https_errors: bool = False,
|
|
91
|
+
java_script_enabled: bool = True,
|
|
92
|
+
bypass_csp: bool = False,
|
|
93
|
+
locale: Optional[str] = None,
|
|
94
|
+
timezone: Optional[str] = None,
|
|
95
|
+
geolocation: Optional[Dict[str, float]] = None,
|
|
96
|
+
device_scale_factor: Optional[float] = None,
|
|
97
|
+
stop_on_error: bool = True,
|
|
98
|
+
slow_mo: Optional[int] = None,
|
|
99
|
+
args: Optional[List[str]] = None,
|
|
100
|
+
executable_path: Optional[str] = None,
|
|
101
|
+
) -> str:
|
|
102
|
+
"""Drive a real browser via Playwright for web recon/exploitation.
|
|
103
|
+
|
|
104
|
+
Run a single `action` (with its relevant params) OR a `steps` list of
|
|
105
|
+
`{"action": ..., ...}` dicts executed in order within one browser session.
|
|
106
|
+
|
|
107
|
+
Supported actions cover navigation (`goto`, `reload`, `back`, `forward`),
|
|
108
|
+
interaction (`click`, `dblclick`, `fill`, `type`, `press`, `hover`, `focus`,
|
|
109
|
+
`check`, `uncheck`, `select_option`, `upload`, `mouse_click`,
|
|
110
|
+
`keyboard_type`, `scroll`), waiting (`wait_for_selector`,
|
|
111
|
+
`wait_for_timeout`, `wait_for_load_state`, `wait_for_url`), JavaScript
|
|
112
|
+
injection (`evaluate`/`eval`, `evaluate_handle`, `add_init_script`,
|
|
113
|
+
`add_script_tag`, `add_style_tag`), extraction (`content`, `inner_text`,
|
|
114
|
+
`inner_html`, `text_content`, `get_attribute`, `query_all`, `title`, `url`),
|
|
115
|
+
capture (`screenshot`, `pdf`), and session control (`set_viewport`,
|
|
116
|
+
`set_extra_headers`, `get_cookies`, `set_cookies`, `clear_cookies`,
|
|
117
|
+
`storage_state`, `emulate_media`).
|
|
118
|
+
|
|
119
|
+
Session-level options let you pick `browser_type` (chromium/firefox/webkit),
|
|
120
|
+
toggle `headless`, set a `user_agent`, `viewport`, `extra_http_headers`,
|
|
121
|
+
`cookies`, `proxy`, `locale`, `timezone`, `geolocation`, per-document
|
|
122
|
+
`init_scripts`, and error handling (`stop_on_error`).
|
|
123
|
+
|
|
124
|
+
JavaScript injection: use `evaluate` (with `script` and optional `arg`) to
|
|
125
|
+
run JS in the current page, or `add_init_script`/`init_scripts` to inject JS
|
|
126
|
+
that runs before every document's own scripts.
|
|
127
|
+
|
|
128
|
+
Requires the optional Playwright extra: `pip install 'dryhack-mcp[browser]'`
|
|
129
|
+
then `playwright install`. Same authorized-use scope rules as the other
|
|
130
|
+
tools apply.
|
|
131
|
+
"""
|
|
132
|
+
return _browser(
|
|
133
|
+
action=action,
|
|
134
|
+
url=url,
|
|
135
|
+
steps=steps,
|
|
136
|
+
selector=selector,
|
|
137
|
+
text=text,
|
|
138
|
+
value=value,
|
|
139
|
+
script=script,
|
|
140
|
+
arg=arg,
|
|
141
|
+
init_script=init_script,
|
|
142
|
+
init_scripts=init_scripts,
|
|
143
|
+
key=key,
|
|
144
|
+
name=name,
|
|
145
|
+
files=files,
|
|
146
|
+
path=path,
|
|
147
|
+
full_page=full_page,
|
|
148
|
+
wait_until=wait_until,
|
|
149
|
+
state=state,
|
|
150
|
+
browser_type=browser_type,
|
|
151
|
+
headless=headless,
|
|
152
|
+
timeout=timeout,
|
|
153
|
+
user_agent=user_agent,
|
|
154
|
+
viewport=viewport,
|
|
155
|
+
extra_http_headers=extra_http_headers,
|
|
156
|
+
cookies=cookies,
|
|
157
|
+
proxy=proxy,
|
|
158
|
+
ignore_https_errors=ignore_https_errors,
|
|
159
|
+
java_script_enabled=java_script_enabled,
|
|
160
|
+
bypass_csp=bypass_csp,
|
|
161
|
+
locale=locale,
|
|
162
|
+
timezone=timezone,
|
|
163
|
+
geolocation=geolocation,
|
|
164
|
+
device_scale_factor=device_scale_factor,
|
|
165
|
+
stop_on_error=stop_on_error,
|
|
166
|
+
slow_mo=slow_mo,
|
|
167
|
+
args=args,
|
|
168
|
+
executable_path=executable_path,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def run(
|
|
173
|
+
transport: str = "stdio",
|
|
174
|
+
host: Optional[str] = None,
|
|
175
|
+
port: Optional[int] = None,
|
|
176
|
+
) -> None:
|
|
177
|
+
"""Start the MCP server.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
transport: "stdio" (default) or "http" (streamable HTTP).
|
|
181
|
+
host: Bind address for http transport.
|
|
182
|
+
port: Bind port for http transport.
|
|
183
|
+
"""
|
|
184
|
+
if transport == "http":
|
|
185
|
+
mcp.settings.host = host or config.HTTP_HOST
|
|
186
|
+
mcp.settings.port = port or config.HTTP_PORT
|
|
187
|
+
mcp.run(transport="streamable-http")
|
|
188
|
+
else:
|
|
189
|
+
mcp.run(transport="stdio")
|