mitm

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 23 Imported by: 0

README

mitm

A Man-in-the-Middle (MITM) HTTP/HTTPS proxy library for Go.

Features

  • Transparent TCP relay or full TLS interception per CONNECT tunnel
  • Unified request/response middleware pipeline for both plain-HTTP and HTTPS traffic
  • Upstream TLS connection pooling with cross-session reuse
  • Per-host certificate forging and caching, signed once however many connections arrive for a host at the same time
  • Certificates for clients that send no SNI, forged for the CONNECT target
  • Built-in middlewares for common use cases

Installation

go get github.com/aomori446/mitm

Quick Start

package main

import (
    "context"
    "os"
    "os/signal"
    "syscall"

    "github.com/aomori446/mitm"
)

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    certMgr, _ := mitm.NewCertManager("testdata/ca.crt", "testdata/ca.key")

    handler := mitm.New(certMgr)

    // Starts proxy server with built-in graceful shutdown on ctx cancellation
    handler.ListenAndServe(ctx, ":8080")
}

Point your browser's proxy settings to localhost:8080 and install testdata/ca.crt as a trusted CA.

Note: mitm.Handler also implements http.Handler, so you can plug it into a custom http.Server or multiplexer if needed.

Middlewares

Register middlewares to inspect or modify traffic:

handler.UseRequest(func(ctx context.Context, req *http.Request) (*http.Request, *http.Response) {
    // Return a modified request, or short-circuit with a synthetic response.
    return req, nil
})

handler.UseResponse(func(ctx context.Context, resp *http.Response) (*http.Response, error) {
    // Return a modified response, or return an error to abort.
    return resp, nil
})

handler.UseError(func(ctx context.Context, req *http.Request, err error) {
    // The upstream round trip failed, so no response middleware runs for req.
})

handler.UseHandshakeError(func(ctx context.Context, host string, err error, d time.Duration) {
    // A client TLS handshake against the forged certificate failed —
    // usually the client does not trust the CA.
})

Middlewares are called in registration order. A non-nil *http.Response from a UseRequest middleware short-circuits the upstream request and sends the response directly to the client. mitm.Response builds one:

return req, mitm.Response(http.StatusForbidden, "text/plain", io.NopCloser(strings.NewReader("blocked")))

Every request that reaches the upstream is followed by exactly one of the response middlewares or the error middlewares, so state keyed on the request can always be released.

Handshake errors that are just EOF, connection reset, or a closed connection are treated as ordinary client probes and do not reach UseHandshakeError.

Built-in Middlewares

All middlewares live in github.com/aomori446/mitm/middleware.

Logger
onReq, onResp := middleware.Logger(slog.Default())
handler.UseRequest(onReq)
handler.UseResponse(onResp)

Logs method, URL, status code, content-type, and elapsed time.

Blocker
// Block by host pattern → 403 Forbidden
handler.UseRequest(middleware.Blocker("ads.example.com", "*.doubleclick.net"))

// Block with a content-appropriate empty response
handler.UseRequest(middleware.BlockerWith(
    middleware.RespondWithAuto(), // pixel / empty JS / empty CSS / empty HTML
    "*.googlesyndication.com",
))

// Block by custom match function
handler.UseRequest(middleware.BlockerFunc(
    middleware.RespondWithEmptyJS(),
    func(req *http.Request) bool {
        return strings.HasSuffix(req.URL.Path, "/analytics.js")
    },
))

Available BlockResponse helpers:

Helper Response
RespondWith403() 403 Forbidden
RespondWithPixel() 1×1 transparent GIF
RespondWithEmptyJS() // (empty JS)
RespondWithEmptyCSS() empty CSS
RespondWithEmptyHTML() <html></html>
RespondWithAuto() inferred from URL extension
Header
handler.UseRequest(middleware.SetRequestHeader("Authorization", "Bearer token"))
handler.UseRequest(middleware.RemoveRequestHeader("Cookie"))
handler.UseResponse(middleware.SetResponseHeader("X-Frame-Options", "DENY"))
handler.UseResponse(middleware.RemoveResponseHeader("Server"))
Dump
onReq, onResp := middleware.Dump(os.Stderr)
handler.UseRequest(onReq)
handler.UseResponse(onResp)

Writes each request and response in HTTP/1.1 wire format to the provided writer.

Each body is read into memory in full to be written out and replayed, so a large upload or download passing through the proxy is held in memory whole. Scope this to the traffic being investigated rather than leaving it on a busy proxy. DumpRequest and DumpResponse are available separately if only one direction is wanted.

Configuration

Upstream transport
handler.SetUpstreamTransport(&http.Transport{
    Proxy:           http.ProxyURL(corporateProxy),
    TLSClientConfig: &tls.Config{RootCAs: privateRoots},
})

Replaces the round tripper used for every proxied request: to reach origins through another proxy, to trust a private certificate authority, or to observe upstream traffic in a test. It applies to plain HTTP as well as to intercepted tunnels, and takes effect on the next round trip rather than only on new tunnels. Middlewares still run around whatever is set here — this replaces only the round trip itself.

The default is tuned for a proxy: one pooled transport for every host, with an idle timeout and a ceiling so a long-lived process does not accumulate connections it will never use again.

Tunnel idle timeout
handler.SetTunnelIdleTimeout(90 * time.Second)

Bounds how long an intercepted tunnel may sit with no request in flight before the proxy closes it. It bounds only the wait for the next request; a request already arriving, however slowly, is not idle. The default of zero disables the bound, which means a client that opens a tunnel and goes quiet holds its goroutines and buffers until it disconnects. Only tunnels opened after the call are affected.

Certificate manager
handler.SetCertManager(newCertMgr)

Swaps the CA in use — for rotating to a freshly generated one without restarting the proxy. handler.CertManager() reads the current one back. Both are safe to call while the proxy is serving.

Generating a CA Certificate

GenerateCA writes a new CA certificate and key, and is what the genca example wraps:

mitm.GenerateCA("testdata/ca.crt", "testdata/ca.key")

Or from the command line:

go run ./examples/genca -cert testdata/ca.crt -key testdata/ca.key

Running the Example Proxy

go run ./examples/proxy -addr :8080 -ca-cert testdata/ca.crt -ca-key testdata/ca.key

Project Structure

cert.go        CA loading, per-host cert forging and caching
connect.go     CONNECT tunnel handling (MITM & TCP relay)
handler.go     Core proxy handler (ServeHTTP, ListenAndServe, middlewares)
relay.go       Bidirectional TCP relay for transparent tunnelling
transport.go   Upstream connection pooling and middleware transport
middleware/    Middleware types and built-in middlewares
examples/      Runnable reference implementations
PERF.md        What the benchmarks measure, and what was measured and left alone

Requirements

  • Go 1.26+

License

(C) 2026 Aomori446, MIT License

Documentation

Index

Constants

View Source
const DefaultTunnelIdleTimeout = 90 * time.Second

DefaultTunnelIdleTimeout bounds how long an intercepted tunnel may sit with no request in flight before the proxy closes it.

Variables

This section is empty.

Functions

func GenerateCA

func GenerateCA(certOut, keyOut string) error

func Response

func Response(status int, contentType string, body io.ReadCloser) *http.Response

Response constructs a standard *http.Response helper.

Types

type CertManager

type CertManager struct {
	// contains filtered or unexported fields
}

func NewCertManager

func NewCertManager(certFile, keyFile string) (*CertManager, error)

func (*CertManager) TLSConfig

func (m *CertManager) TLSConfig() *tls.Config

TLSConfig returns the tls.Config used for every intercepted client connection. The same value is returned on every call, deliberately: a tls.Config owns the session ticket keys, so handing out a fresh config per connection leaves every client unable to decrypt the ticket it was issued and forces a full handshake every time. Callers must treat it as read-only.

type ErrorFunc added in v0.2.0

type ErrorFunc func(ctx context.Context, req *http.Request, err error)

ErrorFunc is called when the upstream round trip fails, so no response middleware will run for req. It is the terminal callback for a request that passed the request middlewares but never produced a response.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler is an http.Handler that acts as a forward proxy and performs TLS interception (MITM) on CONNECT tunnels when a CertManager is provided.

func New

func New(certMgr *CertManager) *Handler

New creates a Handler. Providing a non-nil certMgr enables TLS interception; passing nil falls back to a transparent TCP relay for CONNECT tunnels.

The caller should set http.Server.BaseContext to a context that is canceled on shutdown so that long-lived CONNECT tunnels are torn down promptly when the server stops.

func (*Handler) CertManager added in v0.2.0

func (h *Handler) CertManager() *CertManager

CertManager returns the current CertManager in a thread-safe manner.

func (*Handler) ListenAndServe

func (h *Handler) ListenAndServe(ctx context.Context, addr string) error

ListenAndServe starts an HTTP proxy server on the given network address. It uses ctx as the BaseContext for all incoming connections and shuts down gracefully when ctx is canceled.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP implements http.Handler. CONNECT requests initiate a tunnel; all other methods run request middlewares then are proxied via the reverse proxy.

func (*Handler) SetCertManager added in v0.2.0

func (h *Handler) SetCertManager(certMgr *CertManager)

SetCertManager replaces the active CertManager in a thread-safe manner.

func (*Handler) SetTunnelIdleTimeout added in v0.2.0

func (h *Handler) SetTunnelIdleTimeout(d time.Duration)

SetTunnelIdleTimeout bounds how long an intercepted tunnel may sit with no request in flight before the proxy closes it. It bounds only the wait for the next request; a request already arriving, however slowly, is not idle.

Zero disables the bound, which means a client that opens a tunnel and goes quiet holds its goroutines and buffers until it disconnects. Only tunnels opened after the call are affected.

func (*Handler) SetUpstreamTransport added in v0.2.0

func (h *Handler) SetUpstreamTransport(rt http.RoundTripper)

SetUpstreamTransport replaces the round tripper used for every proxied request, in a thread-safe manner.

The default is tuned for a proxy: one pooled transport for every host, with an idle timeout and a ceiling so a long-lived process does not accumulate connections it will never use again. Replace it to reach origins through another proxy, to trust a private certificate authority, or to observe upstream traffic in a test. Middlewares still run around whatever is set here; this replaces only the round trip itself.

It applies to plain HTTP requests as well as to intercepted tunnels, and takes effect on the next round trip rather than only on new tunnels.

func (*Handler) TunnelIdleTimeout added in v0.2.0

func (h *Handler) TunnelIdleTimeout() time.Duration

TunnelIdleTimeout returns how long an intercepted tunnel may sit idle, in a thread-safe manner.

func (*Handler) UpstreamTransport added in v0.2.0

func (h *Handler) UpstreamTransport() http.RoundTripper

UpstreamTransport returns the round tripper used for every proxied request, in a thread-safe manner.

func (*Handler) UseError added in v0.2.0

func (h *Handler) UseError(fn ErrorFunc)

UseError registers fn as an upstream error middleware. Middlewares are called in registration order whenever an upstream round trip fails. Every request that reaches the upstream is followed by exactly one of the response middlewares or the error middlewares, so state keyed on the request can always be released.

func (*Handler) UseHandshakeError added in v0.2.0

func (h *Handler) UseHandshakeError(fn HandshakeErrorFunc)

UseHandshakeError registers fn as a handshake error middleware callback.

func (*Handler) UseRequest

func (h *Handler) UseRequest(fn RequestFunc)

UseRequest registers fn as a request middleware. Middlewares are called in registration order before each upstream request.

func (*Handler) UseResponse

func (h *Handler) UseResponse(fn ResponseFunc)

UseResponse registers fn as a response middleware. Middlewares are called in registration order after each upstream response.

type HandshakeErrorFunc added in v0.2.0

type HandshakeErrorFunc func(ctx context.Context, host string, err error, duration time.Duration)

HandshakeErrorFunc is called when a client TLS handshake fails during CONNECT tunnel interception.

type RequestFunc

type RequestFunc func(ctx context.Context, req *http.Request) (*http.Request, *http.Response)

RequestFunc is called before a request is forwarded to the upstream server.

type ResponseFunc

type ResponseFunc func(ctx context.Context, resp *http.Response) (*http.Response, error)

ResponseFunc is called after the upstream response is received and before it is written back to the client. The original request is available via resp.Request.

Directories

Path Synopsis
examples
blocker command
Command blocker demonstrates the Blocker interceptor with various response helpers.
Command blocker demonstrates the Blocker interceptor with various response helpers.
dump command
Command dump demonstrates the Dump interceptor, which prints every proxied request and response in HTTP/1.1 wire format to stderr.
Command dump demonstrates the Dump interceptor, which prints every proxied request and response in HTTP/1.1 wire format to stderr.
genca command
Command genca generates a self-signed ECDSA CA certificate and private key for use with the MITM proxy.
Command genca generates a self-signed ECDSA CA certificate and private key for use with the MITM proxy.
header command
Command header demonstrates the Header interceptor helpers: injecting headers into upstream requests and stripping or adding headers on responses.
Command header demonstrates the Header interceptor helpers: injecting headers into upstream requests and stripping or adding headers on responses.
logger command
Command logger demonstrates how to attach the Logger interceptor to a MITM proxy to log every proxied request and response.
Command logger demonstrates how to attach the Logger interceptor to a MITM proxy to log every proxied request and response.
proxy command
Command proxy is a minimal MITM HTTP/HTTPS proxy built on github.com/aomori446/mitm.
Command proxy is a minimal MITM HTTP/HTTPS proxy built on github.com/aomori446/mitm.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL