srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/PLUGINS.md
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2026-08-04 09:35:00 +0200
committersrdusr <[email protected]>2026-08-04 09:35:00 +0200
commitdde73a349a68f942a5bd89b27ac980d7973148e0 (patch)
treecec48acc8d65d2610f74752eab4db0705a2d38a1 /PLUGINS.md
parent4c4c8dd4983092ffa5c6602ed070f401bbbf74f1 (diff)
downloadmitmux-dde73a349a68f942a5bd89b27ac980d7973148e0.tar.gz
mitmux-dde73a349a68f942a5bd89b27ac980d7973148e0.zip
Plugin protocol foundation: tag_entry, tag search/sort, TUI tag view
The prerequisite for the plugin ecosystem: any external process - any language - that can reach the control socket can now tag a history entry with a short string marker and an opaque JSON data blob, stored in a new entry_tags table rather than requiring the plugin stay connected for a later live round-trip. A plugin does its analysis once; the data it attaches is what a human reviewing the entry later actually sees. internal/ipc: new "tag_entry" request (id, tag_plugin, tag, tag_data) and EntryDetail.Tags (the full record for one entry, populated by "get"). internal/store: entry_tags table, EntryTag struct, AddEntryTag/ ListEntryTags, a comma-joined Tags aggregate added to List/Search via a correlated subquery (cheap enough per row that showing a tag badge in the history list needs no N+1 query), and a new tag: search filter alongside the existing status:/source:/flagged:. TUI: a Tags column in the history table (sortable via o/O, the eighth sort column), T from detail view opens a tag list (mirroring the WebSocket-messages view's table-then-detail-viewport pattern), enter on one shows its data - JSON-colorized via the existing jsoncolor.go if it parses as JSON, sanitized plain text otherwise. Also fixed a pre-existing gap while touching this: the WebSocket-messages view never got mouse wheel support when it shipped; wired both it and the new tags view up together. PLUGINS.md documents the wire protocol for non-Go plugin authors - connection model (subscribe vs request/response), the handful of request types a plugin actually needs, and the trust boundary (the socket has no auth beyond OS file permissions, same as the TUI's own access). PLAN.md records the architecture decision (external process over an embedded scripting language - mirrors the daemon/TUI split already in place, no interpreter to sandbox, any language) and groups ~20 researched Burp extensions/Pro features into what Phase 1 already covers (Autorize, Param Miner, Backslash Powered Scanner, Retire.js - all just subscribe+repeat+tag, no new capability needed), what needs a second protocol addition (JWT Editor, SAML Raider - live RPC to a specific connected plugin for interactive actions like re-signing), and what deserves its own separate project rather than a plugin (active vulnerability scanning, Collaborator/OAST, a crawler). Verified live end to end against a real daemon: a throwaway program simulating a real plugin tagged a captured entry with structured JWT data over the actual wire protocol; confirmed the tag badge, tag: search filter, and full tag record all round-tripped correctly through List/Search/Get. Confirmed in the TUI itself (tmux, real keystrokes): the Tags column renders, T opens the tag list, entering it shows the JSON data with real ANSI-verified syntax highlighting (not just eyeballed), and tag: search filtering works from the history list.
Diffstat (limited to 'PLUGINS.md')
-rw-r--r--PLUGINS.md117
1 files changed, 117 insertions, 0 deletions
diff --git a/PLUGINS.md b/PLUGINS.md
new file mode 100644
index 0000000..bb46461
--- /dev/null
+++ b/PLUGINS.md
@@ -0,0 +1,117 @@
+# 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.
+
+## 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.