# gitproto: dump outgoing POST request line+headers under HTTP_TRACE

`6e9bc8d`→[main](/content/gh/entireio/git-sync/commits/main/index.html)·

Soph·1mo ago·1 file·+45 added/-0 removed

Adds httputil.DumpRequestOut on POST requests when GITSYNC_HTTP_TRACE is enabled, so the actual wire-format request (Transfer-Encoding, Content-Length, headers added by Go's transport) is visible alongside the connection-level trace. Authorization values are redacted so credentials don't leak into shared logs.

Body is not consumed (body=false). The dump runs once per POST and goes to stderr like the rest of the trace.

Use case: when a server behaves unexpectedly on a POST, the connection-level trace tells you which TCP/TLS connection was used but not what the request looked like on the wire — Transfer-Encoding, Content-Length, headers Go's transport added. This fills that gap.

## Changes

1

- internal/gitproto

- Msmarthttp.go+45

```
8 unmodified lines

9
10
11
12
13
14
15
115 unmodified lines

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
217 unmodified lines

394
395
396
397
398
399
400
401
402
403

8 unmodified lines

"mime"
	"net/http"
	"net/http/httptrace"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
115 unmodified lines

return httptrace.WithClientTrace(ctx, trace)
}

// dumpOutgoingRequest prints the wire-format request line and headers for
// req to stderr, prefixed with label. The body is not consumed (passes
// body=false to httputil.DumpRequestOut), but Transfer-Encoding and
// Content-Length will reflect what Go's transport would actually send.
// Useful when a server behaves unexpectedly on a POST and you need to
// see what the request looked like at the protocol level — the
// connection-level trace tells you which TCP/TLS connection was used
// but not what was written on it. Best-effort: dump errors are
// surfaced as a single line so a transient dump failure doesn't mask
// the underlying request.
func dumpOutgoingRequest(req *http.Request, label string) {
	dump, err := httputil.DumpRequestOut(req, false)
	if err != nil {
		fmt.Fprintf(os.Stderr, "[httptrace] %s dump error: %v\n", label, err)
		return
	}
	fmt.Fprintf(os.Stderr, "[httptrace] %s outgoing request:\n%s\n", label, redactAuthorization(dump))
}

// redactAuthorization scrubs any Authorization header value from a dumped
// HTTP request so the credentials don't leak into stderr when
// GITSYNC_HTTP_TRACE is enabled in environments with shoulder-surfers,
// pasted-into-tickets logs, or shared shells.
func redactAuthorization(dump []byte) []byte {
	const header = "Authorization:"
	idx := bytes.Index(dump, []byte(header))
	if idx < 0 {
		return dump
	}
	end := bytes.IndexByte(dump[idx:], '\n')
	if end < 0 {
		end = len(dump) - idx
	}
	out := make([]byte, 0, len(dump))
	out = append(out, dump[:idx]...)
	out = append(out, []byte(header+" [REDACTED]")...)
	out = append(out, dump[idx+end:]...)
	return out
}

// AuthMethod authorizes outbound HTTP requests for a remote. It is satisfied
// by *transporthttp.BasicAuth and *transporthttp.TokenAuth, whose Authorizer
// methods replaced the AuthMethod interface that go-git removed in v6 alpha.2.
217 unmodified lines

}
	ApplyAuth(req, c.Auth)

if httpTraceEnabled() {
		dumpOutgoingRequest(req, "POST "+service)
	}

res, err := c.HTTP.Do(req)
	if err != nil {
		return nil, fmt.Errorf("post RPC: %w", err)
	}
