An easy-to-use and flexible MITM proxy library for Go that can intercept and inspect HTTP, HTTPS, HTTP/2, h2c, WebSocket, and WSS traffic.
- HTTP/1.1, HTTP/2, and h2c support
- HTTPS interception with custom CA certificates
- WebSocket and secure WebSocket interception
- HTTP proxy mode and SOCKS5 proxy mode
- HTTP interceptor chaining
- Host include/exclude filtering with wildcard matching
- Upstream proxy support
- Optional custom root CAs and client certificates for mTLS
- Request metadata for TLS, timing, and connection details
go get github.com/josexy/mitmproxy-goHTTPS interception requires a CA certificate and private key:
chmod +x ./tools/gen_cert.sh
OUTDIR=certs ./tools/gen_cert.shClients that use the proxy must trust the generated CA certificate.
package main
import (
"fmt"
"log"
"net/http"
mitmproxy "github.com/josexy/mitmproxy-go"
)
func main() {
handler, err := mitmproxy.NewMitmProxyHandler(
mitmproxy.WithCACertPath("certs/ca.crt"),
mitmproxy.WithCAKeyPath("certs/ca.key"),
)
if err != nil {
log.Fatal(err)
}
defer handler.Cleanup()
fmt.Println("proxy listening on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}package main
import (
"context"
"fmt"
"net/http"
mitmproxy "github.com/josexy/mitmproxy-go"
)
func httpInterceptor(ctx context.Context, req *http.Request, invoker mitmproxy.HTTPDelegatedInvoker) (*http.Response, error) {
fmt.Printf("-> %s %s\n", req.Method, req.URL)
resp, err := invoker.Invoke(req)
if err != nil {
return nil, err
}
fmt.Printf("<- %s\n", resp.Status)
return resp, nil
}handler, err := mitmproxy.NewMitmProxyHandler(
mitmproxy.WithCACertPath("certs/ca.crt"),
mitmproxy.WithCAKeyPath("certs/ca.key"),
mitmproxy.WithHTTPInterceptor(httpInterceptor),
)package main
import (
"context"
"log"
"net/http"
mitmproxy "github.com/josexy/mitmproxy-go"
)
func websocketInterceptor(ctx context.Context, req *http.Request, resp *http.Response, fw mitmproxy.WebsocketFramesWatcher) {
log.Printf("websocket upgrade %s -> %d", req.URL.String(), resp.StatusCode)
for {
select {
case <-ctx.Done():
return
case frame, ok := <-fw.Receive():
if !ok {
return
}
log.Printf("%s %d %q", frame.Direction(), frame.MessageType(), frame.DataBuffer().String())
if err := frame.Invoke(); err != nil {
log.Printf("forward websocket frame: %v", err)
}
}
}
}handler, err := mitmproxy.NewMitmProxyHandler(
mitmproxy.WithCACertPath("certs/ca.crt"),
mitmproxy.WithCAKeyPath("certs/ca.key"),
mitmproxy.WithWebsocketInterceptor(websocketInterceptor),
)package main
import (
"context"
"fmt"
"log"
"net"
mitmproxy "github.com/josexy/mitmproxy-go"
)
func main() {
handler, err := mitmproxy.NewMitmProxyHandler(
mitmproxy.WithCACertPath("certs/ca.crt"),
mitmproxy.WithCAKeyPath("certs/ca.key"),
)
if err != nil {
log.Fatal(err)
}
defer handler.Cleanup()
ln, err := net.Listen("tcp", ":1080")
if err != nil {
log.Fatal(err)
}
defer ln.Close()
fmt.Println("SOCKS5 proxy listening on :1080")
for {
conn, err := ln.Accept()
if err != nil {
continue
}
go func() {
defer conn.Close()
_ = handler.ServeSOCKS5(context.Background(), conn)
}()
}
}mitmproxy.WithCACertPath("certs/ca.crt")
mitmproxy.WithCAKeyPath("certs/ca.key")
mitmproxy.WithStreamBaseContext(ctx)
mitmproxy.WithErrorHandler(func(ec mitmproxy.ErrorContext) {})mitmproxy.WithProxy("http://127.0.0.1:8080")
mitmproxy.WithDisableProxy()
mitmproxy.WithDialer(&net.Dialer{Timeout: 30 * time.Second})
mitmproxy.WithIdleConnTimeout(60 * time.Second)
mitmproxy.WithDisableHTTP2()
mitmproxy.WithSkipVerifySSLFromServer()mitmproxy.WithRootCAs("certs/internal-ca.crt", "certs/partner-ca.crt")
mitmproxy.WithClientCert("example.com", mitmproxy.ClientCert{
CertPath: "certs/client.crt",
KeyPath: "certs/client.key",
})
mitmproxy.WithCertCachePool(2048, 30, 15)mitmproxy.WithHTTPInterceptor(httpInterceptor)
mitmproxy.WithChainHTTPInterceptor(interceptor1, interceptor2, interceptor3)
mitmproxy.WithWebsocketInterceptor(websocketInterceptor)
mitmproxy.WithMaxWebsocketFramesPerForward(4096)mitmproxy.WithIncludeHosts("api.example.com", "*.example.org")
mitmproxy.WithExcludeHosts("*.cdn.com", "static.example.com")WithExcludeHosts takes precedence over WithIncludeHosts.
Interceptors can read connection and TLS metadata from the request context:
package main
import (
"context"
"crypto/tls"
"fmt"
"net/http"
mitmproxy "github.com/josexy/mitmproxy-go"
"github.com/josexy/mitmproxy-go/metadata"
)
func httpInterceptor(ctx context.Context, req *http.Request, invoker mitmproxy.HTTPDelegatedInvoker) (*http.Response, error) {
mdCtx, _ := metadata.FromContext(ctx)
md := mdCtx.MD()
fmt.Printf("target: %s\n", md.RequestHostport)
fmt.Printf("local connected at: %v\n", md.LocalConnectionEstablishedTs)
fmt.Printf("remote connected at: %v\n", md.RemoteConnectionEstablishedTs)
fmt.Printf("request processed at: %v\n", md.RequestProcessedTs)
if md.TLSState != nil {
fmt.Printf("selected ALPN: %s\n", md.TLSState.SelectedALPN)
fmt.Printf("TLS version: %s\n", tls.VersionName(md.TLSState.SelectedTLSVersion))
fmt.Printf("cipher suite: %s\n", tls.CipherSuiteName(md.TLSState.SelectedCipherSuite))
}
if md.ServerCertificate != nil {
fmt.Printf("subject: %s\n", md.ServerCertificate.Subject.String())
fmt.Printf("issuer: %s\n", md.ServerCertificate.Issuer.String())
fmt.Printf("sha256: %s\n", md.ServerCertificate.Sha256FingerprintHex())
}
return invoker.Invoke(req)
}Current examples in this repository:
examples/helloworld: minimal HTTP proxy with request/response loggingexamples/dumper: HTTP and WebSocket dumping example with metadata loggingexamples/modify-content: modifies request headers and response bodyexamples/chain-interceptors: demonstrates ordered HTTP interceptor chaining
Run them from the repository root.
go run ./examples/helloworld/main.go -cacert certs/ca.crt -cakey certs/ca.key -port 10086# HTTP proxy mode
go run ./examples/dumper/main.go -cacert certs/ca.crt -cakey certs/ca.key -mode http -port 10086
# SOCKS5 proxy mode
go run ./examples/dumper/main.go -cacert certs/ca.crt -cakey certs/ca.key -mode socks5 -port 10086go run ./examples/modify-content/main.go -cacert certs/ca.crt -cakey certs/ca.key -port 10086go run ./examples/chain-interceptors/main.go -cacert certs/ca.crt -cakey certs/ca.key -port 10086- Call
handler.Cleanup()before shutdown to stop background certificate cache cleanup. WithStreamBaseContextis useful when you need shared cancellation for long-lived HTTP/2 streams.- If
WithIncludeHostsis unset, traffic is intercepted by default unless excluded.
go test -v ./...Format modified Go files with:
gofmt -w <files>This project is available under the terms specified in the repository.