Repository navigation
Deployment
Manifests live in contrib/, versioned with the code. release-please moves their image tag to each new release. Every option keeps the same hardening: a non-root user, read-only root, no capabilities.
Building the image yourself fetches the interpose dependency from GitHub at a pinned tag (the builder stage installs git for that), so the build needs access to github.com.
mkdir -p config state
cp contrib/mitm-proxy.example.yaml config/config.yaml # edit it
docker compose -f contrib/docker/compose.yml up -d
docker compose -f contrib/docker/compose.yml logs -fTwo ports are published, both on 127.0.0.1 only: the admin API (8080) and the proxy (3128). Inside the container both listeners bind 0.0.0.0: the image sets MITM_PROXY_LISTEN_HOST and MITM_PROXY_PROXY_HOST (the compose file repeats them, to be explicit), because the 127.0.0.1 default outside containers can't be reached through a published port. The published ports decide what's reachable. Because of that, the proxy's startup warning about listening on 0.0.0.0 without proxy.auth also appears in containers; it matters only if you publish 3128 beyond loopback. To offer the proxy to other machines, publish 3128 beyond loopback and set proxy.auth (MITM_PROXY_PROXY_AUTH_FILE). Put a reverse proxy (Traefik) in front of the admin API for remote access.
To run a reverse listener (record a service's inbound traffic; see In-Depth Configuration), set MITM_PROXY_REVERSE_0_LISTEN / _0_UPSTREAM and publish the listen port. The compose file has both commented. A reverse listener has no authentication, so publish its port only where the clients you mean can reach it.
To capture flows (the built-in capture middleware; see In-Depth Configuration), its dir must be writable, and the root filesystem isn't. Put it under the state volume (dir: /var/lib/mitm-proxy/captures, which is ./state/captures on the host), or mount a volume of its own read-write (- ./captures:/captures, with dir: /captures). The files hold request and response bodies: keep the host directory readable only by uid 1000, and delete old files yourself.
Secrets go in files mounted read-only, passed as MITM_PROXY_<KEY>_FILE. The root CA is generated in ./state/ca/ on first start: keep that directory, it's what your clients trust.
contrib/k8s is a kustomize base. It contains a Namespace with the restricted Pod Security Standard, a Deployment, a Service, a NetworkPolicy, and a ConfigMap generated from config.yaml.
kubectl apply -k contrib/k8s # directlyWith Flux, point an overlay in the cluster repo at a release:
# kustomization.yaml in the GitOps repo
resources:
- https://github.com/metalayerlab/mitm-proxy//contrib/k8s?ref=v1.2.3
patches: [] # replicas, resources, the ConfigMap's config.yaml, ingressThe Service exposes the admin API (http, 8080) and the proxy (proxy, 3128). The NetworkPolicy admits only the traefik namespace to the admin API and no one to the proxy port: add a rule for each namespace whose pods should use the proxy (an example is commented in networkpolicy.yaml), and set proxy.auth. For a reverse listener, uncomment the reverse port in deployment.yaml and service.yaml and admit only its intended clients in the NetworkPolicy.
State is on an emptyDir, so the root CA is regenerated whenever the pod is replaced, and clients must trust the new one each time. For a stable CA, swap it for a PersistentVolumeClaim, or supply the CA from a Secret: deployment.yaml has the secrets volume and the MITM_PROXY_CA_CERT_FILE, MITM_PROXY_CA_KEY_FILE and MITM_PROXY_PROXY_AUTH_FILE variables commented out, ready to enable. Secrets: SOPS-encrypted Secrets in the GitOps repo, mounted as files. For capture, mount a volume read-write at its dir (a PersistentVolumeClaim, so captures outlive the pod; an emptyDir loses them on every replacement).
install -Dm644 contrib/systemd/mitm-proxy.sysusers /usr/lib/sysusers.d/mitm-proxy.conf
systemd-sysusers
UV_PYTHON_INSTALL_DIR=/opt/uv-python uv venv /opt/mitm-proxy
uv pip install --python /opt/mitm-proxy git+https://github.com/metalayerlab/mitm-proxy@v1.2.3
install -Dm644 contrib/mitm-proxy.example.yaml /etc/mitm-proxy/config.yaml # edit it
install -Dm644 contrib/systemd/mitm-proxy.service /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now mitm-proxy
journalctl -u mitm-proxy -fThe proxy listens on 127.0.0.1:3128 and the admin API on 127.0.0.1:8080 (set proxy.host and proxy.auth to serve other machines). The root CA is generated in /var/lib/mitm-proxy/ca/ on first start. For capture, use a dir under /var/lib/mitm-proxy (the unit's StateDirectory=, mode 0700), say /var/lib/mitm-proxy/captures: ProtectSystem=strict makes everywhere else read-only, and a dir elsewhere needs a ReadWritePaths= line. The unit runs as the mitm-proxy system user in a tight sandbox (systemd-analyze security mitm-proxy scores it). If a feature needs more access, loosen exactly one directive, with a comment saying why.
Read the CHANGELOG entries between your version and the new one; breaking changes are marked ⚠. Then:
- Compose: change the image tag, then
docker compose up -d. - Kubernetes: change
?ref=(or theimages:tag) in the overlay, and let Flux reconcile. - systemd:
uv pip install --python /opt/mitm-proxy git+https://github.com/metalayerlab/mitm-proxy@vX.Y.Z, thensystemctl restart mitm-proxy.
Keep <state_dir>/ca/ across upgrades: it's the root CA your clients trust, and the file layout there is fixed. A new release never needs a data migration so far. New settings arrive with defaults, so an existing config file keeps working. One exception: since design 0008, a value of the wrong type (a decimal for a whole number, a list for a single value) stops startup instead of being silently converted; mitm-proxy config with the new version shows any before you switch.
To roll back, deploy the previous image tag (Compose, Kubernetes) or reinstall the previous version (systemd). The CA and config files work with either.
mitm-proxy | Async Python MITM proxy with a self-signed CA — HTTP/1.1 & HTTP/2, pluggable middleware for inspecting and rewriting traffic in flight.
GitHub • Releases • Issues • Changelog • Contributing
This wiki is generated from docs/wiki/ on every push to main: edits made here are overwritten. Change it with a pull request.
- 🔍 Intercept only the hosts you list; tunnel the rest untouched
- 🔀 HTTP/1.1 and HTTP/2 on both sides, bridged
- 🧩 Python middleware: inspect, rewrite, answer or deny
- 🔐 Its own root CA; origins always verified
- 📈 Prometheus metrics and a read-only status API