<feed xmlns='http://www.w3.org/2005/Atom'>
<title>mitmux/internal/proxy/capture.go, branch main</title>
<subtitle>Terminal-based intercepting HTTP proxy.
</subtitle>
<id>https://srdusr.com/git/mitmux/atom?h=main</id>
<link rel='self' href='https://srdusr.com/git/mitmux/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/mitmux/'/>
<updated>2026-05-15T17:48:00+00:00</updated>
<entry>
<title>Stop mislabeling truncated captures as "exact"</title>
<updated>2026-05-15T17:48:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2026-05-15T17:48:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/mitmux/commit/?id=dd1621c0077e079e0693f16fe83f17d50338216e'/>
<id>urn:sha1:dd1621c0077e079e0693f16fe83f17d50338216e</id>
<content type='text'>
internal/proxy/tee.go's teeConn silently drops bytes past
maxCaptureBytes (10 MiB) but callers unconditionally marked the result
"exact" anyway. Confirmed live: proxying a 15 MiB response worked
correctly end-to-end (the client got the full, real 15 MiB - proxying
itself is unbounded, only storage is capped), but the stored history
entry was exactly 10485760 bytes with response_exact=1 still set. For a
tool whose core value proposition is "raw bytes are the source of
truth," a silently truncated capture presented as complete could hide
the very evidence a smuggling or parser-differential investigation is
looking for in the tail of a large body - and give false confidence
that it isn't there.

teeConn.Take() now returns (data, truncated) instead of just data;
truncated is true whenever a Read had to drop bytes because the buffer
was already at cap. Every caller (proxy.go's forward() on both the
request and response side, repeat.go's sendRaw for Repeater/Intruder)
now folds truncated into exact - a truncated capture is never marked
exact - and additionally threads a distinct RequestTruncated/
ResponseTruncated bool through store.Entry, ipc.EntryDetail, and the
TUI, since "truncated" and "reconstructed" (HTTP/2, which never had
wire-exact bytes to begin with) are different situations worth telling
apart: a truncated capture is still real wire bytes, just incomplete,
not a synthesized reconstruction. The detail/repeater views now show
"truncated (hit capture size limit)" specifically rather than lumping
it in with "reconstructed", which would have implied more
transformation happened than actually did.

Schema: history gains request_truncated/response_truncated columns via
the same ALTER-TABLE-and-ignore-duplicate-column pattern already used
for source/flagged, so existing databases upgrade in place.

Verified live: a target server returning a 15 MiB body (over the 10 MiB
cap) proxied through cleanly - full body reached the client - while the
stored entry shows length=10485760, response_exact=0,
response_truncated=1 (previously would have shown response_exact=1);
the TUI's Detail view correctly displays "Response (10485760 bytes,
truncated (hit capture size limit))" instead of "exact".

go build/vet/gofmt/test/mod tidy all clean.
</content>
</entry>
<entry>
<title>Fix hang and data-race bugs found while re-verifying steps 1-5</title>
<updated>2024-09-19T21:57:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-09-19T21:57:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/mitmux/commit/?id=aae93b4575e10d223c6cdd8722ca0cce2d47397c'/>
<id>urn:sha1:aae93b4575e10d223c6cdd8722ca0cce2d47397c</id>
<content type='text'>
Audited every file in the proxy/store/ipc/TUI stack before starting
step 6, per request. Found and fixed three real bugs in already-shipped
code, all confirmed with live tests (including a race-detector build)
rather than just read:

1. No timeout covered the write-request/read-response phase of an
   upstream exchange, in either the main proxy path (roundTripH1/
   roundTripH2) or Repeater - only the dial itself was bounded. A
   server that accepted the connection and then never finished
   responding hung the request forever. Fixed with conn.SetDeadline
   after a successful dial in both forward() and Repeat() (new
   upstreamTimeout constant, 60s). Verified against a real hung TCP
   listener: the daemon returned a clean "i/o timeout" error at exactly
   60s instead of hanging.

2. ipc.Client shared one connection/encoder/decoder with no locking.
   Bubble Tea dispatches each request as its own goroutine, and
   viewList's 'r' key doesn't change mode while its loadDetail call is
   in flight - pressing it again (or 'enter' on another row) before the
   first response arrives calls Get/List/Repeat concurrently on the same
   connection, which can interleave JSON on the wire or hand one call
   another's response. Fixed with a mutex serializing round trips.
   Stress-tested with rapid overlapping key input against a -race build
   of both binaries: no warnings, no corruption.

3. The IPC "subscribe" handler only noticed a disconnected client when
   the next broadcast's Encode failed - a subscriber that quit while the
   daemon was otherwise idle leaked its goroutine and channel
   indefinitely. Fixed by reading the connection in the background too,
   so disconnection is detected immediately regardless of traffic.

Also removed a dead, misleading parameter: captureResponse took a *teeConn
it was never actually called with (the exact-capture path is handled
directly in forward()), so the branch using it was unreachable.

Re-verified all five prior steps end-to-end against a fresh build:
plain HTTP, HTTPS H1.1/H2/untrusted-CA-rejection, exact vs reconstructed
capture flags cross-checked directly in SQLite, Repeater over both HTTP
and HTTPS, and search (plain text, dotted domains, hyphenated terms,
column filters) - all correct.
</content>
</entry>
<entry>
<title>History view: SQLite storage, daemon/TUI split over Unix socket</title>
<updated>2024-02-13T22:37:00+00:00</updated>
<author>
<name>srdusr</name>
<email>99972264+srdusr@users.noreply.github.com</email>
</author>
<published>2024-02-13T22:37:00+00:00</published>
<link rel='alternate' type='text/html' href='https://srdusr.com/git/mitmux/commit/?id=8d15c2e0b326933f8fc912e3b13f37e78a9bc0b6'/>
<id>urn:sha1:8d15c2e0b326933f8fc912e3b13f37e78a9bc0b6</id>
<content type='text'>
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).
</content>
</entry>
</feed>
