| Age | Commit message (Collapse) | Author | Files | Lines |
|
Next item off the "worth considering" list from the Burp/ZAP/Caido gap
research. Mark an entry with 'c' (from the history list or detail
view - no fetch yet, just remembers the ID), then 'c' on a different
entry fetches both and opens a colored unified diff of either side's
request or response, tab to switch between them.
Unified (git-diff style: +/- prefixed lines) rather than Burp's
side-by-side two-pane layout - a two-column view fights terminal width
for anything but a wide window, and unified reuses the same scrollable
viewport pattern already used everywhere else in this TUI rather than
needing new layout machinery. Uses github.com/pmezard/go-difflib
(SequenceMatcher-based, a tested port of Python's difflib) rather than
hand-rolling LCS/Myers diff, which has real edge cases worth not
reinventing. CRLF is normalized to LF before diffing - display-only,
same reasoning as the JSON pretty-printer - so an HTTP/1.1 exact
capture doesn't show every single line as changed purely from an
invisible trailing \r.
Verified live against two real, distinctly different captured POST
requests (different form bodies, different Content-Length): the request
diff correctly isolated exactly the two changed lines with the
unchanged headers shown as context, colors confirmed via raw ANSI
codes in the captured pane output (red 203 for removed, green 42 for
added) rather than assumed from the code, and the response tab showed
a correct independent diff of the two responses (Date header, JSON
body). Also confirmed the "same entry marked twice" path shows a hint
rather than silently doing something confusing.
|
|
Implements build-order step 4, the feature the plan calls out as used
daily. internal/proxy/repeat.go adds Server.Repeat(scheme, host, raw):
dials fresh (HTTP/1.1-only - raw edited text has no equivalent in
HTTP/2's binary framing), writes raw exactly as given with no framing
correction or header injection, and captures the exact response bytes.
This is deliberately separate from forward()'s parsed-*http.Request path
since Repeater's entire point is letting a malformed/edited request
reach the wire unmodified.
Repeater sends are recorded to the same history table as proxy traffic
(added a "source" column: "proxy" vs "repeater") so they show up in the
unified history view and the live subscribe stream, not a separate silo.
internal/ipc gains a "repeat" request/response pair; cmd/mitmuxd wires
proxy.Server into ipc.NewServer via a small Repeater interface so the
daemon keeps owning all network I/O and the TUI stays a thin client.
cmd/mitmux gains a repeater view (bubbles/textarea for the editable raw
request, a read-only viewport for the response), reachable with 'r' from
either the list or detail view, ctrl+r to send.
One real bug found via testing: bubbles/textarea only understands LF,
but HTTP/1.1 requires CRLF, so loading raw bytes straight into it split
each line in two on render. Fixed by normalizing CRLF<->LF at the editor
boundary only (load: strip \r; send: restore it) - documented as a
narrow, known trade-off for bodies with their own embedded LF line
breaks, which is the cost of being able to edit raw HTTP as text at all.
Verified live: edited and sent a plain-HTTP repeater request (confirmed
in SQLite that the edit - including an intentional extra blank line from
imprecise cursor navigation during testing - went out completely
unmodified, which is the correct behavior: mitmux must never "fix" what
the user typed), and sent an HTTPS repeater request against a freshly
captured entry, both getting real 200 responses with exact response
bytes back.
|
|
Implements build-order step 3. Adds:
- internal/store: SQLite (WAL, single-writer) history table, raw
request/response blobs plus metadata for the list view.
- internal/proxy: request/response capture wired into forward(). HTTP/1.1
legs are captured byte-exact via a teeConn that records wire bytes as
they're read, taken right after the message is fully drained (so no
manual re-reading/replaying is needed - RoundTrip's own streaming does
the draining). HTTP/2 legs (no meaningful "raw bytes" of their own -
multiplexed, HPACK-compressed framing) are reconstructed instead, and
marked as such in storage.
- internal/ipc: JSON-over-Unix-socket protocol between mitmuxd (owns the
proxy and the DB) and any client - list/get for queries, subscribe for
a live push stream of newly captured entries. Keeps the proxy engine
independent of the UI, per the architecture sketch.
- cmd/mitmux: Bubble Tea TUI - a live-updating history table and a
request/response detail view with raw bytes.
Two real bugs surfaced during testing and got fixed before commit:
1. http.Transport's HTTP/2 auto-dispatch does a literal *tls.Conn type
assertion on the dialed connection; wrapping it in a capturing teeConn
broke that silently, and HTTP/2 framing got parsed as HTTP/1.1 text.
Fixed by dropping http.Transport for the upstream leg entirely in
favor of an explicit per-protocol round trip (see PLAN.md stack note).
2. singleConnListener wrapped the client teeConn *inside* a
closeSignalConn, so ConnContext's type assertion for it silently
failed and HTTP/1.1 client-side capture never activated. Fixed the
wrap order; verified via direct SQLite inspection that request_exact
flips back to 1 and the stored bytes are genuinely wire-exact
(preserved chunked-encoding framing, original header casing/order).
Verified live: plain HTTP, HTTPS H1.1, HTTPS H2, and a POST with a body,
checked against the raw stored bytes directly in SQLite; IPC list/get/
subscribe against a throwaway client; and the TUI driven end-to-end in a
tmux session (list, detail view, tab between request/response, live
update on a new request while sitting on the list).
|
|
Implements build-order step 2. CA gains LeafFor(host), signing and
caching per-host leaf certificates on demand. The proxy's CONNECT
handler now terminates TLS with the client using a matching leaf cert
instead of tunneling raw bytes, and forwards each request upstream
over its own independently negotiated TLS connection.
Client-side and upstream-side ALPN are negotiated separately rather
than one being forced to mirror the other: an http.Transport configured
via http2.ConfigureTransport auto-bridges HTTP/1.1 and HTTP/2 on each
side independently, so e.g. an HTTP/1.1-only client reaching an
HTTP/2-preferring origin still works instead of failing the handshake
(caught by testing curl --http1.1 against example.com before this fix).
Verified live: plain HTTP passthrough, HTTPS with default (H2) and
forced HTTP/1.1 clients, and that requests without the mitmux CA
trusted are correctly rejected.
|
|
Implements build-order step 1: headless proxy daemon (mitmuxd) with
plaintext HTTP passthrough and raw CONNECT tunneling, plus root CA
generation/persistence for later TLS interception. Verified live
against real HTTP and HTTPS requests through the proxy.
|