Skip to content

Latest commit

 

History

History
63 lines (42 loc) · 3 KB

File metadata and controls

63 lines (42 loc) · 3 KB
title HCR062 — Prefer per-request headers over DefaultRequestHeaders
description Mutating HttpClient.DefaultRequestHeaders for per-request values races on shared clients from IHttpClientFactory.

HCR062

Prefer per-request headers over mutating DefaultRequestHeaders.

Why

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.

Bad

client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", accessToken);

return client.GetAsync("/orders", cancellationToken);
client.DefaultRequestHeaders.Add("X-Tenant", tenantId);
return client.PostAsync("/orders", content, cancellationToken);

Better

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);

Current Detection

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>.

Suppression

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.

References