Overview
This page showcases real addon examples from examples/addons/ in the mitmproxy source code. Each example demonstrates practical use cases and best practices.
All examples can be run with: mitmproxy -s examples/addons/example-name.py
Basic Examples
Simple Request Counter
The most basic addon - counting requests as they flow through the proxy.
examples/addons/anatomy.py
import logging
class Counter :
def __init__ ( self ):
self .num = 0
def request ( self , flow ):
self .num = self .num + 1
logging.info( "We've seen %d flows" % self .num)
addons = [Counter()]
Usage:
Add a custom header to every HTTP response.
examples/addons/http-add-header.py
class AddHeader :
def __init__ ( self ):
self .num = 0
def response ( self , flow ):
self .num = self .num + 1
flow.response.headers[ "count" ] = str ( self .num)
addons = [AddHeader()]
Key concepts:
Access response headers via flow.response.headers
Headers are case-insensitive dictionaries
State persists across requests
HTTP Modification
Modifying Query Parameters
Add or modify URL query parameters on the fly.
examples/addons/http-modify-query-string.py
from mitmproxy import http
def request ( flow : http.HTTPFlow) -> None :
flow.request.query[ "mitmproxy" ] = "rocks"
You can define hooks as module-level functions instead of class methods. Both styles work.
Intercept and modify POST form data.
examples/addons/http-modify-form.py
from mitmproxy import http
def request ( flow : http.HTTPFlow) -> None :
# Modify form data
if flow.request.urlencoded_form:
flow.request.urlencoded_form[ "username" ] = "modified_user"
Redirecting Requests
Redirect specific requests to different URLs.
examples/addons/http-redirect-requests.py
from mitmproxy import http
def request ( flow : http.HTTPFlow) -> None :
if flow.request.pretty_host == "example.com" :
flow.request.host = "example.org"
Replying from Proxy
Generate responses directly from the proxy without contacting the server.
examples/addons/http-reply-from-proxy.py
from mitmproxy import http
def request ( flow : http.HTTPFlow) -> None :
if flow.request.path == "/local" :
flow.response = http.Response.make(
200 ,
b "Hello from mitmproxy!" ,
{ "Content-Type" : "text/plain" }
)
Use cases:
Mock API endpoints
Block requests
Serve local content
Streaming
Enabling Response Streaming
Stream large responses to save memory.
examples/addons/http-stream-simple.py
def responseheaders ( flow ):
"""
Enables streaming for all responses.
Equivalent to --set stream_large_bodies=1
"""
flow.response.stream = True
Enable streaming in the responseheaders hook before the body is received.
Conditional Streaming
Stream only large responses.
examples/addons/http-stream-modify.py
def responseheaders ( flow ):
content_length = flow.response.headers.get( "content-length" , 0 )
if int (content_length) > 1000000 : # 1MB
flow.response.stream = True
Options and Configuration
Adding Custom Options
Create addons with configurable options.
examples/addons/options-simple.py
from mitmproxy import ctx
class AddHeader :
def __init__ ( self ):
self .num = 0
def load ( self , loader ):
loader.add_option(
name = "addheader" ,
typespec = bool ,
default = False ,
help = "Add a count header to responses" ,
)
def response ( self , flow ):
if ctx.options.addheader:
self .num = self .num + 1
flow.response.headers[ "count" ] = str ( self .num)
addons = [AddHeader()]
Usage:
mitmproxy -s options-simple.py --set addheader= true
Reacting to Option Changes
Handle configuration changes at runtime.
examples/addons/options-configure.py
from mitmproxy import ctx
from mitmproxy.addonmanager import Loader
import logging
class OptionExample :
def load ( self , loader : Loader):
loader.add_option(
name = "my_threshold" ,
typespec = int ,
default = 100 ,
help = "Response size threshold"
)
def configure ( self , updated ):
if "my_threshold" in updated:
logging.info( f "Threshold changed to: { ctx.options.my_threshold } " )
def response ( self , flow ):
if len (flow.response.content) > ctx.options.my_threshold:
logging.info( f "Large response: { len (flow.response.content) } bytes" )
addons = [OptionExample()]
Filtering
Flow Filtering
Use mitmproxy’s powerful filter syntax in addons.
examples/addons/filter-flows.py
import logging
from mitmproxy import flowfilter, http
from mitmproxy.addonmanager import Loader
class Filter :
filter : flowfilter.TFilter
def load ( self , loader : Loader):
loader.add_option(
"flowfilter" ,
str ,
"" ,
"Check that flow matches filter."
)
def configure ( self , updated ):
if "flowfilter" in updated:
self .filter = flowfilter.parse( "." )
def response ( self , flow : http.HTTPFlow) -> None :
if flowfilter.match( self .filter, flow):
logging.info( "Flow matches filter:" )
logging.info(flow)
addons = [Filter()]
Filter examples:
~d example.com - Domain matches
~m POST - Method matches
~c 200 - Status code matches
~u /api/ - URL path contains
Commands
Simple Command
Add custom commands to mitmproxy’s command prompt.
examples/addons/commands-simple.py
import logging
from mitmproxy import command
class MyAddon :
def __init__ ( self ):
self .num = 0
@command.command ( "myaddon.inc" )
def inc ( self ) -> None :
self .num += 1
logging.info( f "num = { self .num } " )
addons = [MyAddon()]
Usage in mitmproxy:
Commands with Flow Arguments
Create commands that operate on flows.
examples/addons/commands-flows.py
import logging
from mitmproxy import command, flow
class MyAddon :
@command.command ( "myaddon.duplicate" )
def duplicate ( self , flows : list[flow.Flow]) -> None :
for f in flows:
logging.info( f "Duplicating: { f.request.url } " )
# Create duplicate
addons = [MyAddon()]
WebSocket
WebSocket Message Processing
Intercept and modify WebSocket messages.
examples/addons/websocket-simple.py
import logging
import re
from mitmproxy import http
def websocket_message ( flow : http.HTTPFlow):
assert flow.websocket is not None
message = flow.websocket.messages[ - 1 ]
# Check message direction
if message.from_client:
logging.info( f "Client sent: { message.content !r} " )
else :
logging.info( f "Server sent: { message.content !r} " )
# Modify message content
message.content = re.sub( rb " ^ Hello" , b "HAPPY" , message.content)
# Drop messages
if b "FOOBAR" in message.content:
message.drop()
Key concepts:
Access messages via flow.websocket.messages[-1]
Check direction with message.from_client
Drop messages with message.drop()
WebSocket Message Injection
Inject custom messages into WebSocket connections.
examples/addons/websocket-inject-message.py
import logging
from mitmproxy import ctx, http
from mitmproxy.websocket import WebSocketMessage
class InjectWebSocket :
def websocket_start ( self , flow : http.HTTPFlow) -> None :
# Store flow for later injection
self .flow = flow
def inject_message ( self ):
if hasattr ( self , 'flow' ) and self .flow.websocket:
msg = WebSocketMessage(
type = 0x 1 , # Text message
from_client = False ,
content = b "Injected message"
)
self .flow.websocket.messages.append(msg)
addons = [InjectWebSocket()]
TCP
TCP Message Processing
Handle raw TCP connections.
examples/addons/tcp-simple.py
import logging
from mitmproxy import tcp
from mitmproxy.utils import strutils
def tcp_message ( flow : tcp.TCPFlow):
message = flow.messages[ - 1 ]
message.content = message.content.replace( b "foo" , b "bar" )
logging.info(
f "tcp_message[from_client= { message.from_client } , "
f "content= { strutils.bytes_to_escaped_str(message.content) } ]"
)
TCP is stream-based. Message boundaries are arbitrary and based on socket.recv() calls.
Enable TCP interception:
mitmdump --tcp-hosts ".*" -s tcp-simple.py
DNS
DNS Response Spoofing
Modify or spoof DNS responses.
examples/addons/dns-simple.py
import ipaddress
import logging
from mitmproxy import dns
def dns_request ( flow : dns.DNSFlow) -> None :
q = flow.request.question
if q and q.type == dns.types. AAAA :
logging.info( f "Spoofing IPv6 records for { q.name } ..." )
if q.name == "example.com" :
flow.response = flow.request.succeed([
dns.ResourceRecord(
name = "example.com" ,
type = dns.types. AAAA ,
class_ = dns.classes. IN ,
ttl = dns.ResourceRecord. DEFAULT_TTL ,
data = ipaddress.ip_address( "::1" ).packed,
)
])
elif q.name == "example.org" :
flow.response = flow.request.fail(dns.response_codes. NXDOMAIN )
else :
flow.response = flow.request.succeed([])
Use cases:
Local DNS resolution
Blocking domains
Testing DNS failures
Async Operations
Non-blocking Addon
Use async/await for non-blocking I/O.
examples/addons/nonblocking.py
import asyncio
import logging
# Async hooks (preferred)
async def request ( flow ):
logging.info( f "Handle request: { flow.request.host }{ flow.request.path } " )
await asyncio.sleep( 5 ) # Non-blocking delay
logging.info( f "Start request: { flow.request.host }{ flow.request.path } " )
Alternative with threads:
from mitmproxy.script import concurrent
import time
@concurrent
def request ( flow ):
logging.info( f "Handle request: { flow.request.host }{ flow.request.path } " )
time.sleep( 5 ) # Runs in separate thread
logging.info( f "Start request: { flow.request.host }{ flow.request.path } " )
Async hooks are preferred over @concurrent as they provide better performance without race conditions.
Advanced Examples
Duplicate, Modify, and Replay
Duplicate flows, modify them, and replay to the server.
examples/addons/duplicate-modify-replay.py
import logging
from mitmproxy import ctx, http
class DuplicateAndReplay :
def response ( self , flow : http.HTTPFlow) -> None :
if flow.request.path == "/api/data" :
# Create a duplicate
duplicate = flow.copy()
duplicate.request.path = "/api/data/v2"
duplicate.request.headers[ "X-Replayed" ] = "true"
# Replay the modified request
ctx.master.commands.call( "replay.client" , [duplicate])
logging.info( f "Replayed modified request to { duplicate.request.url } " )
addons = [DuplicateAndReplay()]
Flow Persistence
Save and load flows from disk.
examples/addons/io-write-flow-file.py
import logging
from mitmproxy import ctx, http, io
class FlowWriter :
def __init__ ( self ):
self .f = None
def load ( self , loader ):
loader.add_option(
"flowfile" ,
str ,
"flows.mitm" ,
"File to save flows"
)
def configure ( self , updates ):
if "flowfile" in updates:
if self .f:
self .f.close()
self .f = open (ctx.options.flowfile, "wb" )
self .w = io.FlowWriter( self .f)
def response ( self , flow : http.HTTPFlow):
if self .w:
self .w.add(flow)
def done ( self ):
if self .f:
self .f.close()
addons = [FlowWriter()]
Reading Saved Flows
examples/addons/io-read-saved-flows.py
import logging
from mitmproxy import io
class FlowReader :
def load ( self , loader ):
loader.add_option(
"flowfile" ,
str ,
"" ,
"Flow file to read"
)
def running ( self ):
if ctx.options.flowfile:
with open (ctx.options.flowfile, "rb" ) as f:
reader = io.FlowReader(f)
for flow in reader.stream():
logging.info( f "Loaded: { flow.request.url } " )
addons = [FlowReader()]
HTTP Trailers
Handle HTTP trailers (rare but useful for some protocols).
examples/addons/http-trailers.py
import logging
from mitmproxy import http
def request ( flow : http.HTTPFlow) -> None :
# Add request trailers
if not flow.request.trailers:
flow.request.trailers = http.Headers()
flow.request.trailers[ "X-Request-Trailer" ] = "value"
def response ( flow : http.HTTPFlow) -> None :
# Read response trailers
if flow.response.trailers:
logging.info( f "Response trailers: { flow.response.trailers } " )
Testing and Debugging
Logging Events
Log all events for debugging.
examples/addons/log-events.py
import logging
class LogEvents :
def request ( self , flow ):
logging.info( f "Request: { flow.request.url } " )
def response ( self , flow ):
logging.info( f "Response: { flow.response.status_code } " )
def error ( self , flow ):
logging.error( f "Error: { flow.error.msg } " )
def websocket_message ( self , flow ):
message = flow.websocket.messages[ - 1 ]
logging.info( f "WebSocket: { message.content[: 50 ] } " )
def tcp_message ( self , flow ):
message = flow.messages[ - 1 ]
logging.info( f "TCP: { len (message.content) } bytes" )
addons = [LogEvents()]
Shutdown Handling
Perform cleanup on shutdown.
examples/addons/shutdown.py
import logging
class ShutdownExample :
def running ( self ):
logging.info( "Addon started and running" )
def done ( self ):
logging.info( "Addon shutting down" )
# Cleanup resources
# Close connections
# Save state
addons = [ShutdownExample()]
Integration Examples
WSGI/Flask Integration
Serve a Flask app through mitmproxy.
examples/addons/wsgi-flask-app.py
from flask import Flask
from mitmproxy import ctx, http
app = Flask( "proxapp" )
@app.route ( "/" )
def hello ():
return "Hello from Flask!"
class WSGIApp :
def request ( self , flow : http.HTTPFlow) -> None :
if flow.request.host == "proxapp" :
flow.response = http.Response.make(
200 ,
b "Flask app response"
)
addons = [WSGIApp()]
Best Practices
Use Logging Always use logging module instead of print() for output.
Handle Errors Wrap risky operations in try/except to avoid breaking other addons.
Check Flow State Always verify that flow.response or flow.websocket exists before accessing.
Document Options Provide clear help text for custom options.
Running Examples
All examples are located in examples/addons/ and can be run with:
# Run with mitmproxy (interactive)
mitmproxy -s examples/addons/example-name.py
# Run with mitmdump (command-line)
mitmdump -s examples/addons/example-name.py
# Run with mitmweb (web interface)
mitmweb -s examples/addons/example-name.py
# Set options
mitmproxy -s example.py --set option_name=value
Next Steps
Event Hooks Complete reference of all available hooks
Built-in Addons Learn from production-quality built-in addons