odda 0.44.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.
- odda-0.44.0/LICENSE +21 -0
- odda-0.44.0/PKG-INFO +164 -0
- odda-0.44.0/README.md +134 -0
- odda-0.44.0/pyproject.toml +147 -0
- odda-0.44.0/setup.cfg +4 -0
- odda-0.44.0/src/odda/__init__.py +15 -0
- odda-0.44.0/src/odda/__main__.py +6 -0
- odda-0.44.0/src/odda/browser.py +1989 -0
- odda-0.44.0/src/odda/chrome_args.py +141 -0
- odda-0.44.0/src/odda/cli.py +98 -0
- odda-0.44.0/src/odda/coverage.py +213 -0
- odda-0.44.0/src/odda/docs/__init__.py +1 -0
- odda-0.44.0/src/odda/docs/dynamic-analysis.md +151 -0
- odda-0.44.0/src/odda/docs/flows.md +51 -0
- odda-0.44.0/src/odda/docs/proxy-scripts.md +46 -0
- odda-0.44.0/src/odda/docs/recipes.md +306 -0
- odda-0.44.0/src/odda/docs/request-crafting.md +97 -0
- odda-0.44.0/src/odda/docs/userscripts.md +26 -0
- odda-0.44.0/src/odda/flowstore.py +555 -0
- odda-0.44.0/src/odda/logpoint.py +191 -0
- odda-0.44.0/src/odda/mcp.py +1086 -0
- odda-0.44.0/src/odda/proxy.py +143 -0
- odda-0.44.0/src/odda/proxyscript.py +234 -0
- odda-0.44.0/src/odda/request/__init__.py +16 -0
- odda-0.44.0/src/odda/request/api.py +665 -0
- odda-0.44.0/src/odda/request/h1.py +459 -0
- odda-0.44.0/src/odda/request/h2.py +431 -0
- odda-0.44.0/src/odda/request/parsing.py +135 -0
- odda-0.44.0/src/odda/request/response.py +95 -0
- odda-0.44.0/src/odda/request/storage.py +62 -0
- odda-0.44.0/src/odda/request/types.py +73 -0
- odda-0.44.0/src/odda/userscript.py +171 -0
- odda-0.44.0/src/odda/userscripts/default/logpoint-helpers.js +79 -0
- odda-0.44.0/src/odda/wrap.py +467 -0
- odda-0.44.0/src/odda.egg-info/PKG-INFO +164 -0
- odda-0.44.0/src/odda.egg-info/SOURCES.txt +38 -0
- odda-0.44.0/src/odda.egg-info/dependency_links.txt +1 -0
- odda-0.44.0/src/odda.egg-info/entry_points.txt +2 -0
- odda-0.44.0/src/odda.egg-info/requires.txt +11 -0
- odda-0.44.0/src/odda.egg-info/top_level.txt +1 -0
odda-0.44.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zakaria Ounissi
|
|
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.
|
odda-0.44.0/PKG-INFO
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: odda
|
|
3
|
+
Version: 0.44.0
|
|
4
|
+
Summary: Browser automation and HTTP traffic capture for AI agents (MCP server)
|
|
5
|
+
Author-email: Zakaria Ounissi <ounissizakaria@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ounissi-zakaria/odda
|
|
8
|
+
Project-URL: Repository, https://github.com/ounissi-zakaria/odda
|
|
9
|
+
Project-URL: Issues, https://github.com/ounissi-zakaria/odda/issues
|
|
10
|
+
Keywords: browser-automation,http-proxy,mcp,ai-agents,traffic-capture,bug-bounty,pentest,penetration-testing,security-research
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Requires-Python: >=3.14
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: mitmproxy>=11.0.2
|
|
20
|
+
Requires-Dist: patchright==1.62.1
|
|
21
|
+
Requires-Dist: h2>=4.0.0
|
|
22
|
+
Requires-Dist: mcp==2.1.1
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: ruff>=0.15.11; extra == "dev"
|
|
25
|
+
Requires-Dist: hypercorn>=0.18.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest>=9.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=1.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-xdist>=3.6; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# odda
|
|
32
|
+
|
|
33
|
+
`odda` is a toolkit for agent-driven web security research, composable from a single MCP server. It gives an AI agent a real Chrome it can drive and observe, a transparent mitmproxy that writes every flow to disk, and a raw-HTTP send path for smuggling and race conditions.
|
|
34
|
+
|
|
35
|
+
- **Drive Chrome** — open, navigate, run JS, screenshot, and interact with the page by accessibility-tree refs.
|
|
36
|
+
- **Capture every flow** — a transparent proxy writes every request/response to disk, including media bodies browser capture drops.
|
|
37
|
+
- **Observe JS in progress** — wrap functions/properties, plant source logpoints, record block coverage.
|
|
38
|
+
- **Craft raw HTTP** — byte-faithful sends for smuggling, parser-differentials, and races.
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
- Python 3.14+
|
|
43
|
+
- Chrome or Chromium installed and on `PATH`
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv tool install git+https://github.com/ounissi-zakaria/odda.git
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This installs the `odda` command (an MCP server plus one helper subcommand, with `--version` for the version probe). Then register the server with your harness — one MCP config entry, and every odda capability becomes typed tools your agent can call.
|
|
52
|
+
|
|
53
|
+
### Harness configuration
|
|
54
|
+
|
|
55
|
+
All configs spawn the same stdio server: `odda mcp`. One MCP process = one odda session with its own Chrome, proxy, and `.odda/` data dir (created lazily under the process's working directory on first use).
|
|
56
|
+
|
|
57
|
+
**Claude Code** — project `.mcp.json` (or `~/.claude.json` under `mcpServers`):
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"odda": {
|
|
63
|
+
"command": "odda",
|
|
64
|
+
"args": ["mcp"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**OpenCode** — `~/.config/opencode/opencode.json`:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcp": {
|
|
75
|
+
"odda": {
|
|
76
|
+
"type": "local",
|
|
77
|
+
"command": ["odda", "mcp"]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**pi** — install the MCP adapter once (`pi install npm:pi-mcp-adapter`), then add to project `.mcp.json` (same `mcpServers` shape as Claude Code):
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"odda": {
|
|
89
|
+
"command": "odda",
|
|
90
|
+
"args": ["mcp"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Chrome profile (one-time)
|
|
97
|
+
|
|
98
|
+
To give odda's isolated browser sessions a base profile (cookies, extensions, preferences):
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
odda init-chrome-profile
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This opens a visible Chrome window pointed at odda's base profile directory. Log in, install extensions, and set preferences as you want them; close the window when done. odda copies this profile into each isolated browser session opened by `browser_open`. Skip this step to start from a clean profile each time.
|
|
105
|
+
|
|
106
|
+
## Usage
|
|
107
|
+
|
|
108
|
+
Agents drive odda through the MCP tools (`browser_open`, `navigate`, `page_snapshot`, `request_send`, ...). IDs are integers, monotonic within a session, and never reused; every browser/tab tool takes explicit `browser_id` / `tab_id` parameters.
|
|
109
|
+
|
|
110
|
+
A typical session:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
browser_open → {browser_id, tab_id}
|
|
114
|
+
navigate → tab loads https://target.example/
|
|
115
|
+
read .odda/flows/flows.jsonl → every request/response captured on disk
|
|
116
|
+
request_clone + request_send → the Burp Repeater loop, as tools
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
All captured traffic is stored under `.odda/flows/<id>/` — request bytes, response body, headers, timing — plus the append-only `flows.jsonl` index. Grep the index to find flows; read `.odda/flows/<id>/response_body.*` for the body (including image/video/audio/font Content-Types that browser capture drops).
|
|
120
|
+
|
|
121
|
+
## Features
|
|
122
|
+
|
|
123
|
+
### Drive Chrome
|
|
124
|
+
|
|
125
|
+
A real Chrome (via patchright/Playwright), headless by default:
|
|
126
|
+
|
|
127
|
+
- **Open / navigate / eval / wait-for / screenshot** — drive the browser and run JS in the page.
|
|
128
|
+
- **Ref-driven page interaction** — `page_snapshot` returns the accessibility tree with `[ref=eN]` tags; pass the ref to `page_click` / `page_fill` / `page_hover` / `page_upload`. `page_click` and `page_hover` also accept viewport coordinates as a raw trusted event for targets the a11y tree can't name. Cross-iframe is transparent.
|
|
129
|
+
- **Per-browser userscripts** — JS that auto-runs at `document_start` on every navigation, before the page's own scripts.
|
|
130
|
+
- **Dialog blocking** — `alert` / `confirm` / `prompt` / `beforeunload` stay open until handled: the action that opens one returns its details, other tools on that tab wait, and every same-browser result lists open dialogs. Resolve with `dialog_handle` (accept/dismiss, prompt text); a human closing it in a headed window works too.
|
|
131
|
+
|
|
132
|
+
### Capture every flow
|
|
133
|
+
|
|
134
|
+
A transparent mitmproxy sits between Chrome and the network; driving the browser *is* traffic capture. Every request/response is saved under `.odda/flows/<id>/`; the index is `.odda/flows/flows.jsonl`. See the `odda://docs/flows` resource for the file layout and schema.
|
|
135
|
+
|
|
136
|
+
### Observe JS in progress
|
|
137
|
+
|
|
138
|
+
Three lenses, chosen by what you know:
|
|
139
|
+
|
|
140
|
+
- **Wrap** — wrap a named function or property; record each call/access with `this`, `args`, `ret`, and `stack`. Takes effect on the next navigation.
|
|
141
|
+
- **Logpoint** — plant a non-pausing observation at a source `url` + `line` + `col`; the expression is evaluated in the paused frame's scope, so it reads locals by name.
|
|
142
|
+
- **Coverage** — record which code blocks execute across one or more navigations; start, trigger behavior, snapshot or stop.
|
|
143
|
+
|
|
144
|
+
Wrap and Logpoint records wipe on navigation — dump before navigating again. The `odda://docs/dynamic-analysis` resource documents the full surface; `odda://docs/recipes` has a worked example tracing untrusted DOM data to a sink.
|
|
145
|
+
|
|
146
|
+
### Craft raw HTTP
|
|
147
|
+
|
|
148
|
+
Byte-faithful raw HTTP sends, bypassing the browser — the Burp Repeater model, as tools:
|
|
149
|
+
|
|
150
|
+
- **Wire-verbatim HTTP/1.1** — the request file *is* the wire; nothing is re-framed.
|
|
151
|
+
- **HTTP/2 frame-source** — the request file is parsed into H2 frames; a custom line terminator lets a literal CRLF live inside an H2 pseudo-header for downgrade smuggling.
|
|
152
|
+
- **Clone or craft** — `request_clone` copies a captured flow's exact bytes; `request_new` starts an empty file.
|
|
153
|
+
- **Multi-name pipeline** — pass several names to send several requests on one connection (H1 keep-alive, or H2 concurrent stream-multiplex) for same-connection attacks.
|
|
154
|
+
- **Concurrent send** — `repeat` fires N copies of one request concurrently for race conditions and limit-overrun attacks.
|
|
155
|
+
- **`fix_content_length`** — recompute Content-Length after body edits.
|
|
156
|
+
|
|
157
|
+
See the `odda://docs/request-crafting` resource for framing details.
|
|
158
|
+
|
|
159
|
+
## How it works
|
|
160
|
+
|
|
161
|
+
- `odda mcp` is the **only** odda process: a stdio MCP server whose lifespan owns the proxy, the browser manager, and the flow storage. Your harness spawns it per agent session; closing the session tears everything down.
|
|
162
|
+
- The remaining CLI surface is helpers, not the automation surface: `odda init-chrome-profile` (interactive, human-run) and `odda --version`/`-V`.
|
|
163
|
+
- Project state lives in `.odda/` under the MCP process's working directory: `flows/`, `requests/`, `browsers/`. It's created lazily on the first state-producing call, not when the server boots.
|
|
164
|
+
- Errors from tools are odda's messages verbatim (as tool errors); unanticipated crashes log their traceback to the server's stderr, which the harness captures.
|
odda-0.44.0/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# odda
|
|
2
|
+
|
|
3
|
+
`odda` is a toolkit for agent-driven web security research, composable from a single MCP server. It gives an AI agent a real Chrome it can drive and observe, a transparent mitmproxy that writes every flow to disk, and a raw-HTTP send path for smuggling and race conditions.
|
|
4
|
+
|
|
5
|
+
- **Drive Chrome** — open, navigate, run JS, screenshot, and interact with the page by accessibility-tree refs.
|
|
6
|
+
- **Capture every flow** — a transparent proxy writes every request/response to disk, including media bodies browser capture drops.
|
|
7
|
+
- **Observe JS in progress** — wrap functions/properties, plant source logpoints, record block coverage.
|
|
8
|
+
- **Craft raw HTTP** — byte-faithful sends for smuggling, parser-differentials, and races.
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
- Python 3.14+
|
|
13
|
+
- Chrome or Chromium installed and on `PATH`
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv tool install git+https://github.com/ounissi-zakaria/odda.git
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This installs the `odda` command (an MCP server plus one helper subcommand, with `--version` for the version probe). Then register the server with your harness — one MCP config entry, and every odda capability becomes typed tools your agent can call.
|
|
22
|
+
|
|
23
|
+
### Harness configuration
|
|
24
|
+
|
|
25
|
+
All configs spawn the same stdio server: `odda mcp`. One MCP process = one odda session with its own Chrome, proxy, and `.odda/` data dir (created lazily under the process's working directory on first use).
|
|
26
|
+
|
|
27
|
+
**Claude Code** — project `.mcp.json` (or `~/.claude.json` under `mcpServers`):
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"odda": {
|
|
33
|
+
"command": "odda",
|
|
34
|
+
"args": ["mcp"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**OpenCode** — `~/.config/opencode/opencode.json`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcp": {
|
|
45
|
+
"odda": {
|
|
46
|
+
"type": "local",
|
|
47
|
+
"command": ["odda", "mcp"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**pi** — install the MCP adapter once (`pi install npm:pi-mcp-adapter`), then add to project `.mcp.json` (same `mcpServers` shape as Claude Code):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"odda": {
|
|
59
|
+
"command": "odda",
|
|
60
|
+
"args": ["mcp"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Chrome profile (one-time)
|
|
67
|
+
|
|
68
|
+
To give odda's isolated browser sessions a base profile (cookies, extensions, preferences):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
odda init-chrome-profile
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This opens a visible Chrome window pointed at odda's base profile directory. Log in, install extensions, and set preferences as you want them; close the window when done. odda copies this profile into each isolated browser session opened by `browser_open`. Skip this step to start from a clean profile each time.
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
Agents drive odda through the MCP tools (`browser_open`, `navigate`, `page_snapshot`, `request_send`, ...). IDs are integers, monotonic within a session, and never reused; every browser/tab tool takes explicit `browser_id` / `tab_id` parameters.
|
|
79
|
+
|
|
80
|
+
A typical session:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
browser_open → {browser_id, tab_id}
|
|
84
|
+
navigate → tab loads https://target.example/
|
|
85
|
+
read .odda/flows/flows.jsonl → every request/response captured on disk
|
|
86
|
+
request_clone + request_send → the Burp Repeater loop, as tools
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
All captured traffic is stored under `.odda/flows/<id>/` — request bytes, response body, headers, timing — plus the append-only `flows.jsonl` index. Grep the index to find flows; read `.odda/flows/<id>/response_body.*` for the body (including image/video/audio/font Content-Types that browser capture drops).
|
|
90
|
+
|
|
91
|
+
## Features
|
|
92
|
+
|
|
93
|
+
### Drive Chrome
|
|
94
|
+
|
|
95
|
+
A real Chrome (via patchright/Playwright), headless by default:
|
|
96
|
+
|
|
97
|
+
- **Open / navigate / eval / wait-for / screenshot** — drive the browser and run JS in the page.
|
|
98
|
+
- **Ref-driven page interaction** — `page_snapshot` returns the accessibility tree with `[ref=eN]` tags; pass the ref to `page_click` / `page_fill` / `page_hover` / `page_upload`. `page_click` and `page_hover` also accept viewport coordinates as a raw trusted event for targets the a11y tree can't name. Cross-iframe is transparent.
|
|
99
|
+
- **Per-browser userscripts** — JS that auto-runs at `document_start` on every navigation, before the page's own scripts.
|
|
100
|
+
- **Dialog blocking** — `alert` / `confirm` / `prompt` / `beforeunload` stay open until handled: the action that opens one returns its details, other tools on that tab wait, and every same-browser result lists open dialogs. Resolve with `dialog_handle` (accept/dismiss, prompt text); a human closing it in a headed window works too.
|
|
101
|
+
|
|
102
|
+
### Capture every flow
|
|
103
|
+
|
|
104
|
+
A transparent mitmproxy sits between Chrome and the network; driving the browser *is* traffic capture. Every request/response is saved under `.odda/flows/<id>/`; the index is `.odda/flows/flows.jsonl`. See the `odda://docs/flows` resource for the file layout and schema.
|
|
105
|
+
|
|
106
|
+
### Observe JS in progress
|
|
107
|
+
|
|
108
|
+
Three lenses, chosen by what you know:
|
|
109
|
+
|
|
110
|
+
- **Wrap** — wrap a named function or property; record each call/access with `this`, `args`, `ret`, and `stack`. Takes effect on the next navigation.
|
|
111
|
+
- **Logpoint** — plant a non-pausing observation at a source `url` + `line` + `col`; the expression is evaluated in the paused frame's scope, so it reads locals by name.
|
|
112
|
+
- **Coverage** — record which code blocks execute across one or more navigations; start, trigger behavior, snapshot or stop.
|
|
113
|
+
|
|
114
|
+
Wrap and Logpoint records wipe on navigation — dump before navigating again. The `odda://docs/dynamic-analysis` resource documents the full surface; `odda://docs/recipes` has a worked example tracing untrusted DOM data to a sink.
|
|
115
|
+
|
|
116
|
+
### Craft raw HTTP
|
|
117
|
+
|
|
118
|
+
Byte-faithful raw HTTP sends, bypassing the browser — the Burp Repeater model, as tools:
|
|
119
|
+
|
|
120
|
+
- **Wire-verbatim HTTP/1.1** — the request file *is* the wire; nothing is re-framed.
|
|
121
|
+
- **HTTP/2 frame-source** — the request file is parsed into H2 frames; a custom line terminator lets a literal CRLF live inside an H2 pseudo-header for downgrade smuggling.
|
|
122
|
+
- **Clone or craft** — `request_clone` copies a captured flow's exact bytes; `request_new` starts an empty file.
|
|
123
|
+
- **Multi-name pipeline** — pass several names to send several requests on one connection (H1 keep-alive, or H2 concurrent stream-multiplex) for same-connection attacks.
|
|
124
|
+
- **Concurrent send** — `repeat` fires N copies of one request concurrently for race conditions and limit-overrun attacks.
|
|
125
|
+
- **`fix_content_length`** — recompute Content-Length after body edits.
|
|
126
|
+
|
|
127
|
+
See the `odda://docs/request-crafting` resource for framing details.
|
|
128
|
+
|
|
129
|
+
## How it works
|
|
130
|
+
|
|
131
|
+
- `odda mcp` is the **only** odda process: a stdio MCP server whose lifespan owns the proxy, the browser manager, and the flow storage. Your harness spawns it per agent session; closing the session tears everything down.
|
|
132
|
+
- The remaining CLI surface is helpers, not the automation surface: `odda init-chrome-profile` (interactive, human-run) and `odda --version`/`-V`.
|
|
133
|
+
- Project state lives in `.odda/` under the MCP process's working directory: `flows/`, `requests/`, `browsers/`. It's created lazily on the first state-producing call, not when the server boots.
|
|
134
|
+
- Errors from tools are odda's messages verbatim (as tool errors); unanticipated crashes log their traceback to the server's stderr, which the harness captures.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "odda"
|
|
3
|
+
version = "0.44.0"
|
|
4
|
+
description = "Browser automation and HTTP traffic capture for AI agents (MCP server)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [{ name = "Zakaria Ounissi", email = "ounissizakaria@gmail.com" }]
|
|
10
|
+
keywords = ["browser-automation", "http-proxy", "mcp", "ai-agents", "traffic-capture", "bug-bounty", "pentest", "penetration-testing", "security-research"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
14
|
+
"Programming Language :: Python :: 3.14",
|
|
15
|
+
"Topic :: Internet :: WWW/HTTP :: Browsers",
|
|
16
|
+
"Topic :: Security",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"mitmproxy>=11.0.2",
|
|
20
|
+
"patchright==1.62.1",
|
|
21
|
+
"h2>=4.0.0",
|
|
22
|
+
"mcp==2.1.1",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
odda = "odda.cli:main"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/ounissi-zakaria/odda"
|
|
30
|
+
Repository = "https://github.com/ounissi-zakaria/odda"
|
|
31
|
+
Issues = "https://github.com/ounissi-zakaria/odda/issues"
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"ruff>=0.15.11",
|
|
36
|
+
"hypercorn>=0.18.0",
|
|
37
|
+
"pytest>=9.0",
|
|
38
|
+
"pytest-asyncio>=1.0",
|
|
39
|
+
"pytest-xdist>=3.6",
|
|
40
|
+
]
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-dir]
|
|
45
|
+
"" = "src"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
odda = ["docs/*.md", "userscripts/default/*.js"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
target-version = "py311"
|
|
52
|
+
line-length = 88
|
|
53
|
+
indent-width = 4
|
|
54
|
+
exclude = ["tests/e2e/*.sh"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
select = [
|
|
58
|
+
"E",
|
|
59
|
+
"W",
|
|
60
|
+
"F",
|
|
61
|
+
"I",
|
|
62
|
+
"N",
|
|
63
|
+
"D",
|
|
64
|
+
"S",
|
|
65
|
+
"B",
|
|
66
|
+
"C4",
|
|
67
|
+
"UP",
|
|
68
|
+
"SIM",
|
|
69
|
+
"TCH",
|
|
70
|
+
"PTH",
|
|
71
|
+
"ERA",
|
|
72
|
+
"RUF",
|
|
73
|
+
"ARG",
|
|
74
|
+
"PL",
|
|
75
|
+
"PERF",
|
|
76
|
+
"FURB",
|
|
77
|
+
"TRY",
|
|
78
|
+
"EM",
|
|
79
|
+
"RET",
|
|
80
|
+
"SLF",
|
|
81
|
+
"TID",
|
|
82
|
+
"RSE",
|
|
83
|
+
"FBT",
|
|
84
|
+
"COM",
|
|
85
|
+
"C90",
|
|
86
|
+
"INP",
|
|
87
|
+
"PIE",
|
|
88
|
+
"T20",
|
|
89
|
+
"PGH",
|
|
90
|
+
"FAST",
|
|
91
|
+
]
|
|
92
|
+
ignore = [
|
|
93
|
+
"D100",
|
|
94
|
+
"D104",
|
|
95
|
+
"COM812",
|
|
96
|
+
"ISC001",
|
|
97
|
+
"T201",
|
|
98
|
+
"EM101",
|
|
99
|
+
"EM102",
|
|
100
|
+
"TRY003",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[tool.ruff.lint.pydocstyle]
|
|
104
|
+
convention = "google"
|
|
105
|
+
|
|
106
|
+
[tool.ruff.lint.mccabe]
|
|
107
|
+
max-complexity = 10
|
|
108
|
+
|
|
109
|
+
[tool.ruff.lint.pylint]
|
|
110
|
+
max-args = 6
|
|
111
|
+
max-branches = 15
|
|
112
|
+
max-returns = 8
|
|
113
|
+
max-statements = 60
|
|
114
|
+
max-public-methods = 25
|
|
115
|
+
|
|
116
|
+
[tool.ruff.format]
|
|
117
|
+
quote-style = "double"
|
|
118
|
+
indent-style = "space"
|
|
119
|
+
skip-magic-trailing-comma = false
|
|
120
|
+
line-ending = "auto"
|
|
121
|
+
|
|
122
|
+
[tool.ruff.lint.isort]
|
|
123
|
+
known-first-party = ["odda"]
|
|
124
|
+
combine-as-imports = true
|
|
125
|
+
split-on-trailing-comma = false
|
|
126
|
+
|
|
127
|
+
[tool.ruff.lint.per-file-ignores]
|
|
128
|
+
"src/odda/cli.py" = ["PLC0415"]
|
|
129
|
+
"src/odda/flowstore.py" = ["PLW0603", "PLR0913"]
|
|
130
|
+
"src/odda/request/*.py" = ["PLR0913", "PLR2004", "C901", "PLR0912", "PLR0915", "TC003"]
|
|
131
|
+
"src/odda/wrap.py" = ["E501"]
|
|
132
|
+
"src/odda/logpoint.py" = ["E501", "PLR0913"]
|
|
133
|
+
# F401's odda_session flagging is the import-shadow pattern (each test's
|
|
134
|
+
# fixture parameter redefines the imported factory; ruff can't model
|
|
135
|
+
# pytest fixtures) — covered by the F811 ignore; modules use it in-body.
|
|
136
|
+
"tests/e2e/conftest.py" = ["D107", "D403", "D205", "D209", "D202", "D402", "PLC0415", "S101", "S603", "S607", "SLF001", "SIM300", "E501", "E741", "TC003", "RUF100", "W292"]
|
|
137
|
+
"tests/e2e/test_*.py" = ["S101", "PLR2004", "E501", "E741", "D415", "D403", "D205", "D209", "PLC0415", "RUF100", "RUF003", "RUF059", "SIM300", "W292", "INP001", "S105", "S311", "F811", "F401", "ARG001", "D301", "W605", "TC003"]
|
|
138
|
+
"tests/e2e/fixtures/dyn_asgi.py" = ["INP001", "ARG001", "S108"]
|
|
139
|
+
"tests/e2e/fixtures/echo_path_h2server.py" = ["INP001", "S110"]
|
|
140
|
+
"tests/e2e/fixtures/ws_header_h2server.py" = ["INP001", "S110"]
|
|
141
|
+
"tests/e2e/fixtures/h2_cookie_client.py" = ["INP001"]
|
|
142
|
+
"tests/e2e/fixtures/slow_load_server.py" = ["INP001"]
|
|
143
|
+
"tests/e2e/fixtures/close_after_first_server.py" = ["INP001", "S110"]
|
|
144
|
+
"tests/e2e/fixtures/close_without_response_server.py" = ["INP001", "S110"]
|
|
145
|
+
[build-system]
|
|
146
|
+
requires = ["setuptools>=77"]
|
|
147
|
+
build-backend = "setuptools.build_meta"
|
odda-0.44.0/setup.cfg
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""odda - Browser automation and HTTP traffic capture CLI for AI agents."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.44.0"
|
|
4
|
+
|
|
5
|
+
#: Playwright ``page.goto`` lifecycle events accepted by navigate, in firing
|
|
6
|
+
#: order. The single source of truth for the MCP server's ``navigate``
|
|
7
|
+
#: param validation and error message; the browser layer passes the value
|
|
8
|
+
#: through to Playwright. Lives here so importing it stays lightweight —
|
|
9
|
+
#: no pull-in of the patchright-backed ``odda.browser`` module.
|
|
10
|
+
NAVIGATE_WAIT_UNTIL_EVENTS: tuple[str, ...] = (
|
|
11
|
+
"commit",
|
|
12
|
+
"domcontentloaded",
|
|
13
|
+
"load",
|
|
14
|
+
"networkidle",
|
|
15
|
+
)
|