Mitmproxy has a powerful scripting API that allows you to control almost any aspect of traffic being proxied. In fact, much of mitmproxy’s own core functionality is implemented using the exact same API exposed to scripters (see mitmproxy/addons).

A simple example

Scripting is event driven, with named handlers on the script object called at appropriate points of mitmproxy’s operation. Here’s a complete mitmproxy script that adds a new header to every HTTP response before it is returned to the client:

def response(flow):
    flow.response.headers["newheader"] = "foo"

All events that deal with an HTTP request get an instance of HTTPFlow, which we can use to manipulate the response itself. We can now run this script using mitmdump, and the new header will be added to all responses passing through the proxy:

>>> mitmdump -s add_header.py

Using classes

In the example above, the script object is the add_header module itself. That is, the handlers are declared at the global level of the script. This is great for quick hacks, but soon becomes limiting as scripts become more sophisticated.

When a script first starts up, the start, event is called before anything else happens. You can replace the current script object by returning it from this handler. Here’s how this looks when applied to the example above:

class AddHeader:
    def response(self, flow):
        flow.response.headers["newheader"] = "foo"

def start():
    return AddHeader()

So here, we’re using a module-level script to “boot up” into a class instance. From this point on, the module-level script is removed from the handler chain, and is replaced by the class instance.

Handling arguments

Scripts can handle their own command-line arguments, just like any other Python program. Let’s build on the example above to do something slightly more sophisticated - replace one value with another in all responses. Mitmproxy’s HTTPRequest and HTTPResponse objects have a handy replace method that takes care of all the details for us.

import argparse

class Replacer:
    def __init__(self, src, dst):
        self.src, self.dst = src, dst

    def response(self, flow):
        flow.response.replace(self.src, self.dst)

def start():
    parser = argparse.ArgumentParser()
    parser.add_argument("src", type=str)
    parser.add_argument("dst", type=str)
    args = parser.parse_args()
    return Replacer(args.src, args.dst)

We can now call this script on the command-line like this:

>>> mitmdump -dd -s "./script_arguments.py html faketml"

Whenever a handler is called, mitpmroxy rewrites the script environment so that it sees its own arguments as if it was invoked from the command-line.

Logging and the context

Scripts should not output straight to stderr or stdout. Instead, the log object on the ctx context module should be used, so that the mitmproxy host program can handle output appropriately. So, mitmdump can print colorised script output to the terminal, and mitmproxy console can place script output in the event buffer.

Here’s how this looks:

It is recommended to use `ctx.log` for logging within a script.
This goes to the event log in mitmproxy and to stdout in mitmdump.

If you want to help us out: https://github.com/mitmproxy/mitmproxy/issues/1530 :-)
from mitmproxy import ctx

def start():
    ctx.log.info("This is some informative text.")
    ctx.log.error("This is an error.")

The ctx module also exposes the mitmproxy master object at ctx.master for advanced usage.

Running scripts on saved flows

When a flow is loaded from disk, the sequence of events that the flow would have gone through on the wire is partially replayed. So, for instance, an HTTP flow loaded from disk will trigger requestheaders, request, responseheaders and response in order. We can use this behaviour to transform saved traffic using scripts. For example, we can invoke the replacer script from above on saved traffic as follows:

>>> mitmdump -dd -s "./arguments.py html fakehtml" -r saved -w changed

This command starts the arguments script, reads all the flows from saved transforming them in the process, then writes them all to changed.

The mitmproxy console tool provides interactive ways to run transforming scripts on flows - for instance, you can run a one-shot script on a single flow through the | (pipe) shortcut.


The mitmproxy script mechanism is single threaded, and the proxy blocks while script handlers execute. This hugely simplifies the most common case, where handlers are light-weight and the blocking doesn’t have a performance impact. It’s possible to implement a concurrent mechanism on top of the blocking framework, and mitmproxy includes a handy example of this that is fit for most purposes. You can use it as follows:

import time

from mitmproxy.script import concurrent

@concurrent  # Remove this and see what happens
def request(flow):
    # You don't want to use mitmproxy.ctx from a different thread
    print("handle request: %s%s" % (flow.request.host, flow.request.path))
    print("start  request: %s%s" % (flow.request.host, flow.request.path))


Mitmproxy includes a number of helpers for testing addons. The mitmproxy.test.taddons module contains a context helper that takes care of setting up and tearing down the addon event context. The mitmproxy.test.tflow module contains helpers for quickly creating test flows. Pydoc is the canonical reference for these modules, and mitmproxy’s own test suite is an excellent source of examples of usage. Here, for instance, is the mitmproxy unit tests for the anticache option, demonstrating a good cross-section of the test helpers:

from mitmproxy.test import tflow

from mitmproxy.addons import anticache
from mitmproxy.test import taddons

class TestAntiCache:
    def test_simple(self):
        sa = anticache.AntiCache()
        with taddons.context() as tctx:
            f = tflow.tflow(resp=True)
            f.request.headers["if-modified-since"] = "test"
            f.request.headers["if-none-match"] = "test"

            assert "if-modified-since" in f.request.headers
            assert "if-none-match" in f.request.headers

            tctx.configure(sa, anticache = True)
            assert "if-modified-since" not in f.request.headers
            assert "if-none-match" not in f.request.headers

Developing scripts

Mitmproxy monitors scripts for modifications, and reloads them on change. When this happens, the script is shut down (the done event is called), and the new instance is started up as if the script had just been loaded (the start and configure events are called).