diff options
| author | srdusr <[email protected]> | 2026-05-15 19:48:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-05-15 19:48:00 +0200 |
| commit | dd1621c0077e079e0693f16fe83f17d50338216e (patch) | |
| tree | 411a06d723a16aff1077d5d3e724680436aac85a /cmd | |
| parent | d7d50ae9902d69b2e52d446684b5ab01a5ecab1e (diff) | |
| download | mitmux-dd1621c0077e079e0693f16fe83f17d50338216e.tar.gz mitmux-dd1621c0077e079e0693f16fe83f17d50338216e.zip | |
Stop mislabeling truncated captures as "exact"
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.
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/mitmux/main.go | 21 |
1 files changed, 15 insertions, 6 deletions
diff --git a/cmd/mitmux/main.go b/cmd/mitmux/main.go index 75f4410..83e59cd 100644 --- a/cmd/mitmux/main.go +++ b/cmd/mitmux/main.go @@ -1532,8 +1532,8 @@ func (m *model) detailView() string { b.WriteString(titleStyle.Render(title)) b.WriteString("\n") - reqLabel := fmt.Sprintf("Request (%d bytes%s)", len(m.detail.RequestRaw), exactSuffix(m.detail.RequestExact)) - respLabel := fmt.Sprintf("Response (%d bytes%s)", len(m.detail.ResponseRaw), exactSuffix(m.detail.ResponseExact)) + reqLabel := fmt.Sprintf("Request (%d bytes%s)", len(m.detail.RequestRaw), exactSuffix(m.detail.RequestExact, m.detail.RequestTruncated)) + respLabel := fmt.Sprintf("Response (%d bytes%s)", len(m.detail.ResponseRaw), exactSuffix(m.detail.ResponseExact, m.detail.ResponseTruncated)) if m.prettyMode { respLabel += " [pretty]" } @@ -1580,7 +1580,7 @@ func (m *model) repeaterView() string { reqLabel := "Request (editable)" respLabel := "Response" if t.result != nil { - respLabel = fmt.Sprintf("Response (%d bytes%s)", len(t.result.ResponseRaw), exactSuffix(t.result.ResponseExact)) + respLabel = fmt.Sprintf("Response (%d bytes%s)", len(t.result.ResponseRaw), exactSuffix(t.result.ResponseExact, t.result.ResponseTruncated)) } if t.focus == focusRequest { b.WriteString(tabActive.Render(reqLabel)) @@ -1783,11 +1783,20 @@ func intrudeRowsFor(rs []ipc.IntrudeResultMsg) []table.Row { return rows } -func exactSuffix(exact bool) string { - if exact { +// exactSuffix labels a capture's trust level. truncated is a distinct +// state from a plain "reconstructed" (HTTP/2, which never had wire-exact +// bytes to begin with): these ARE real wire bytes, just cut short by +// hitting the capture size cap - telling them apart matters for a tool +// whose value proposition is "raw bytes are the source of truth." +func exactSuffix(exact, truncated bool) string { + switch { + case exact: return ", exact" + case truncated: + return ", truncated (hit capture size limit)" + default: + return ", reconstructed" } - return ", reconstructed" } // detailContent is what actually gets shown in the detail viewport: |