pypproxy 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.
- pypproxy-0.1.0/.github/dependabot.yml +19 -0
- pypproxy-0.1.0/.github/workflows/ci.yml +47 -0
- pypproxy-0.1.0/.github/workflows/docs.yml +29 -0
- pypproxy-0.1.0/.github/workflows/publish.yml +46 -0
- pypproxy-0.1.0/.gitignore +24 -0
- pypproxy-0.1.0/.pre-commit-config.yaml +19 -0
- pypproxy-0.1.0/LICENSE +21 -0
- pypproxy-0.1.0/Makefile +37 -0
- pypproxy-0.1.0/PKG-INFO +19 -0
- pypproxy-0.1.0/README.md +14 -0
- pypproxy-0.1.0/docs/api.md +152 -0
- pypproxy-0.1.0/docs/architecture.md +109 -0
- pypproxy-0.1.0/docs/configuration.md +78 -0
- pypproxy-0.1.0/docs/getting-started.md +111 -0
- pypproxy-0.1.0/docs/index.md +35 -0
- pypproxy-0.1.0/docs/protocols.md +115 -0
- pypproxy-0.1.0/docs/replay.md +89 -0
- pypproxy-0.1.0/docs/rule-engine.md +126 -0
- pypproxy-0.1.0/docs/scripting.md +96 -0
- pypproxy-0.1.0/docs/web-ui.md +157 -0
- pypproxy-0.1.0/main.py +212 -0
- pypproxy-0.1.0/mkdocs.yml +62 -0
- pypproxy-0.1.0/pypproxy/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/api/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/api/server.py +427 -0
- pypproxy-0.1.0/pypproxy/bulk/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/bulk/sender.py +97 -0
- pypproxy-0.1.0/pypproxy/cert/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/cert/ca.py +144 -0
- pypproxy-0.1.0/pypproxy/cert/client_cert.py +65 -0
- pypproxy-0.1.0/pypproxy/codec.py +176 -0
- pypproxy-0.1.0/pypproxy/config/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/config/config.py +106 -0
- pypproxy-0.1.0/pypproxy/dns/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/dns/server.py +149 -0
- pypproxy-0.1.0/pypproxy/exporter/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/exporter/exporter.py +122 -0
- pypproxy-0.1.0/pypproxy/exporter/importer.py +169 -0
- pypproxy-0.1.0/pypproxy/graphql/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/graphql/detector.py +76 -0
- pypproxy-0.1.0/pypproxy/graphql/introspection.py +217 -0
- pypproxy-0.1.0/pypproxy/graphql/modifier.py +98 -0
- pypproxy-0.1.0/pypproxy/graphql/schema_store.py +33 -0
- pypproxy-0.1.0/pypproxy/intercept/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/intercept/manager.py +142 -0
- pypproxy-0.1.0/pypproxy/interceptor/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/interceptor/interceptor.py +172 -0
- pypproxy-0.1.0/pypproxy/proto/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/proto/grpc.py +48 -0
- pypproxy-0.1.0/pypproxy/proto/mqtt.py +119 -0
- pypproxy-0.1.0/pypproxy/proto/ws.py +120 -0
- pypproxy-0.1.0/pypproxy/proto/ws_intercept.py +117 -0
- pypproxy-0.1.0/pypproxy/proxy/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/proxy/proxy.py +407 -0
- pypproxy-0.1.0/pypproxy/replay/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/replay/replay.py +77 -0
- pypproxy-0.1.0/pypproxy/rule/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/rule/rule.py +198 -0
- pypproxy-0.1.0/pypproxy/scan/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/scan/scanner.py +296 -0
- pypproxy-0.1.0/pypproxy/script/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/script/engine.py +49 -0
- pypproxy-0.1.0/pypproxy/security/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/security/header_checker.py +308 -0
- pypproxy-0.1.0/pypproxy/security/int_overflow.py +193 -0
- pypproxy-0.1.0/pypproxy/security/jwt_checker.py +273 -0
- pypproxy-0.1.0/pypproxy/security/plugin.py +152 -0
- pypproxy-0.1.0/pypproxy/security/randomness.py +165 -0
- pypproxy-0.1.0/pypproxy/store/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/store/db.py +189 -0
- pypproxy-0.1.0/pypproxy/store/filter_parser.py +181 -0
- pypproxy-0.1.0/pypproxy/store/fts.py +105 -0
- pypproxy-0.1.0/pypproxy/store/models.py +81 -0
- pypproxy-0.1.0/pypproxy/store/scope.py +63 -0
- pypproxy-0.1.0/pypproxy/store/store.py +120 -0
- pypproxy-0.1.0/pypproxy/ui/__init__.py +0 -0
- pypproxy-0.1.0/pypproxy/ui/app.py +386 -0
- pypproxy-0.1.0/pypproxy/ui/bulk_sender_ui.py +125 -0
- pypproxy-0.1.0/pypproxy/ui/cui.py +162 -0
- pypproxy-0.1.0/pypproxy/ui/detail.py +179 -0
- pypproxy-0.1.0/pypproxy/ui/diff_view.py +118 -0
- pypproxy-0.1.0/pypproxy/ui/graphql_tab.py +265 -0
- pypproxy-0.1.0/pypproxy/ui/import_tab.py +136 -0
- pypproxy-0.1.0/pypproxy/ui/intercept_dialog.py +74 -0
- pypproxy-0.1.0/pypproxy/ui/resender.py +140 -0
- pypproxy-0.1.0/pypproxy/ui/scan_tab.py +98 -0
- pypproxy-0.1.0/pypproxy/ui/security_tab.py +356 -0
- pypproxy-0.1.0/pypproxy/ui/settings.py +413 -0
- pypproxy-0.1.0/pypproxy/ui/theme.py +59 -0
- pypproxy-0.1.0/pyproject.toml +68 -0
- pypproxy-0.1.0/tests/__init__.py +0 -0
- pypproxy-0.1.0/tests/test_advanced.py +287 -0
- pypproxy-0.1.0/tests/test_bulk.py +60 -0
- pypproxy-0.1.0/tests/test_cert.py +71 -0
- pypproxy-0.1.0/tests/test_codec.py +113 -0
- pypproxy-0.1.0/tests/test_exporter.py +102 -0
- pypproxy-0.1.0/tests/test_filter_parser.py +97 -0
- pypproxy-0.1.0/tests/test_graphql.py +249 -0
- pypproxy-0.1.0/tests/test_interceptor.py +64 -0
- pypproxy-0.1.0/tests/test_mqtt.py +73 -0
- pypproxy-0.1.0/tests/test_rule.py +115 -0
- pypproxy-0.1.0/tests/test_security.py +240 -0
- pypproxy-0.1.0/tests/test_store.py +113 -0
- pypproxy-0.1.0/uv.lock +2239 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: pip
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
day: monday
|
|
8
|
+
open-pull-requests-limit: 5
|
|
9
|
+
labels:
|
|
10
|
+
- dependencies
|
|
11
|
+
|
|
12
|
+
- package-ecosystem: github-actions
|
|
13
|
+
directory: /
|
|
14
|
+
schedule:
|
|
15
|
+
interval: weekly
|
|
16
|
+
day: monday
|
|
17
|
+
open-pull-requests-limit: 5
|
|
18
|
+
labels:
|
|
19
|
+
- dependencies
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, feat/**]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v6
|
|
14
|
+
|
|
15
|
+
- uses: astral-sh/setup-uv@v7
|
|
16
|
+
with:
|
|
17
|
+
version: latest
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
run: uv python install 3.11
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: uv sync
|
|
24
|
+
|
|
25
|
+
- name: Lint (ruff)
|
|
26
|
+
run: uv run ruff check pypproxy/
|
|
27
|
+
|
|
28
|
+
- name: Format check (ruff)
|
|
29
|
+
run: uv run ruff format --check pypproxy/
|
|
30
|
+
|
|
31
|
+
test:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v6
|
|
35
|
+
|
|
36
|
+
- uses: astral-sh/setup-uv@v7
|
|
37
|
+
with:
|
|
38
|
+
version: latest
|
|
39
|
+
|
|
40
|
+
- name: Set up Python
|
|
41
|
+
run: uv python install 3.11
|
|
42
|
+
|
|
43
|
+
- name: Install dependencies
|
|
44
|
+
run: uv sync
|
|
45
|
+
|
|
46
|
+
- name: Run tests
|
|
47
|
+
run: uv run pytest tests/ -v --tb=short
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Deploy docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- 'docs/**'
|
|
8
|
+
- 'mkdocs.yml'
|
|
9
|
+
- '.github/workflows/docs.yml'
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
deploy:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-python@v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.x'
|
|
24
|
+
|
|
25
|
+
- name: Install MkDocs
|
|
26
|
+
run: pip install mkdocs-material
|
|
27
|
+
|
|
28
|
+
- name: Deploy to GitHub Pages
|
|
29
|
+
run: mkdocs gh-deploy --force
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write # for trusted publishing
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6
|
|
17
|
+
|
|
18
|
+
- uses: astral-sh/setup-uv@v7
|
|
19
|
+
with:
|
|
20
|
+
version: latest
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
run: uv python install 3.11
|
|
24
|
+
|
|
25
|
+
- name: Build distribution
|
|
26
|
+
run: uv build
|
|
27
|
+
|
|
28
|
+
- uses: actions/upload-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
name: dist
|
|
31
|
+
path: dist/
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment:
|
|
37
|
+
name: pypi
|
|
38
|
+
url: https://pypi.org/project/paxy/
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/download-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: dist
|
|
43
|
+
path: dist/
|
|
44
|
+
|
|
45
|
+
- name: Publish to PyPI
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v4.6.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-toml
|
|
9
|
+
- id: check-merge-conflict
|
|
10
|
+
- id: check-added-large-files
|
|
11
|
+
args: [--maxkb=500]
|
|
12
|
+
- id: debug-statements
|
|
13
|
+
|
|
14
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
15
|
+
rev: v0.4.10
|
|
16
|
+
hooks:
|
|
17
|
+
- id: ruff
|
|
18
|
+
args: [--fix]
|
|
19
|
+
- id: ruff-format
|
pypproxy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kuma
|
|
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.
|
pypproxy-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
.PHONY: install run dev web-install web-build web-dev test lint clean docs-dev docs-build
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
uv sync
|
|
5
|
+
|
|
6
|
+
run:
|
|
7
|
+
uv run python main.py
|
|
8
|
+
|
|
9
|
+
gui:
|
|
10
|
+
uv run python main.py --mode gui
|
|
11
|
+
|
|
12
|
+
cui:
|
|
13
|
+
uv run python main.py --mode cui
|
|
14
|
+
|
|
15
|
+
web-install:
|
|
16
|
+
cd web && npm install
|
|
17
|
+
|
|
18
|
+
web-build:
|
|
19
|
+
cd web && npm run build
|
|
20
|
+
|
|
21
|
+
web-dev:
|
|
22
|
+
cd web && npm run dev
|
|
23
|
+
|
|
24
|
+
test:
|
|
25
|
+
uv run pytest tests/ -v
|
|
26
|
+
|
|
27
|
+
lint:
|
|
28
|
+
uv run ruff check paxy/
|
|
29
|
+
|
|
30
|
+
clean:
|
|
31
|
+
rm -rf __pycache__ paxy/**/__pycache__ .pytest_cache site/ web/dist/
|
|
32
|
+
|
|
33
|
+
docs-dev:
|
|
34
|
+
mkdocs serve
|
|
35
|
+
|
|
36
|
+
docs-build:
|
|
37
|
+
mkdocs build
|
pypproxy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pypproxy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MITM HTTP/HTTPS proxy for inspecting and modifying traffic
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
8
|
+
Requires-Dist: brotli>=1.1.0
|
|
9
|
+
Requires-Dist: cbor2>=5.6.0
|
|
10
|
+
Requires-Dist: cryptography>=48.0.0
|
|
11
|
+
Requires-Dist: fastapi>=0.111.0
|
|
12
|
+
Requires-Dist: httpx>=0.27.0
|
|
13
|
+
Requires-Dist: httpx[http2]>=0.27.0
|
|
14
|
+
Requires-Dist: msgpack>=1.1.0
|
|
15
|
+
Requires-Dist: nicegui>=3.12.1
|
|
16
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
17
|
+
Requires-Dist: rich>=15.0.0
|
|
18
|
+
Requires-Dist: uvicorn[standard]>=0.30.0
|
|
19
|
+
Requires-Dist: websockets>=16.0
|
pypproxy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# pypproxy
|
|
2
|
+
|
|
3
|
+
MITM HTTP/HTTPS proxy for inspecting and modifying traffic.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/ykus4/pypproxy/actions/workflows/ci.yml)
|
|
6
|
+
[](https://pypi.org/project/pypproxy/)
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install pypproxy
|
|
10
|
+
pypproxy # GUI mode → http://localhost:8081
|
|
11
|
+
pypproxy --mode cui # terminal UI
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
See the **[docs](https://ykus4.github.io/pypproxy/)** for setup, CA installation, and feature guides.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
The API server runs on `http://localhost:8081` by default.
|
|
4
|
+
|
|
5
|
+
## Traffic
|
|
6
|
+
|
|
7
|
+
### `GET /api/traffic`
|
|
8
|
+
|
|
9
|
+
List captured entries.
|
|
10
|
+
|
|
11
|
+
**Query parameters:** `offset`, `limit`, `method`, `host`, `search`, `protocol`
|
|
12
|
+
|
|
13
|
+
### `GET /api/traffic/{id}`
|
|
14
|
+
|
|
15
|
+
Get a single entry by ID.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
### `GET /api/rules` · `POST /api/rules` · `PUT /api/rules/{id}` · `DELETE /api/rules/{id}`
|
|
22
|
+
|
|
23
|
+
CRUD for intercept rules.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Replay
|
|
28
|
+
|
|
29
|
+
### `POST /api/replay`
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{"entry_id": 42, "options": {"override_host": "staging.example.com", "count": 1}}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Bulk Sender
|
|
38
|
+
|
|
39
|
+
### `POST /api/bulk`
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{"entry_id": 42, "mode": "payloads", "payloads": [{"label": "p1", "body": "..."}], "concurrency": 10}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`mode`: `"payloads"` or `"race"`
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Active Scan
|
|
50
|
+
|
|
51
|
+
### `POST /api/scan`
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{"entry_id": 42, "categories": ["xss", "sqli", "cmdi", "ssti", "path_traversal"], "concurrency": 5}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Export / Import
|
|
60
|
+
|
|
61
|
+
| Endpoint | Description |
|
|
62
|
+
|----------|-------------|
|
|
63
|
+
| `GET /api/export/json` | Export all entries + rules as JSON |
|
|
64
|
+
| `GET /api/export/har` | Export all entries as HAR 1.2 |
|
|
65
|
+
| `POST /api/import/har` | Import entries from HAR body |
|
|
66
|
+
| `POST /api/import/json` | Import entries from paxy JSON body |
|
|
67
|
+
| `POST /api/import/rules` | Import rules from JSON body |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Full-text Search
|
|
72
|
+
|
|
73
|
+
### `GET /api/search?q=keyword&limit=50`
|
|
74
|
+
|
|
75
|
+
Search across host, path, request body, response body, and headers using SQLite FTS5.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Scope
|
|
80
|
+
|
|
81
|
+
### `GET /api/scope`
|
|
82
|
+
|
|
83
|
+
Returns `{"enabled": bool, "rules": [...]}`.
|
|
84
|
+
|
|
85
|
+
### `POST /api/scope`
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{"enabled": true, "add": {"pattern": "*.example.com", "mode": "glob"}}
|
|
89
|
+
{"remove": "*.example.com"}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## GraphQL
|
|
95
|
+
|
|
96
|
+
### `POST /api/graphql/introspect`
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{"url": "https://api.example.com/graphql", "headers": {"Authorization": "Bearer token"}}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `GET /api/graphql/schemas`
|
|
103
|
+
|
|
104
|
+
List cached schemas: `[{"host": "api.example.com", "query_type": "Query", ...}]`
|
|
105
|
+
|
|
106
|
+
### `GET /api/graphql/schema/{host}`
|
|
107
|
+
|
|
108
|
+
Full schema object for a host.
|
|
109
|
+
|
|
110
|
+
### `DELETE /api/graphql/schema/{host}`
|
|
111
|
+
|
|
112
|
+
Remove cached schema. Returns `204`.
|
|
113
|
+
|
|
114
|
+
### `POST /api/graphql/replay`
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{"entry_id": 42, "query": "query { user { id } }", "variables": {"id": "123"}}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Misc
|
|
123
|
+
|
|
124
|
+
### `POST /api/clear`
|
|
125
|
+
|
|
126
|
+
Delete all captured entries. Returns `204`.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## WebSocket
|
|
131
|
+
|
|
132
|
+
### `GET /ws`
|
|
133
|
+
|
|
134
|
+
Upgrade to WebSocket. Pushes `Entry` JSON objects in real time.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Schemas
|
|
139
|
+
|
|
140
|
+
### Entry
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
{
|
|
144
|
+
"id": int, "created_at": str,
|
|
145
|
+
"method": str, "scheme": str, "host": str, "path": str, "query": str,
|
|
146
|
+
"req_headers": dict, "req_body": str, # base64
|
|
147
|
+
"status_code": int, "resp_headers": dict, "resp_body": str, # base64
|
|
148
|
+
"duration_ms": int, "protocol": str,
|
|
149
|
+
"tags": list[str], "modified": bool, "color": str,
|
|
150
|
+
"graphql_operation": str, "graphql_op_type": str
|
|
151
|
+
}
|
|
152
|
+
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
7
|
+
│ Client (browser / mobile app) │
|
|
8
|
+
└────────────────────────┬─────────────────────────────────────┘
|
|
9
|
+
│ HTTP / CONNECT
|
|
10
|
+
┌────────────────────────▼─────────────────────────────────────┐
|
|
11
|
+
│ paxy/proxy/proxy.py (port :8080) │
|
|
12
|
+
│ asyncio TCP server │
|
|
13
|
+
│ ├─ HTTP → intercept → forward upstream (httpx, HTTP/2) │
|
|
14
|
+
│ ├─ CONNECT → TLS termination (MITM) │
|
|
15
|
+
│ │ ├─ HTTP/HTTPS → intercept → forward upstream │
|
|
16
|
+
│ │ ├─ WebSocket → paxy/proto/ws.py │
|
|
17
|
+
│ │ ├─ gRPC → paxy/proto/grpc.py │
|
|
18
|
+
│ │ └─ MQTT → paxy/proto/mqtt.py │
|
|
19
|
+
│ └─ ignored hosts → raw TCP tunnel (passthrough) │
|
|
20
|
+
└─────────────┬────────────────────────┬───────────────────────┘
|
|
21
|
+
│ │
|
|
22
|
+
┌─────────────▼──────┐ ┌────────────▼──────────────────────┐
|
|
23
|
+
│ paxy/cert/ca.py │ │ paxy/interceptor/ │
|
|
24
|
+
│ CA + per-host TLS │ │ apply rules, record entries │
|
|
25
|
+
│ SSL Context cache │ └────────────┬───────────────────────┘
|
|
26
|
+
│ │ │
|
|
27
|
+
│ paxy/cert/ │ ┌────────────▼───────────────────────┐
|
|
28
|
+
│ client_cert.py │ │ paxy/store/store.py │
|
|
29
|
+
│ Mutual TLS certs │ │ in-memory store + SQLite persist │
|
|
30
|
+
└────────────────────┘ │ asyncio pub/sub │
|
|
31
|
+
└────────────┬───────────────────────┘
|
|
32
|
+
│
|
|
33
|
+
┌────────────────────────────┼──────────────────────┐
|
|
34
|
+
│ │ │
|
|
35
|
+
┌─────────▼──────────┐ ┌─────────────▼─────────┐ ┌────────▼──────────┐
|
|
36
|
+
│ paxy/api/ │ │ paxy/ui/app.py │ │ paxy/ui/cui.py │
|
|
37
|
+
│ FastAPI REST API │ │ NiceGUI 4-tab UI │ │ rich terminal UI │
|
|
38
|
+
│ + WebSocket /ws │ │ Traffic/Resender/ │ │ (CUI mode) │
|
|
39
|
+
│ Bulk/Export APIs │ │ Bulk/Diff │ └───────────────────┘
|
|
40
|
+
└────────────────────┘ └─────────────────────────┘
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Package overview
|
|
44
|
+
|
|
45
|
+
| Package | Responsibility |
|
|
46
|
+
|---------|----------------|
|
|
47
|
+
| `paxy/proxy` | asyncio TCP server; HTTP forwarding; TLS MITM for CONNECT; raw tunnel for ignored hosts |
|
|
48
|
+
| `paxy/cert/ca` | CA certificate generation; per-host SSL Context cache |
|
|
49
|
+
| `paxy/cert/client_cert` | Client certificate management for mutual TLS |
|
|
50
|
+
| `paxy/interceptor` | Apply rules to requests and responses; record entries in the store |
|
|
51
|
+
| `paxy/intercept` | Manual intercept manager; pause requests for user review |
|
|
52
|
+
| `paxy/rule` | Rule evaluation engine; condition matching; priority ordering |
|
|
53
|
+
| `paxy/store/store` | Thread-safe in-memory traffic store; asyncio pub/sub |
|
|
54
|
+
| `paxy/store/db` | SQLite persistence via aiosqlite; load/save entries |
|
|
55
|
+
| `paxy/store/filter_parser` | Filter expression parser (`host == x && method == POST`) |
|
|
56
|
+
| `paxy/api` | FastAPI REST endpoints, WebSocket streaming, bulk/export APIs |
|
|
57
|
+
| `paxy/ui/app` | NiceGUI 4-tab browser UI (Traffic, Resender, Bulk Sender, Diff) |
|
|
58
|
+
| `paxy/ui/settings` | Settings page (rules, SSL passthrough, DNS, ports, client certs) |
|
|
59
|
+
| `paxy/ui/detail` | Request/response detail panel with body view selector |
|
|
60
|
+
| `paxy/ui/resender` | Resender tab — edit and re-send requests |
|
|
61
|
+
| `paxy/ui/bulk_sender_ui` | Bulk Sender tab — parallel payload sending and race testing |
|
|
62
|
+
| `paxy/ui/diff_view` | Diff tab — unified diff between two captured entries |
|
|
63
|
+
| `paxy/ui/intercept_dialog` | Intercept dialog — pause, edit, forward or drop requests |
|
|
64
|
+
| `paxy/ui/cui` | rich terminal UI (CUI mode) |
|
|
65
|
+
| `paxy/proto/ws` | WebSocket frame relay and logging |
|
|
66
|
+
| `paxy/proto/grpc` | gRPC length-prefix frame decoding |
|
|
67
|
+
| `paxy/proto/mqtt` | MQTT frame decoding and detection |
|
|
68
|
+
| `paxy/script` | Python script engine; `on_request` / `on_response` hooks |
|
|
69
|
+
| `paxy/replay` | Async HTTP replay and parallel fuzzing via httpx |
|
|
70
|
+
| `paxy/bulk` | Bulk sender and race condition test runner |
|
|
71
|
+
| `paxy/dns` | Built-in DNS server with domain spoofing |
|
|
72
|
+
| `paxy/exporter` | JSON/HAR export and rule import/export |
|
|
73
|
+
| `paxy/codec` | Content-encoding decode (gzip/br/deflate); binary format decode (Protobuf/MessagePack/CBOR) |
|
|
74
|
+
| `paxy/config` | YAML config loading |
|
|
75
|
+
|
|
76
|
+
## Key design decisions
|
|
77
|
+
|
|
78
|
+
### asyncio TCP server
|
|
79
|
+
|
|
80
|
+
The proxy is a raw `asyncio.start_server` TCP server that parses HTTP manually.
|
|
81
|
+
This lets a single connection handle HTTP/1.1 keep-alive, CONNECT tunnels, WebSocket upgrades, and MQTT detection without switching servers mid-connection.
|
|
82
|
+
|
|
83
|
+
### TLS termination with `loop.start_tls()`
|
|
84
|
+
|
|
85
|
+
After responding `200 Connection Established` to a CONNECT request, paxy calls `loop.start_tls()` to upgrade the existing asyncio transport to TLS server-side. Per-host certificates are cached as `ssl.SSLContext` objects.
|
|
86
|
+
|
|
87
|
+
### SQLite persistence via aiosqlite
|
|
88
|
+
|
|
89
|
+
All captured traffic is stored in memory for fast access. Writes to SQLite are fire-and-forget via `asyncio.run_coroutine_threadsafe`. On startup, `store.load_from_db()` restores prior sessions. The DB path defaults to `~/.paxy/paxy.db`.
|
|
90
|
+
|
|
91
|
+
### Store pub/sub
|
|
92
|
+
|
|
93
|
+
The `Store` maintains a list of `asyncio.Queue` subscribers. The proxy calls `loop.call_soon_threadsafe` to push entries into queues from the proxy coroutine. The UI polls each queue to receive live updates without blocking.
|
|
94
|
+
|
|
95
|
+
### HTTP/2
|
|
96
|
+
|
|
97
|
+
All upstream requests use `httpx` with `http2=True`. httpx negotiates HTTP/2 via ALPN where the server supports it and falls back to HTTP/1.1 transparently.
|
|
98
|
+
|
|
99
|
+
### Filter expression engine
|
|
100
|
+
|
|
101
|
+
The filter bar in the UI accepts a structured expression parsed by `paxy/store/filter_parser.py`. The parser tokenizes `field op value` conditions and evaluates them with AND/OR short-circuit logic against `Entry` objects in memory.
|
|
102
|
+
|
|
103
|
+
### Binary format detection
|
|
104
|
+
|
|
105
|
+
`paxy/codec.py` implements `sniff_content_type()` which combines Content-Type inspection with a JSON parse attempt and a binary entropy heuristic to guess the best display mode. Protobuf decoding uses wire-type heuristics without requiring a `.proto` schema.
|
|
106
|
+
|
|
107
|
+
### GUI / CUI startup
|
|
108
|
+
|
|
109
|
+
In **GUI mode**, `ui.run()` owns the event loop and the proxy is launched via `nicegui_app.on_startup`. In **CUI mode**, `asyncio.run()` owns the loop and `asyncio.gather` runs the proxy, uvicorn API server, and rich TUI concurrently.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
paxy can be configured via CLI flags or a YAML file. CLI flags take precedence over the config file.
|
|
4
|
+
|
|
5
|
+
## YAML config file
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv run python main.py --config paxy.yaml
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Full example
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
proxy:
|
|
15
|
+
addr: "0.0.0.0"
|
|
16
|
+
port: 8080
|
|
17
|
+
# Hosts to pass through without MITM (certificate pinning, internal services, etc.)
|
|
18
|
+
ignore:
|
|
19
|
+
- pinned.example.com
|
|
20
|
+
- internal.corp
|
|
21
|
+
# Maximum body size to capture, in bytes
|
|
22
|
+
max_body: 1048576 # 1 MB
|
|
23
|
+
|
|
24
|
+
ca:
|
|
25
|
+
cert_path: /custom/ca-cert.pem
|
|
26
|
+
key_path: /custom/ca-key.pem
|
|
27
|
+
|
|
28
|
+
ui:
|
|
29
|
+
addr: "0.0.0.0"
|
|
30
|
+
port: 8081
|
|
31
|
+
|
|
32
|
+
script:
|
|
33
|
+
path: /path/to/script.py
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Field reference
|
|
37
|
+
|
|
38
|
+
### `proxy`
|
|
39
|
+
|
|
40
|
+
| Key | Type | Default | Description |
|
|
41
|
+
|-----|------|---------|-------------|
|
|
42
|
+
| `addr` | string | `0.0.0.0` | Proxy listen address |
|
|
43
|
+
| `port` | int | `8080` | Proxy port |
|
|
44
|
+
| `ignore` | list | `[]` | Hosts to tunnel without MITM |
|
|
45
|
+
| `max_body` | int | `1048576` | Max body bytes to capture |
|
|
46
|
+
|
|
47
|
+
### `ca`
|
|
48
|
+
|
|
49
|
+
| Key | Type | Default | Description |
|
|
50
|
+
|-----|------|---------|-------------|
|
|
51
|
+
| `cert_path` | string | `~/.paxy/ca-cert.pem` | CA certificate path |
|
|
52
|
+
| `key_path` | string | `~/.paxy/ca-key.pem` | CA private key path |
|
|
53
|
+
|
|
54
|
+
### `ui`
|
|
55
|
+
|
|
56
|
+
| Key | Type | Default | Description |
|
|
57
|
+
|-----|------|---------|-------------|
|
|
58
|
+
| `addr` | string | `0.0.0.0` | Web UI / API listen address |
|
|
59
|
+
| `port` | int | `8081` | Web UI / API port |
|
|
60
|
+
|
|
61
|
+
### `script`
|
|
62
|
+
|
|
63
|
+
| Key | Type | Default | Description |
|
|
64
|
+
|-----|------|---------|-------------|
|
|
65
|
+
| `path` | string | — | Python script path |
|
|
66
|
+
|
|
67
|
+
## CLI flags
|
|
68
|
+
|
|
69
|
+
| Flag | Default | Description |
|
|
70
|
+
|------|---------|-------------|
|
|
71
|
+
| `--mode` | `gui` | UI mode: `gui` or `cui` |
|
|
72
|
+
| `--addr` | `0.0.0.0` | Proxy listen address |
|
|
73
|
+
| `--port` | `8080` | Proxy port |
|
|
74
|
+
| `--ui-addr` | `0.0.0.0` | Web UI / API address |
|
|
75
|
+
| `--ui-port` | `8081` | Web UI / API port |
|
|
76
|
+
| `--config` | — | Config file path |
|
|
77
|
+
| `--script` | — | Python script path |
|
|
78
|
+
| `--ca-dir` | `~/.paxy` | Directory to store CA cert and key |
|