1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
# mitmux plugins
A plugin is any external process, in any language, that connects to
`mitmuxd`'s control socket and speaks the same JSON protocol the `mitmux`
TUI itself uses - there's no separate "plugin API," no interpreter
embedded in mitmuxd, and no in-process extension mechanism to trust.
This is deliberate: it keeps the daemon simple, lets a plugin be written
in whatever language actually suits the job (a JWT plugin doing crypto
work might want Python's `jwcrypto`; a fast passive scanner might want
Go), and means a plugin can be developed and tested against the exact
same socket the TUI is already using, with `mitmux` itself open in
another terminal watching what happens in real time.
`plugins/authcheck` is a real, working reference implementation - an
Autorize-style authorization checker (resends a captured request with
its auth header stripped, tags the entry if the response still
succeeds) - written to only ever exercise what's documented on this
page, not any of mitmux's own internal Go packages, specifically so it
proves this protocol is sufficient on its own. Worth reading alongside
this document, or just copying as a starting point.
## Connecting
The socket path is the same one `mitmux -socket` and `mitmuxd -socket`
use - `$XDG_RUNTIME_DIR/mitmux.sock` by default, else
`<config-dir>/mitmux.sock` (see the README's Platforms section for the
per-OS config directory). A plugin needs **two connections**, for two
different jobs:
1. A **subscribe connection** - long-lived, one-way, the daemon pushes
newly-captured entries to it as they happen. Send one `{"type":
"subscribe"}` request, then just keep reading `Response` objects off
the connection; no more requests are ever sent on it.
2. A **request/response connection** - send one JSON request object,
read back exactly one JSON response object, repeat. This is how a
plugin looks up entries, resends requests, and tags things - anything
that isn't the live feed.
Both are the same wire format: JSON values written directly to the
socket, no length prefix or newline delimiter required (Go's
`encoding/json` reads a stream and extracts one balanced JSON value at a
time; if your language's JSON library needs an explicit framing, treat
each object as ending where its braces balance - using a proper
streaming/incremental JSON parser, not naive line-splitting, is the
reliable way to consume this).
## Requests a plugin actually needs
The full protocol (`internal/ipc/ipc.go`'s `Request`/`Response` structs)
also covers everything the TUI itself does - search, export, rules,
scope, client certs, Intruder attacks, and so on. A plugin only needs a
handful of these:
- **`subscribe`** - see above. No fields. Response stream: each object
has `"type": "new"` and a `"new"` field (a `store.Summary`) for one
freshly-captured entry. **Slow consumers silently drop entries** - the
daemon never blocks proxying to wait for a plugin to keep up. If your
plugin needs to guarantee it never misses one, don't rely on the live
feed alone; periodically reconcile against `list`/`search` too.
- **`get`** - `{"type": "get", "id": <entry id>}`. Returns the full
entry (raw request/response bytes, exact/truncated flags, existing
tags) as `"detail"`.
- **`repeat`** - `{"type": "repeat", "scheme": "https", "host":
"example.com", "raw": <base64 raw bytes>}`. Sends `raw` to
`scheme://host` exactly as given - no normalization, no header
injection, no auto-fixed `Content-Length` - and records the exchange
to history with `source: "repeater"`. This is what an Autorize- or
Param-Miner-style plugin uses to send its own probe requests.
- **`list`** / **`search`** - read existing history, same filters the
TUI's own `/` search uses (`status:`, `source:`, `flagged:`, `tag:`,
free text). Useful for a plugin that reconciles past traffic on
startup rather than only watching what happens from here on.
- **`tag_entry`** - the actual plugin-integration primitive:
```json
{
"type": "tag_entry",
"id": 42,
"tag_plugin": "jwt-decoder",
"tag": "jwt",
"tag_data": "{\"header\":{...},\"payload\":{...}}"
}
```
`id` is the history entry being marked. `tag_plugin` is a
human-readable name your plugin picks for itself - purely
informational, not an identity or auth mechanism, since anything that
can reach the socket can claim any name (the socket itself is the
trust boundary - see Security below). `tag` is the short marker shown
as a badge in the TUI's history list and searchable via `tag:jwt`.
`tag_data` is an opaque string - conventionally JSON, since the TUI's
tag-detail view syntax-highlights it automatically if it parses as
JSON (same highlighter the pretty-printed response body view uses),
falling back to plain text otherwise - that a human reviewing the
entry later can open and read. Response: `{"type": "tag_entry",
"tag_id": <new tag's id>}`, or `{"type": "error", "error": "..."}`.
A tagged entry shows its tags as a comma-joined badge in the TUI's
history table (a new `Tags` column) and sortable via `o`/`O` like any
other column; from the detail view, `T` opens the full tag list, `enter`
on one shows its data.
## What's not here yet
Everything above covers a plugin that **observes and marks** traffic -
enough for a JWT decoder, an authorization-bypass checker (replay with
a different session token, diff the response, tag it if it looks like
an IDOR), a hidden-parameter prober, or a known-vulnerable-JS-library
scanner. It does not yet cover a plugin **acting interactively** on
demand from the TUI - e.g. JWT Editor's "re-sign this token with a
different key, right now, from the panel" - which needs a live
round-trip to a specific still-connected plugin process, not just
stored data. That's a deliberately separate, second protocol addition,
not yet built; see `PLAN.md`'s plugin section for where it's headed.
## Security
The control socket has no authentication of its own beyond whatever the
OS's Unix domain socket file permissions grant - the same trust
boundary the `mitmux` TUI itself already operates inside. A plugin
connected to it has the same level of access the TUI has: it can read
every captured request and response (which may contain credentials,
session tokens, anything else that crossed the proxy), resend traffic,
and tag entries. Only run plugins you trust, the same way you'd only
run any other local tool with access to your intercepted traffic.
|