0
# Addon Development
1
2
Extensible addon system for custom functionality with lifecycle hooks, event handling, and access to all proxy components. Includes comprehensive built-in addons and framework for developing custom extensions.
3
4
## Capabilities
5
6
### Addon System Overview
7
8
The addon system provides a comprehensive framework for extending mitmproxy functionality.
9
10
```python { .api }
11
def default_addons() -> List[Any]:
12
"""
13
Get list of all built-in addons.
14
15
Returns:
16
- List of addon instances providing core mitmproxy functionality
17
"""
18
19
def concurrent(func: Callable) -> Callable:
20
"""
21
Decorator for concurrent addon execution.
22
23
Allows addon methods to run concurrently without blocking
24
the main proxy processing thread.
25
26
Parameters:
27
- func: Addon method to make concurrent
28
29
Returns:
30
- Decorated function that runs concurrently
31
"""
32
```
33
34
### Addon Context Access
35
36
Global context object providing access to proxy components.
37
38
```python { .api }
39
# Context module for addon access
40
import mitmproxy.ctx as ctx
41
42
# Context provides access to:
43
# - ctx.master: Master instance
44
# - ctx.options: Configuration options
45
# - ctx.log: Logging interface
46
47
class Context:
48
"""
49
Global context object for addon access to proxy components.
50
51
Available as `ctx` in addon methods.
52
"""
53
master: Master
54
options: Options
55
log: LogEntry
56
57
def log_info(self, message: str) -> None:
58
"""Log info message."""
59
60
def log_warn(self, message: str) -> None:
61
"""Log warning message."""
62
63
def log_error(self, message: str) -> None:
64
"""Log error message."""
65
```
66
67
### Built-in Addons
68
69
Comprehensive set of built-in addons for common functionality.
70
71
```python { .api }
72
# Core functionality addons
73
class Core:
74
"""Core proxy functionality."""
75
76
class Browser:
77
"""Browser integration and automation."""
78
79
class Block:
80
"""Request/response blocking capabilities."""
81
82
class BlockList:
83
"""Host and URL blacklisting functionality."""
84
85
class AntiCache:
86
"""Cache prevention for testing."""
87
88
class AntiComp:
89
"""Compression disabling for analysis."""
90
91
# Playback and testing addons
92
class ClientPlayback:
93
"""Client-side request replay."""
94
95
class ServerPlayback:
96
"""Server-side response replay."""
97
98
# Content modification addons
99
class ModifyBody:
100
"""Request/response body modification."""
101
102
class ModifyHeaders:
103
"""Header modification and injection."""
104
105
class MapRemote:
106
"""Remote URL mapping and redirection."""
107
108
class MapLocal:
109
"""Local file serving and mapping."""
110
111
# Authentication and security addons
112
class ProxyAuth:
113
"""Proxy authentication management."""
114
115
class StickyAuth:
116
"""Authentication persistence across requests."""
117
118
class StickyCookie:
119
"""Cookie persistence and management."""
120
121
class UpstreamAuth:
122
"""Upstream server authentication."""
123
124
# Data export and persistence addons
125
class Save:
126
"""Flow saving and persistence."""
127
128
class SaveHar:
129
"""HAR format export."""
130
131
class Export:
132
"""Multi-format flow export."""
133
134
# Network and protocol addons
135
class DnsResolver:
136
"""DNS resolution and caching."""
137
138
class NextLayer:
139
"""Protocol layer detection and routing."""
140
141
class TlsConfig:
142
"""TLS/SSL configuration management."""
143
144
# User interface addons
145
class CommandHistory:
146
"""Command history management."""
147
148
class Comment:
149
"""Flow commenting and annotation."""
150
151
class Cut:
152
"""Data extraction and filtering."""
153
154
# Development and debugging addons
155
class ScriptLoader:
156
"""Custom script loading and management."""
157
158
class Onboarding:
159
"""User onboarding and help system."""
160
161
class DisableH2C:
162
"""Disable HTTP/2 cleartext upgrade for compatibility."""
163
164
class StripDnsHttpsRecords:
165
"""Strip DNS HTTPS records for testing."""
166
167
class UpdateAltSvc:
168
"""Update Alt-Svc headers for HTTP/3 support."""
169
```
170
171
## Usage Examples
172
173
### Basic Addon Development
174
175
```python
176
from mitmproxy import http
177
import mitmproxy.ctx as ctx
178
179
class BasicAddon:
180
"""Basic addon example demonstrating common patterns."""
181
182
def __init__(self):
183
self.request_count = 0
184
self.blocked_domains = {"malicious.com", "blocked.example"}
185
186
def load(self, loader):
187
"""Called when the addon is loaded."""
188
ctx.log.info("BasicAddon loaded")
189
190
# Add custom options
191
loader.add_option(
192
name="basic_addon_enabled",
193
typespec=bool,
194
default=True,
195
help="Enable basic addon functionality"
196
)
197
198
def configure(self, updates):
199
"""Called when configuration changes."""
200
if "basic_addon_enabled" in updates:
201
enabled = ctx.options.basic_addon_enabled
202
ctx.log.info(f"BasicAddon {'enabled' if enabled else 'disabled'}")
203
204
def request(self, flow: http.HTTPFlow) -> None:
205
"""Handle HTTP requests."""
206
if not ctx.options.basic_addon_enabled:
207
return
208
209
self.request_count += 1
210
211
# Log request
212
ctx.log.info(f"Request #{self.request_count}: {flow.request.method} {flow.request.url}")
213
214
# Block malicious domains
215
if flow.request.host in self.blocked_domains:
216
ctx.log.warn(f"Blocked request to {flow.request.host}")
217
flow.response = http.Response.make(
218
status_code=403,
219
content="Blocked by BasicAddon",
220
headers={"Content-Type": "text/plain"}
221
)
222
return
223
224
# Add custom header
225
flow.request.headers["X-BasicAddon"] = "processed"
226
227
def response(self, flow: http.HTTPFlow) -> None:
228
"""Handle HTTP responses."""
229
if not ctx.options.basic_addon_enabled:
230
return
231
232
if flow.response:
233
# Add response header
234
flow.response.headers["X-BasicAddon-Response"] = "processed"
235
236
# Log response
237
ctx.log.info(f"Response: {flow.response.status_code} for {flow.request.url}")
238
239
def done(self):
240
"""Called when mitmproxy shuts down."""
241
ctx.log.info(f"BasicAddon processed {self.request_count} requests")
242
243
# Register the addon
244
addons = [BasicAddon()]
245
```
246
247
### Advanced Addon with Concurrent Processing
248
249
```python
250
from mitmproxy import http
251
from mitmproxy.script import concurrent
252
import mitmproxy.ctx as ctx
253
import asyncio
254
import aiohttp
255
import json
256
257
class APIAnalyzerAddon:
258
"""Advanced addon with concurrent processing for API analysis."""
259
260
def __init__(self):
261
self.api_calls = []
262
self.session = None
263
264
def load(self, loader):
265
"""Initialize addon with configuration."""
266
ctx.log.info("APIAnalyzerAddon loaded")
267
268
# Add configuration options
269
loader.add_option(
270
name="api_analyzer_webhook",
271
typespec=str,
272
default="",
273
help="Webhook URL for API analysis results"
274
)
275
276
loader.add_option(
277
name="api_analyzer_filter",
278
typespec=str,
279
default="/api/",
280
help="URL pattern to filter API calls"
281
)
282
283
async def setup_session(self):
284
"""Set up HTTP session for webhook calls."""
285
if not self.session:
286
self.session = aiohttp.ClientSession()
287
288
@concurrent
289
def request(self, flow: http.HTTPFlow) -> None:
290
"""Analyze API requests concurrently."""
291
# Filter API calls
292
filter_pattern = ctx.options.api_analyzer_filter
293
if filter_pattern not in flow.request.url:
294
return
295
296
# Extract API call information
297
api_call = {
298
"timestamp": flow.request.timestamp_start,
299
"method": flow.request.method,
300
"url": flow.request.url,
301
"headers": dict(flow.request.headers),
302
"content_type": flow.request.headers.get("content-type", ""),
303
"content_length": len(flow.request.content) if flow.request.content else 0
304
}
305
306
# Parse JSON request body
307
if "application/json" in api_call["content_type"]:
308
try:
309
api_call["json_body"] = flow.request.json()
310
except ValueError:
311
api_call["json_body"] = None
312
313
self.api_calls.append(api_call)
314
ctx.log.info(f"Analyzed API call: {flow.request.method} {flow.request.url}")
315
316
@concurrent
317
def response(self, flow: http.HTTPFlow) -> None:
318
"""Analyze API responses concurrently."""
319
filter_pattern = ctx.options.api_analyzer_filter
320
if filter_pattern not in flow.request.url:
321
return
322
323
# Find corresponding request analysis
324
for api_call in reversed(self.api_calls):
325
if (api_call["url"] == flow.request.url and
326
api_call["method"] == flow.request.method):
327
328
# Add response information
329
if flow.response:
330
api_call.update({
331
"status_code": flow.response.status_code,
332
"response_headers": dict(flow.response.headers),
333
"response_content_type": flow.response.headers.get("content-type", ""),
334
"response_length": len(flow.response.content) if flow.response.content else 0,
335
"response_time": flow.response.timestamp_end - flow.request.timestamp_start if flow.response.timestamp_end else None
336
})
337
338
# Parse JSON response
339
if "application/json" in api_call.get("response_content_type", ""):
340
try:
341
api_call["json_response"] = flow.response.json()
342
except ValueError:
343
api_call["json_response"] = None
344
345
# Send to webhook if configured
346
webhook_url = ctx.options.api_analyzer_webhook
347
if webhook_url:
348
asyncio.create_task(self.send_webhook(api_call, webhook_url))
349
350
break
351
352
async def send_webhook(self, api_call_data, webhook_url):
353
"""Send API analysis data to webhook."""
354
try:
355
await self.setup_session()
356
357
async with self.session.post(
358
webhook_url,
359
json=api_call_data,
360
headers={"Content-Type": "application/json"}
361
) as response:
362
if response.status == 200:
363
ctx.log.info(f"Webhook sent successfully for {api_call_data['url']}")
364
else:
365
ctx.log.warn(f"Webhook failed with status {response.status}")
366
367
except Exception as e:
368
ctx.log.error(f"Webhook error: {e}")
369
370
def done(self):
371
"""Cleanup when addon shuts down."""
372
if self.session:
373
asyncio.create_task(self.session.close())
374
375
ctx.log.info(f"APIAnalyzerAddon analyzed {len(self.api_calls)} API calls")
376
377
addons = [APIAnalyzerAddon()]
378
```
379
380
### Addon with State Management
381
382
```python
383
from mitmproxy import http
384
import mitmproxy.ctx as ctx
385
import sqlite3
386
import threading
387
from datetime import datetime
388
389
class TrafficLoggerAddon:
390
"""Addon that logs traffic to SQLite database with thread safety."""
391
392
def __init__(self):
393
self.db_path = "traffic_log.db"
394
self.lock = threading.Lock()
395
self.setup_database()
396
397
def setup_database(self):
398
"""Initialize SQLite database."""
399
with sqlite3.connect(self.db_path) as conn:
400
conn.execute("""
401
CREATE TABLE IF NOT EXISTS requests (
402
id INTEGER PRIMARY KEY AUTOINCREMENT,
403
timestamp REAL,
404
method TEXT,
405
url TEXT,
406
host TEXT,
407
path TEXT,
408
status_code INTEGER,
409
request_size INTEGER,
410
response_size INTEGER,
411
response_time REAL,
412
user_agent TEXT,
413
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
414
)
415
""")
416
conn.commit()
417
418
def load(self, loader):
419
"""Configure addon options."""
420
loader.add_option(
421
name="traffic_logger_enabled",
422
typespec=bool,
423
default=True,
424
help="Enable traffic logging to database"
425
)
426
427
loader.add_option(
428
name="traffic_logger_filter",
429
typespec=str,
430
default="",
431
help="Host filter for logging (empty = log all)"
432
)
433
434
def request(self, flow: http.HTTPFlow) -> None:
435
"""Log request initiation."""
436
if not ctx.options.traffic_logger_enabled:
437
return
438
439
# Apply host filter
440
host_filter = ctx.options.traffic_logger_filter
441
if host_filter and host_filter not in flow.request.host:
442
return
443
444
# Store request start time for later response time calculation
445
flow.metadata["log_start_time"] = datetime.now()
446
447
def response(self, flow: http.HTTPFlow) -> None:
448
"""Log completed request/response."""
449
if not ctx.options.traffic_logger_enabled:
450
return
451
452
# Apply host filter
453
host_filter = ctx.options.traffic_logger_filter
454
if host_filter and host_filter not in flow.request.host:
455
return
456
457
# Calculate response time
458
response_time = None
459
if "log_start_time" in flow.metadata:
460
start_time = flow.metadata["log_start_time"]
461
response_time = (datetime.now() - start_time).total_seconds()
462
463
# Prepare log data
464
log_data = {
465
"timestamp": flow.request.timestamp_start,
466
"method": flow.request.method,
467
"url": flow.request.url,
468
"host": flow.request.host,
469
"path": flow.request.path,
470
"status_code": flow.response.status_code if flow.response else None,
471
"request_size": len(flow.request.content) if flow.request.content else 0,
472
"response_size": len(flow.response.content) if flow.response and flow.response.content else 0,
473
"response_time": response_time,
474
"user_agent": flow.request.headers.get("User-Agent", "")
475
}
476
477
# Thread-safe database insert
478
with self.lock:
479
try:
480
with sqlite3.connect(self.db_path) as conn:
481
conn.execute("""
482
INSERT INTO requests
483
(timestamp, method, url, host, path, status_code,
484
request_size, response_size, response_time, user_agent)
485
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
486
""", (
487
log_data["timestamp"],
488
log_data["method"],
489
log_data["url"],
490
log_data["host"],
491
log_data["path"],
492
log_data["status_code"],
493
log_data["request_size"],
494
log_data["response_size"],
495
log_data["response_time"],
496
log_data["user_agent"]
497
))
498
conn.commit()
499
500
except sqlite3.Error as e:
501
ctx.log.error(f"Database error: {e}")
502
503
def get_stats(self):
504
"""Get traffic statistics from database."""
505
with self.lock:
506
try:
507
with sqlite3.connect(self.db_path) as conn:
508
cursor = conn.cursor()
509
510
# Get basic stats
511
cursor.execute("SELECT COUNT(*) FROM requests")
512
total_requests = cursor.fetchone()[0]
513
514
cursor.execute("SELECT COUNT(*) FROM requests WHERE status_code >= 400")
515
error_requests = cursor.fetchone()[0]
516
517
cursor.execute("SELECT AVG(response_time) FROM requests WHERE response_time IS NOT NULL")
518
avg_response_time = cursor.fetchone()[0]
519
520
return {
521
"total_requests": total_requests,
522
"error_requests": error_requests,
523
"avg_response_time": avg_response_time
524
}
525
526
except sqlite3.Error as e:
527
ctx.log.error(f"Database error: {e}")
528
return None
529
530
def done(self):
531
"""Log final statistics."""
532
stats = self.get_stats()
533
if stats:
534
ctx.log.info(f"TrafficLogger final stats: {stats}")
535
536
addons = [TrafficLoggerAddon()]
537
```
538
539
### Command-based Addon
540
541
```python
542
from mitmproxy import http, command
543
import mitmproxy.ctx as ctx
544
from typing import Sequence
545
546
class CommandAddon:
547
"""Addon that provides custom commands."""
548
549
def __init__(self):
550
self.blocked_hosts = set()
551
self.request_count = 0
552
553
@command.command("addon.block_host")
554
def block_host(self, host: str) -> None:
555
"""Block requests to a specific host."""
556
self.blocked_hosts.add(host)
557
ctx.log.info(f"Blocked host: {host}")
558
559
@command.command("addon.unblock_host")
560
def unblock_host(self, host: str) -> None:
561
"""Unblock requests to a specific host."""
562
self.blocked_hosts.discard(host)
563
ctx.log.info(f"Unblocked host: {host}")
564
565
@command.command("addon.list_blocked")
566
def list_blocked(self) -> Sequence[str]:
567
"""List all blocked hosts."""
568
return list(self.blocked_hosts)
569
570
@command.command("addon.clear_blocked")
571
def clear_blocked(self) -> None:
572
"""Clear all blocked hosts."""
573
count = len(self.blocked_hosts)
574
self.blocked_hosts.clear()
575
ctx.log.info(f"Cleared {count} blocked hosts")
576
577
@command.command("addon.stats")
578
def get_stats(self) -> str:
579
"""Get addon statistics."""
580
return f"Requests processed: {self.request_count}, Blocked hosts: {len(self.blocked_hosts)}"
581
582
def request(self, flow: http.HTTPFlow) -> None:
583
"""Handle requests with blocking logic."""
584
self.request_count += 1
585
586
if flow.request.host in self.blocked_hosts:
587
ctx.log.info(f"Blocked request to {flow.request.host}")
588
flow.response = http.Response.make(
589
status_code=403,
590
content=f"Host {flow.request.host} is blocked",
591
headers={"Content-Type": "text/plain"}
592
)
593
594
addons = [CommandAddon()]
595
```
596
597
### Addon Integration Example
598
599
```python
600
# Example of loading and using custom addons
601
602
# Save addon code to files, e.g., basic_addon.py, api_analyzer.py, etc.
603
604
# Load addons in mitmproxy:
605
# 1. Command line: mitmproxy -s basic_addon.py -s api_analyzer.py
606
# 2. Programmatically:
607
608
from mitmproxy.tools.main import mitmdump
609
from mitmproxy import master, options
610
from basic_addon import BasicAddon
611
from api_analyzer import APIAnalyzerAddon
612
613
def run_with_addons():
614
"""Run mitmproxy with custom addons."""
615
opts = options.Options(listen_port=8080)
616
617
# Create master with addons
618
m = master.Master(opts)
619
m.addons.add(BasicAddon())
620
m.addons.add(APIAnalyzerAddon())
621
622
try:
623
m.run()
624
except KeyboardInterrupt:
625
m.shutdown()
626
627
if __name__ == "__main__":
628
run_with_addons()
629
```