llm-mitm-proxy 2026.9.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.
- llm_mitm_proxy-2026.9.0/.devcontainer/devcontainer.json +12 -0
- llm_mitm_proxy-2026.9.0/.dockerignore +9 -0
- llm_mitm_proxy-2026.9.0/.github/workflows/checks.yml +64 -0
- llm_mitm_proxy-2026.9.0/.github/workflows/deploy-docker.yml +65 -0
- llm_mitm_proxy-2026.9.0/.github/workflows/prune-ghcr.yml +55 -0
- llm_mitm_proxy-2026.9.0/.github/workflows/publish-pypi.yml +49 -0
- llm_mitm_proxy-2026.9.0/.gitignore +12 -0
- llm_mitm_proxy-2026.9.0/Dockerfile +48 -0
- llm_mitm_proxy-2026.9.0/LICENSE.md +21 -0
- llm_mitm_proxy-2026.9.0/PKG-INFO +116 -0
- llm_mitm_proxy-2026.9.0/README.md +93 -0
- llm_mitm_proxy-2026.9.0/docker-compose.yml +11 -0
- llm_mitm_proxy-2026.9.0/docs/CONVERSATION.md +377003 -0
- llm_mitm_proxy-2026.9.0/docs/PLAN.md +662 -0
- llm_mitm_proxy-2026.9.0/docs/screenshot.png +0 -0
- llm_mitm_proxy-2026.9.0/eslint.config.mjs +24 -0
- llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/PKG-INFO +116 -0
- llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/SOURCES.txt +65 -0
- llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/dependency_links.txt +1 -0
- llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/entry_points.txt +2 -0
- llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/requires.txt +7 -0
- llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/scm_file_list.json +57 -0
- llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/scm_version.json +8 -0
- llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/top_level.txt +1 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/__init__.py +10 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/api/__init__.py +1 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/api/ui.py +162 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/app.py +346 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/config.py +60 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/dissectors/__init__.py +10 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/dissectors/base.py +75 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/dissectors/generic.py +35 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/dissectors/openai.py +109 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/dump.py +62 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/hub.py +164 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/logconf.py +63 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/model/__init__.py +1 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/model/conversation.py +98 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/model/ir.py +48 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/proxy/__init__.py +1 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/proxy/pipeline.py +718 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/proxy/router.py +20 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/proxy/sse.py +108 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/proxy/util.py +19 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/store/__init__.py +1 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/store/memory.py +126 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/tests/__init__.py +1 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/tests/helpers.py +89 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/tests/mock_upstream.py +221 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_cli.py +77 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_dissectors.py +77 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_proxy.py +287 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_shutdown.py +116 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_ui_api.py +246 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_ws.py +396 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/web/app.js +90 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/web/favicon.svg +5 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/web/index.html +69 -0
- llm_mitm_proxy-2026.9.0/llm_proxy/web/styles.css +1 -0
- llm_mitm_proxy-2026.9.0/package-lock.json +1485 -0
- llm_mitm_proxy-2026.9.0/package.json +25 -0
- llm_mitm_proxy-2026.9.0/pyproject.toml +63 -0
- llm_mitm_proxy-2026.9.0/setup.cfg +4 -0
- llm_mitm_proxy-2026.9.0/ui/app.js +970 -0
- llm_mitm_proxy-2026.9.0/ui/favicon.svg +5 -0
- llm_mitm_proxy-2026.9.0/ui/index.html +69 -0
- llm_mitm_proxy-2026.9.0/ui/styles.css +458 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llm-mitm-proxy",
|
|
3
|
+
"image": "mcr.microsoft.com/devcontainers/python:3.14",
|
|
4
|
+
"features": {
|
|
5
|
+
"ghcr.io/devcontainers/features/node:1": {
|
|
6
|
+
"version": "lts"
|
|
7
|
+
},
|
|
8
|
+
"ghcr.io/devcontainers/features/git:1": {}
|
|
9
|
+
},
|
|
10
|
+
"forwardPorts": [8081, 9090],
|
|
11
|
+
"postCreateCommand": "pip install -e '.[dev]' && npm install && npm run build"
|
|
12
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: Code Quality Checks
|
|
2
|
+
permissions:
|
|
3
|
+
contents: read
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: ["main"]
|
|
8
|
+
pull_request:
|
|
9
|
+
|
|
10
|
+
# Kill existing jobs
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
style:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
- uses: actions/setup-python@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.14"
|
|
23
|
+
- run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
pip install black isort
|
|
26
|
+
- run: black --check --diff llm_proxy
|
|
27
|
+
- run: isort --check-only --diff llm_proxy
|
|
28
|
+
- uses: actions/setup-node@v6
|
|
29
|
+
with:
|
|
30
|
+
node-version: "22"
|
|
31
|
+
cache: npm
|
|
32
|
+
- run: npm ci
|
|
33
|
+
- run: npm run verify:js
|
|
34
|
+
|
|
35
|
+
run-unittest:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
strategy:
|
|
38
|
+
max-parallel: 1
|
|
39
|
+
fail-fast: true
|
|
40
|
+
matrix:
|
|
41
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
42
|
+
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v6
|
|
45
|
+
with:
|
|
46
|
+
# History/tags required to generate version info
|
|
47
|
+
fetch-depth: 0
|
|
48
|
+
- uses: actions/setup-python@v6
|
|
49
|
+
with:
|
|
50
|
+
python-version: ${{ matrix.python-version }}
|
|
51
|
+
cache: "pip"
|
|
52
|
+
- run: python -c "import sys; print(sys.version)"
|
|
53
|
+
# Build the UI so the in-package serving + packaging tests run (they
|
|
54
|
+
# skip when llm_proxy/web is absent).
|
|
55
|
+
- uses: actions/setup-node@v6
|
|
56
|
+
with:
|
|
57
|
+
node-version: "22"
|
|
58
|
+
cache: npm
|
|
59
|
+
- run: npm ci
|
|
60
|
+
- run: npm run build
|
|
61
|
+
- run: |
|
|
62
|
+
python -m pip install --upgrade pip
|
|
63
|
+
pip install --editable .
|
|
64
|
+
- run: python -m unittest
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: Publish Docker Images
|
|
2
|
+
permissions:
|
|
3
|
+
contents: read
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: ["main"]
|
|
8
|
+
tags: ["*"]
|
|
9
|
+
|
|
10
|
+
pull_request:
|
|
11
|
+
branches: ["main"]
|
|
12
|
+
|
|
13
|
+
# Kill existing jobs
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
env:
|
|
19
|
+
REGISTRY: ghcr.io
|
|
20
|
+
IMAGE_NAME: mill1000/llm-mitm-proxy
|
|
21
|
+
# No linux/arm/v7: esbuild (the UI build stage) has no armv7 binary.
|
|
22
|
+
PLATFORMS: linux/amd64,linux/arm64
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
build-and-push-image:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
packages: write
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v6
|
|
33
|
+
with:
|
|
34
|
+
# History/tags required to generate version info
|
|
35
|
+
fetch-depth: 0
|
|
36
|
+
|
|
37
|
+
- uses: docker/metadata-action@v6
|
|
38
|
+
id: meta
|
|
39
|
+
with:
|
|
40
|
+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
41
|
+
# Generate tags for latest commit, version tags and PRs
|
|
42
|
+
tags: |
|
|
43
|
+
type=edge
|
|
44
|
+
type=pep440,pattern={{version}}
|
|
45
|
+
type=ref,event=pr
|
|
46
|
+
|
|
47
|
+
- uses: docker/setup-qemu-action@v4
|
|
48
|
+
with:
|
|
49
|
+
platforms: ${{ env.PLATFORMS }}
|
|
50
|
+
|
|
51
|
+
- uses: docker/setup-buildx-action@v4
|
|
52
|
+
|
|
53
|
+
- uses: docker/login-action@v4
|
|
54
|
+
with:
|
|
55
|
+
registry: ${{ env.REGISTRY }}
|
|
56
|
+
username: ${{ github.actor }}
|
|
57
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
58
|
+
|
|
59
|
+
- uses: docker/build-push-action@v7
|
|
60
|
+
with:
|
|
61
|
+
context: .
|
|
62
|
+
platforms: ${{ env.PLATFORMS }}
|
|
63
|
+
push: true
|
|
64
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
65
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Prune GHCR
|
|
2
|
+
permissions:
|
|
3
|
+
contents: read
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
pull_request:
|
|
7
|
+
types: [closed]
|
|
8
|
+
|
|
9
|
+
schedule:
|
|
10
|
+
- cron: "20 4 * * 0"
|
|
11
|
+
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
env:
|
|
15
|
+
CONTAINER_NAME: llm-mitm-proxy
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
prune-pr-images:
|
|
19
|
+
if: github.event_name == 'pull_request'
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
packages: write
|
|
23
|
+
steps:
|
|
24
|
+
- uses: vlaurin/action-ghcr-prune@v0.6.0
|
|
25
|
+
with:
|
|
26
|
+
container: ${{ env.CONTAINER_NAME }}
|
|
27
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
28
|
+
prune-tags-regexes: ^pr-${{github.event.pull_request.number}}
|
|
29
|
+
# dry-run: true
|
|
30
|
+
|
|
31
|
+
prune-old-images:
|
|
32
|
+
if: github.event_name != 'pull_request'
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
permissions:
|
|
35
|
+
packages: write
|
|
36
|
+
steps:
|
|
37
|
+
- uses: vlaurin/action-ghcr-prune@v0.6.0
|
|
38
|
+
with:
|
|
39
|
+
container: ${{ env.CONTAINER_NAME }}
|
|
40
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
41
|
+
# Keep PRs, CalVer releases, latest and edge
|
|
42
|
+
keep-tags-regexes: |
|
|
43
|
+
^pr-
|
|
44
|
+
^\d{4}\.\d{2}\.\d{2}
|
|
45
|
+
keep-tags: |
|
|
46
|
+
edge
|
|
47
|
+
latest
|
|
48
|
+
# dry-run: true
|
|
49
|
+
|
|
50
|
+
- uses: dataaxiom/ghcr-cleanup-action@v1
|
|
51
|
+
with:
|
|
52
|
+
package: ${{ env.CONTAINER_NAME }}
|
|
53
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
54
|
+
# dry-run: true
|
|
55
|
+
validate: true
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Publish Python Package
|
|
2
|
+
permissions:
|
|
3
|
+
contents: read
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
release:
|
|
7
|
+
types: [published]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-package:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v6
|
|
14
|
+
with:
|
|
15
|
+
# History/tags required to generate version info
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
- uses: actions/setup-python@v6
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.14"
|
|
20
|
+
# The wheel ships the WebUI as in-package build output (llm_proxy/web),
|
|
21
|
+
# so the UI must be built before the wheel.
|
|
22
|
+
- uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: "22"
|
|
25
|
+
cache: npm
|
|
26
|
+
- run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
pip install build
|
|
29
|
+
- run: npm ci
|
|
30
|
+
- run: npm run build
|
|
31
|
+
- run: python -m build
|
|
32
|
+
- uses: actions/upload-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist
|
|
36
|
+
|
|
37
|
+
publish-package:
|
|
38
|
+
needs: build-package
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
permissions:
|
|
41
|
+
id-token: write # Mandatory for trusted publishing
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/download-artifact@v8
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist
|
|
47
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
48
|
+
# with:
|
|
49
|
+
# repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# UI build
|
|
2
|
+
FROM node:alpine AS ui
|
|
3
|
+
|
|
4
|
+
WORKDIR /src
|
|
5
|
+
COPY package.json package-lock.json ./
|
|
6
|
+
RUN npm ci
|
|
7
|
+
COPY ui ./ui
|
|
8
|
+
RUN npm run build
|
|
9
|
+
|
|
10
|
+
# Backend build
|
|
11
|
+
FROM python:3.14-alpine AS build
|
|
12
|
+
|
|
13
|
+
RUN apk add --no-cache --update git
|
|
14
|
+
RUN pip install --no-cache-dir build
|
|
15
|
+
|
|
16
|
+
ARG VERSION
|
|
17
|
+
ENV SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION}
|
|
18
|
+
|
|
19
|
+
WORKDIR /app
|
|
20
|
+
COPY . .
|
|
21
|
+
COPY --from=ui /src/llm_proxy/web llm_proxy/web
|
|
22
|
+
RUN python -m build --wheel
|
|
23
|
+
|
|
24
|
+
# Runtime
|
|
25
|
+
FROM alpine:3.24
|
|
26
|
+
|
|
27
|
+
RUN apk add --no-cache --update python3 pipx tini
|
|
28
|
+
|
|
29
|
+
ENV PYTHONDONTWRITEBYTECODE=1
|
|
30
|
+
ENV PYTHONUNBUFFERED=1
|
|
31
|
+
|
|
32
|
+
ENV PIPX_HOME=/opt/pipx
|
|
33
|
+
ENV PIPX_BIN_DIR=/usr/bin
|
|
34
|
+
|
|
35
|
+
COPY --from=build /app/dist/llm_mitm_proxy-*.whl /tmp/
|
|
36
|
+
RUN pipx install /tmp/llm_mitm_proxy-*.whl && rm /tmp/llm_mitm_proxy-*.whl
|
|
37
|
+
|
|
38
|
+
RUN adduser -D -u 1000 -h /dev/null -s /sbin/nologin appuser
|
|
39
|
+
USER appuser
|
|
40
|
+
|
|
41
|
+
EXPOSE 8081 9090
|
|
42
|
+
|
|
43
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s \
|
|
44
|
+
CMD python3 -c "import urllib.request;urllib.request.urlopen('http://127.0.0.1:9090/health', timeout=8)"
|
|
45
|
+
|
|
46
|
+
ENTRYPOINT ["/sbin/tini", "--"]
|
|
47
|
+
|
|
48
|
+
CMD ["/bin/sh", "-c", "llm-mitm-proxy ${UPSTREAM_BASE_URL:+$UPSTREAM_BASE_URL} ${UPSTREAM_API_KEY:+--upstream-api-key $UPSTREAM_API_KEY} ${LISTEN_HOST:+--host $LISTEN_HOST} ${PROXY_PORT:+--proxy-port $PROXY_PORT} ${WEB_PORT:+--web-port $WEB_PORT} ${LOG_LEVEL:+--log-level $LOG_LEVEL}"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tucker Kern
|
|
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,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llm-mitm-proxy
|
|
3
|
+
Version: 2026.9.0
|
|
4
|
+
Summary: A simple transparent MITM proxy for LLM APIs with a live Web UI
|
|
5
|
+
Project-URL: Repository, https://github.com/mill1000/llm-mitm-proxy
|
|
6
|
+
Project-URL: Issues, https://github.com/mill1000/llm-mitm-proxy/issues
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.md
|
|
16
|
+
Requires-Dist: fastapi>=0.110
|
|
17
|
+
Requires-Dist: uvicorn[standard]>=0.29
|
|
18
|
+
Requires-Dist: httpx2>=2
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: isort>=5; extra == "dev"
|
|
21
|
+
Requires-Dist: black>=24; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# llm-mitm-proxy
|
|
25
|
+
A simple transparent MITM proxy for LLM APIs with a live Web UI for inspecting, replaying, and exporting "conversations" between clients and an upstream server.
|
|
26
|
+
|
|
27
|
+
**Trusted networks or testing only**
|
|
28
|
+
There is no authentication, no SSL/TLS termination, or other "reverse proxy" feature set. Its primary intent is to provide visibility between an agent and the server.
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
## Client Identification
|
|
33
|
+
Clients are identified by their source IP, user agent, and API key (if provided), which determines which "conversation" traffic appears under in the Web UI. An API key is not required, IP and user agent are enough to tell many clients apart, but an API key can be used as a further split for clients sharing the same IP, and user agent.
|
|
34
|
+
|
|
35
|
+
## Dissectors
|
|
36
|
+
The proxy forwards all traffic verbatim and decodes requests and responses for the Web UI with per-request dissectors that match on method + path.
|
|
37
|
+
|
|
38
|
+
Supported dissectors:
|
|
39
|
+
- `openai`: OpenAI-compatible chat completions
|
|
40
|
+
- `generic`: everything else, captured as raw data
|
|
41
|
+
|
|
42
|
+
## Vibe Warning
|
|
43
|
+
This project was entirely **vibe coded** with Qwen 3.8 27B and the Zed Agent on local hardware.
|
|
44
|
+
|
|
45
|
+
Work proceeded milestone by milestone according to the [PLAN.md](./docs/PLAN.md) with design, key decisions, and open questions. Occasional updates were made to the plan as direction shifted. Each milestone was tested against a llama.cpp server by a human before committed.
|
|
46
|
+
|
|
47
|
+
The full agent conversation log is preserved in [CONVERSATION.md](./docs/CONVERSATION.md).
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
### Docker Compose
|
|
51
|
+
```yaml
|
|
52
|
+
services:
|
|
53
|
+
llm-mitm-proxy:
|
|
54
|
+
image: ghcr.io/mill1000/llm-mitm-proxy:latest
|
|
55
|
+
restart: unless-stopped
|
|
56
|
+
ports:
|
|
57
|
+
- "8081:8081" # LLM API
|
|
58
|
+
- "9090:9090" # Web UI
|
|
59
|
+
environment:
|
|
60
|
+
UPSTREAM_BASE_URL: "http://<your-llamacpp>:8080"
|
|
61
|
+
extra_hosts:
|
|
62
|
+
- "host.docker.internal:host-gateway"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Docker
|
|
66
|
+
```bash
|
|
67
|
+
docker run -d --name llm-mitm-proxy -p 8081:8081 -p 9090:9090 -e UPSTREAM_BASE_URL=http://<your-llamacpp>:8080 ghcr.io/mill1000/llm-mitm-proxy:latest
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## pipx/uvx
|
|
71
|
+
```bash
|
|
72
|
+
# uvx
|
|
73
|
+
uvx llm-mitm-proxy http://<your-llamacpp>:8080
|
|
74
|
+
|
|
75
|
+
# pipx
|
|
76
|
+
pipx install llm-mitm-proxy
|
|
77
|
+
llm-mitm-proxy http://<your-llamacpp>:8080
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
1. Start the proxy
|
|
82
|
+
2. Point an agent at the proxy
|
|
83
|
+
3. Open the Web UI and look at the requests go brrr.
|
|
84
|
+
|
|
85
|
+
See [Configuration](#configuration) for more information on specifying an upstream API key, changing ports, and other options.
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
### Command Line
|
|
89
|
+
```
|
|
90
|
+
usage: llm-mitm-proxy [-h] [--version] [--host HOST] [--proxy-port PORT] [--web-port PORT]
|
|
91
|
+
[--upstream-api-key KEY] [--log-level LEVEL] [--ui-dir DIR]
|
|
92
|
+
[UPSTREAM_BASE_URL]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Arg | Notes |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `UPSTREAM_BASE_URL` (positional) | upstream base URL (default `http://host.docker.internal:8080`) |
|
|
98
|
+
| `--host` | listen host for both listeners (default `0.0.0.0`) |
|
|
99
|
+
| `--proxy-port` | proxy listener port (default `8081`, the transparent catch-all; llama.cpp's own default is `8080`) |
|
|
100
|
+
| `--web-port` | WebUI + `/api/*` + `/ws` + `/health` listener port (default `9090`) |
|
|
101
|
+
| `--upstream-api-key` | optional server-side fallback key, injected only when a client sends no key |
|
|
102
|
+
| `--log-level` | app loggers, incl. the `llm_proxy.ws` connection trace at `debug` (connect/focus/disconnect) |
|
|
103
|
+
| `--ui-dir` | static UI directory (default: the in-package `llm_proxy/web` build) |
|
|
104
|
+
| `--help` / `--version` | usage / package version |
|
|
105
|
+
|
|
106
|
+
### Docker Environment Variables
|
|
107
|
+
|
|
108
|
+
The Docker image maps the following environment variables to command line options:
|
|
109
|
+
| Var | CLI arg |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `UPSTREAM_BASE_URL` | positional upstream |
|
|
112
|
+
| `UPSTREAM_API_KEY` | `--upstream-api-key` |
|
|
113
|
+
| `LISTEN_HOST` | `--host` |
|
|
114
|
+
| `PROXY_PORT` | `--proxy-port` |
|
|
115
|
+
| `WEB_PORT` | `--web-port` |
|
|
116
|
+
| `LOG_LEVEL` | `--log-level` |
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# llm-mitm-proxy
|
|
2
|
+
A simple transparent MITM proxy for LLM APIs with a live Web UI for inspecting, replaying, and exporting "conversations" between clients and an upstream server.
|
|
3
|
+
|
|
4
|
+
**Trusted networks or testing only**
|
|
5
|
+
There is no authentication, no SSL/TLS termination, or other "reverse proxy" feature set. Its primary intent is to provide visibility between an agent and the server.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Client Identification
|
|
10
|
+
Clients are identified by their source IP, user agent, and API key (if provided), which determines which "conversation" traffic appears under in the Web UI. An API key is not required, IP and user agent are enough to tell many clients apart, but an API key can be used as a further split for clients sharing the same IP, and user agent.
|
|
11
|
+
|
|
12
|
+
## Dissectors
|
|
13
|
+
The proxy forwards all traffic verbatim and decodes requests and responses for the Web UI with per-request dissectors that match on method + path.
|
|
14
|
+
|
|
15
|
+
Supported dissectors:
|
|
16
|
+
- `openai`: OpenAI-compatible chat completions
|
|
17
|
+
- `generic`: everything else, captured as raw data
|
|
18
|
+
|
|
19
|
+
## Vibe Warning
|
|
20
|
+
This project was entirely **vibe coded** with Qwen 3.8 27B and the Zed Agent on local hardware.
|
|
21
|
+
|
|
22
|
+
Work proceeded milestone by milestone according to the [PLAN.md](./docs/PLAN.md) with design, key decisions, and open questions. Occasional updates were made to the plan as direction shifted. Each milestone was tested against a llama.cpp server by a human before committed.
|
|
23
|
+
|
|
24
|
+
The full agent conversation log is preserved in [CONVERSATION.md](./docs/CONVERSATION.md).
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
### Docker Compose
|
|
28
|
+
```yaml
|
|
29
|
+
services:
|
|
30
|
+
llm-mitm-proxy:
|
|
31
|
+
image: ghcr.io/mill1000/llm-mitm-proxy:latest
|
|
32
|
+
restart: unless-stopped
|
|
33
|
+
ports:
|
|
34
|
+
- "8081:8081" # LLM API
|
|
35
|
+
- "9090:9090" # Web UI
|
|
36
|
+
environment:
|
|
37
|
+
UPSTREAM_BASE_URL: "http://<your-llamacpp>:8080"
|
|
38
|
+
extra_hosts:
|
|
39
|
+
- "host.docker.internal:host-gateway"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Docker
|
|
43
|
+
```bash
|
|
44
|
+
docker run -d --name llm-mitm-proxy -p 8081:8081 -p 9090:9090 -e UPSTREAM_BASE_URL=http://<your-llamacpp>:8080 ghcr.io/mill1000/llm-mitm-proxy:latest
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## pipx/uvx
|
|
48
|
+
```bash
|
|
49
|
+
# uvx
|
|
50
|
+
uvx llm-mitm-proxy http://<your-llamacpp>:8080
|
|
51
|
+
|
|
52
|
+
# pipx
|
|
53
|
+
pipx install llm-mitm-proxy
|
|
54
|
+
llm-mitm-proxy http://<your-llamacpp>:8080
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
1. Start the proxy
|
|
59
|
+
2. Point an agent at the proxy
|
|
60
|
+
3. Open the Web UI and look at the requests go brrr.
|
|
61
|
+
|
|
62
|
+
See [Configuration](#configuration) for more information on specifying an upstream API key, changing ports, and other options.
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
### Command Line
|
|
66
|
+
```
|
|
67
|
+
usage: llm-mitm-proxy [-h] [--version] [--host HOST] [--proxy-port PORT] [--web-port PORT]
|
|
68
|
+
[--upstream-api-key KEY] [--log-level LEVEL] [--ui-dir DIR]
|
|
69
|
+
[UPSTREAM_BASE_URL]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Arg | Notes |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `UPSTREAM_BASE_URL` (positional) | upstream base URL (default `http://host.docker.internal:8080`) |
|
|
75
|
+
| `--host` | listen host for both listeners (default `0.0.0.0`) |
|
|
76
|
+
| `--proxy-port` | proxy listener port (default `8081`, the transparent catch-all; llama.cpp's own default is `8080`) |
|
|
77
|
+
| `--web-port` | WebUI + `/api/*` + `/ws` + `/health` listener port (default `9090`) |
|
|
78
|
+
| `--upstream-api-key` | optional server-side fallback key, injected only when a client sends no key |
|
|
79
|
+
| `--log-level` | app loggers, incl. the `llm_proxy.ws` connection trace at `debug` (connect/focus/disconnect) |
|
|
80
|
+
| `--ui-dir` | static UI directory (default: the in-package `llm_proxy/web` build) |
|
|
81
|
+
| `--help` / `--version` | usage / package version |
|
|
82
|
+
|
|
83
|
+
### Docker Environment Variables
|
|
84
|
+
|
|
85
|
+
The Docker image maps the following environment variables to command line options:
|
|
86
|
+
| Var | CLI arg |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `UPSTREAM_BASE_URL` | positional upstream |
|
|
89
|
+
| `UPSTREAM_API_KEY` | `--upstream-api-key` |
|
|
90
|
+
| `LISTEN_HOST` | `--host` |
|
|
91
|
+
| `PROXY_PORT` | `--proxy-port` |
|
|
92
|
+
| `WEB_PORT` | `--web-port` |
|
|
93
|
+
| `LOG_LEVEL` | `--log-level` |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
services:
|
|
2
|
+
llm-mitm-proxy:
|
|
3
|
+
image: ghcr.io/mill1000/llm-mitm-proxy:latest
|
|
4
|
+
restart: unless-stopped
|
|
5
|
+
ports:
|
|
6
|
+
- "8081:8081" # LLM API
|
|
7
|
+
- "9090:9090" # Web UI
|
|
8
|
+
environment:
|
|
9
|
+
UPSTREAM_BASE_URL: "http://<your-llamacpp>:8080"
|
|
10
|
+
extra_hosts:
|
|
11
|
+
- "host.docker.internal:host-gateway"
|