srdusr
aboutsummaryrefslogtreecommitdiffstats

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, plugins/paramminer, plugins/jslibscan, and plugins/bpscanner are real, working reference implementations - an Autorize-style authorization checker (resends a captured request with its auth header stripped, tags the entry if the response still succeeds), a Param Miner-style hidden parameter prober (probes a small wordlist of candidate query parameters, tags the entry if any noticeably change the response), a Retire.js-style passive scanner for known-vulnerable JS library versions (reads response bodies already captured by ordinary proxying, no probing at all), and a Backslash Powered Scanner-style generic injection detector (mutates each existing query parameter's value with syntactically-significant characters vs. an equal-length inert control, tags the entry if a stable marker survives one but not the other) - all written to only ever exercise what's documented on this page, not any of mitmux's own internal Go packages, specifically so they prove this protocol is sufficient on its own. Worth reading alongside this document, or just copying as a starting point. jslibscan in particular is the simplest possible plugin shape - subscribe, inspect, tag, nothing else - worth starting from if a plugin idea doesn't need to send any traffic of its own.

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. Every probe becomes its own history row - a plugin sending many probes per entry (Param Miner's wordlist, say) will visibly fill the history view with them. That's intentional (every resend is auditable, same as a human using Repeater by hand), and source:proxy in search filters them back out when they're just noise.
  • 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.