@@ -34,6 +34,51 @@ public class Keywords
3434 public const EventKeywords HttpRequestAndResponse = ( EventKeywords ) 1 ;
3535 }
3636
37+ // Ordered list of headers extracted and forwarded positionally to Event(1).
38+ // IMPORTANT: The order of entries here matches the field order declared by [Event(1)]
39+ // and the `{index}` placeholders in its Message template. Do not reorder, insert, or
40+ // remove entries without updating Event(1) and the redaction logic in Request(...).
41+ // The Authorization slot is populated from the raw header and then overwritten with
42+ // "REDACTED" in the [NonEvent] wrapper before being emitted to ETW.
43+ private static readonly string [ ] RequestHeaderKeysToExtract =
44+ {
45+ HttpConstants . HttpHeaders . Accept ,
46+ HttpConstants . HttpHeaders . Authorization , // SECURITY: redacted to "REDACTED" in Request(...) before ETW emission. Do not remove.
47+ HttpConstants . HttpHeaders . ConsistencyLevel ,
48+ HttpConstants . HttpHeaders . ContentType ,
49+ HttpConstants . HttpHeaders . ContentEncoding ,
50+ HttpConstants . HttpHeaders . ContentLength ,
51+ HttpConstants . HttpHeaders . ContentLocation ,
52+ HttpConstants . HttpHeaders . Continuation ,
53+ HttpConstants . HttpHeaders . EmitVerboseTracesInQuery ,
54+ HttpConstants . HttpHeaders . EnableScanInQuery ,
55+ HttpConstants . HttpHeaders . ETag ,
56+ HttpConstants . HttpHeaders . HttpDate ,
57+ HttpConstants . HttpHeaders . IfMatch ,
58+ HttpConstants . HttpHeaders . IfNoneMatch ,
59+ HttpConstants . HttpHeaders . IndexingDirective ,
60+ HttpConstants . HttpHeaders . KeepAlive ,
61+ HttpConstants . HttpHeaders . OfferType ,
62+ HttpConstants . HttpHeaders . PageSize ,
63+ HttpConstants . HttpHeaders . PreTriggerExclude ,
64+ HttpConstants . HttpHeaders . PreTriggerInclude ,
65+ HttpConstants . HttpHeaders . PostTriggerExclude ,
66+ HttpConstants . HttpHeaders . PostTriggerInclude ,
67+ HttpConstants . HttpHeaders . ProfileRequest ,
68+ HttpConstants . HttpHeaders . ResourceTokenExpiry ,
69+ HttpConstants . HttpHeaders . SessionToken ,
70+ HttpConstants . HttpHeaders . SetCookie ,
71+ HttpConstants . HttpHeaders . Slug ,
72+ HttpConstants . HttpHeaders . UserAgent ,
73+ HttpConstants . HttpHeaders . XDate
74+ } ;
75+
76+ // Index of the Authorization header in RequestHeaderKeysToExtract, computed once at class
77+ // initialization so the per-request redaction path in Request(...) does not pay an O(n)
78+ // Array.IndexOf lookup on every HTTP call.
79+ private static readonly int AuthorizationHeaderIndex
80+ = Array . IndexOf ( RequestHeaderKeysToExtract , HttpConstants . HttpHeaders . Authorization ) ;
81+
3782 [ NonEvent ]
3883 private unsafe void WriteEventCoreWithActivityId ( Guid activityId , int eventId , int eventDataCount , EventSource . EventData * dataDesc )
3984 {
@@ -270,45 +315,48 @@ private unsafe void Request(
270315 }
271316 }
272317
318+ // SECURITY: The Authorization header contains a live credential (master-key HMAC
319+ // signature, resource token, or AAD Bearer access token). It must never be written
320+ // to the ETW event payload where any listener subscribing to the "DocumentDBClient"
321+ // EventSource at Verbose level (e.g., Geneva MonitoringAgent, PerfView, dotnet-trace)
322+ // would capture it. Replace the value with a fixed placeholder while preserving the
323+ // 33-field ETW manifest so existing consumers remain compatible. The IsNullOrEmpty
324+ // guard preserves the existing "" semantics for requests that never had an
325+ // Authorization header attached (e.g. internal plumbing); we only overwrite when a
326+ // real value is present. Factored out as an internal helper so the redaction logic
327+ // can be unit-tested directly without relying on ETW listener wiring (which is
328+ // inherently racy under parallel test execution).
329+ internal static void RedactSensitiveHeaderValues ( string [ ] headerValues )
330+ {
331+ if ( headerValues == null )
332+ {
333+ return ;
334+ }
335+
336+ // AuthorizationHeaderIndex is computed once at class initialization (see field
337+ // above); the Debug.Assert catches any future refactor that removes Authorization
338+ // from RequestHeaderKeysToExtract.
339+ System . Diagnostics . Debug . Assert (
340+ AuthorizationHeaderIndex >= 0 ,
341+ "Authorization must be present in RequestHeaderKeysToExtract so the redaction below takes effect." ) ;
342+
343+ if ( AuthorizationHeaderIndex >= 0
344+ && headerValues . Length > AuthorizationHeaderIndex
345+ && ! string . IsNullOrEmpty ( headerValues [ AuthorizationHeaderIndex ] ) )
346+ {
347+ headerValues [ AuthorizationHeaderIndex ] = "REDACTED" ;
348+ }
349+ }
350+
273351 [ NonEvent ]
274352 public void Request ( Guid activityId , Guid localId , string uri , string resourceType , HttpRequestHeaders requestHeaders )
275353 {
276354 if ( this . IsEnabled ( EventLevel . Verbose , Keywords . HttpRequestAndResponse ) )
277355 {
278- string [ ] keysToExtract =
279- {
280- HttpConstants . HttpHeaders . Accept ,
281- HttpConstants . HttpHeaders . Authorization ,
282- HttpConstants . HttpHeaders . ConsistencyLevel ,
283- HttpConstants . HttpHeaders . ContentType ,
284- HttpConstants . HttpHeaders . ContentEncoding ,
285- HttpConstants . HttpHeaders . ContentLength ,
286- HttpConstants . HttpHeaders . ContentLocation ,
287- HttpConstants . HttpHeaders . Continuation ,
288- HttpConstants . HttpHeaders . EmitVerboseTracesInQuery ,
289- HttpConstants . HttpHeaders . EnableScanInQuery ,
290- HttpConstants . HttpHeaders . ETag ,
291- HttpConstants . HttpHeaders . HttpDate ,
292- HttpConstants . HttpHeaders . IfMatch ,
293- HttpConstants . HttpHeaders . IfNoneMatch ,
294- HttpConstants . HttpHeaders . IndexingDirective ,
295- HttpConstants . HttpHeaders . KeepAlive ,
296- HttpConstants . HttpHeaders . OfferType ,
297- HttpConstants . HttpHeaders . PageSize ,
298- HttpConstants . HttpHeaders . PreTriggerExclude ,
299- HttpConstants . HttpHeaders . PreTriggerInclude ,
300- HttpConstants . HttpHeaders . PostTriggerExclude ,
301- HttpConstants . HttpHeaders . PostTriggerInclude ,
302- HttpConstants . HttpHeaders . ProfileRequest ,
303- HttpConstants . HttpHeaders . ResourceTokenExpiry ,
304- HttpConstants . HttpHeaders . SessionToken ,
305- HttpConstants . HttpHeaders . SetCookie ,
306- HttpConstants . HttpHeaders . Slug ,
307- HttpConstants . HttpHeaders . UserAgent ,
308- HttpConstants . HttpHeaders . XDate
309- } ;
356+ string [ ] headerValues = Helpers . ExtractValuesFromHTTPHeaders ( requestHeaders , RequestHeaderKeysToExtract ) ;
357+
358+ RedactSensitiveHeaderValues ( headerValues ) ;
310359
311- string [ ] headerValues = Helpers . ExtractValuesFromHTTPHeaders ( requestHeaders , keysToExtract ) ;
312360 this . Request ( activityId , localId , uri , resourceType , headerValues [ 0 ] , headerValues [ 1 ] , headerValues [ 2 ] , headerValues [ 3 ] , headerValues [ 4 ] ,
313361 headerValues [ 5 ] , headerValues [ 6 ] , headerValues [ 7 ] , headerValues [ 8 ] , headerValues [ 9 ] , headerValues [ 10 ] , headerValues [ 11 ] , headerValues [ 12 ] ,
314362 headerValues [ 13 ] , headerValues [ 14 ] , headerValues [ 15 ] , headerValues [ 16 ] , headerValues [ 17 ] , headerValues [ 18 ] , headerValues [ 19 ] , headerValues [ 20 ] ,
0 commit comments