| title | HCR062 — Prefer per-request headers over DefaultRequestHeaders |
|---|---|
| description | Mutating HttpClient.DefaultRequestHeaders for per-request values races on shared clients from IHttpClientFactory. |
Prefer per-request headers over mutating DefaultRequestHeaders.
HttpClient.DefaultRequestHeaders applies to every request sent by that client. Mutating it for per-request data can leak tenant, user, authorization, correlation, or feature headers across requests when the client is reused.
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
return client.GetAsync("/orders", cancellationToken);client.DefaultRequestHeaders.Add("X-Tenant", tenantId);
return client.PostAsync("/orders", content, cancellationToken);using var request = new HttpRequestMessage(HttpMethod.Get, "/orders");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return client.SendAsync(request, cancellationToken);using var request = new HttpRequestMessage(HttpMethod.Post, "/orders")
{
Content = content
};
request.Headers.Add("X-Tenant", tenantId);
return client.SendAsync(request, cancellationToken);The implementation reports visible mutations under a real System.Net.Http.HttpClient.DefaultRequestHeaders receiver. It catches direct header mutations such as Add(...), TryAddWithoutValidation(...), Remove(...), Clear(...), and ParseAdd(...), nested collection mutations such as client.DefaultRequestHeaders.Accept.Add(...), and assignments such as client.DefaultRequestHeaders.Authorization = ....
The receiver and resolved mutator method are validated with Roslyn type information when available, with syntactic fallback for unresolved snippets that visibly declare an HttpClient receiver. Reads such as client.DefaultRequestHeaders.Contains(...) are skipped, request-scoped HttpRequestMessage.Headers mutations are skipped, and resolved custom HttpClient or header-extension lookalikes are skipped.
One-time configuration sites are skipped: mutations on the HttpClient parameter of an AddHttpClient/ConfigureHttpClient configure delegate, and mutations on the injected client (parameter or instance member) inside the constructor of a type registered via AddHttpClient<T>.
Suppress only for process-wide static headers that are intentionally constant for the entire lifetime of the client, such as a fixed product header. Prefer setting those once during client construction or typed-client initialization.