@@ -173,6 +173,46 @@ where
173173 builder. send ( ) ;
174174}
175175
176+ /// Request headers whose values may contain credentials or user-identifying
177+ /// data and so must not be recorded on Sentry events. Compared case-insensitively.
178+ const REDACTED_HEADERS : & [ & str ] = & [
179+ "authorization" ,
180+ "proxy-authorization" ,
181+ "cookie" ,
182+ "x-client-state" ,
183+ "x-keyid" ,
184+ "x-forwarded-for" ,
185+ ] ;
186+
187+ /// Placeholder substituted for the value of a [`REDACTED_HEADERS`] header.
188+ const REDACTED_HEADER_VALUE : & str = "[redacted]" ;
189+
190+ /// Return the header value to record on a Sentry event, substituting a
191+ /// placeholder for headers whose values may carry credentials or
192+ /// user-identifying data.
193+ fn sentry_header_value ( name : & str , value : & str ) -> String {
194+ if REDACTED_HEADERS . contains ( & name. to_ascii_lowercase ( ) . as_str ( ) ) {
195+ REDACTED_HEADER_VALUE . to_owned ( )
196+ } else {
197+ value. to_owned ( )
198+ }
199+ }
200+
201+ /// Redact sensitive request-header values on a Sentry event in place.
202+ ///
203+ /// Intended to be called from a `before_send` hook so that every event is
204+ /// scrubbed before transmission — regardless of where it originates — not only
205+ /// requests built by [`sentry_request_from_http`].
206+ pub fn scrub_event_request_headers ( event : & mut Event < ' static > ) {
207+ if let Some ( request) = event. request . as_mut ( ) {
208+ for ( name, value) in request. headers . iter_mut ( ) {
209+ if REDACTED_HEADERS . contains ( & name. to_ascii_lowercase ( ) . as_str ( ) ) {
210+ * value = REDACTED_HEADER_VALUE . to_owned ( ) ;
211+ }
212+ }
213+ }
214+ }
215+
176216/// Build a Sentry request struct from the HTTP request
177217fn sentry_request_from_http ( request : & ServiceRequest ) -> sentry:: protocol:: Request {
178218 sentry:: protocol:: Request {
@@ -188,7 +228,13 @@ fn sentry_request_from_http(request: &ServiceRequest) -> sentry::protocol::Reque
188228 headers : request
189229 . headers ( )
190230 . iter ( )
191- . map ( |( k, v) | ( k. to_string ( ) , v. to_str ( ) . unwrap_or_default ( ) . to_string ( ) ) )
231+ . map ( |( k, v) | {
232+ let name = k. as_str ( ) ;
233+ (
234+ name. to_owned ( ) ,
235+ sentry_header_value ( name, v. to_str ( ) . unwrap_or_default ( ) ) ,
236+ )
237+ } )
192238 . collect ( ) ,
193239 ..Default :: default ( )
194240 }
@@ -344,3 +390,60 @@ fn resolved_backtrace(err: &dyn ReportableError) -> Option<Backtrace> {
344390 }
345391 backtrace
346392}
393+
394+ #[ cfg( test) ]
395+ mod tests {
396+ use super :: { REDACTED_HEADER_VALUE , scrub_event_request_headers, sentry_header_value} ;
397+
398+ #[ test]
399+ fn before_send_scrub_redacts_request_headers ( ) {
400+ use sentry:: protocol:: { Event , Request } ;
401+
402+ let mut headers = std:: collections:: BTreeMap :: new ( ) ;
403+ headers. insert ( "Authorization" . to_owned ( ) , "Bearer secret" . to_owned ( ) ) ;
404+ headers. insert ( "X-KeyID" . to_owned ( ) , "kid-value" . to_owned ( ) ) ;
405+ headers. insert ( "content-type" . to_owned ( ) , "application/json" . to_owned ( ) ) ;
406+
407+ let mut event = Event {
408+ request : Some ( Request {
409+ headers,
410+ ..Default :: default ( )
411+ } ) ,
412+ ..Default :: default ( )
413+ } ;
414+ scrub_event_request_headers ( & mut event) ;
415+
416+ let headers = event. request . unwrap ( ) . headers ;
417+ assert_eq ! ( headers[ "Authorization" ] , REDACTED_HEADER_VALUE ) ;
418+ assert_eq ! ( headers[ "X-KeyID" ] , REDACTED_HEADER_VALUE ) ;
419+ assert_eq ! ( headers[ "content-type" ] , "application/json" ) ;
420+ }
421+
422+ #[ test]
423+ fn sensitive_header_values_are_redacted ( ) {
424+ // Sensitive headers are redacted regardless of case.
425+ for name in [
426+ "authorization" ,
427+ "Authorization" ,
428+ "Cookie" ,
429+ "X-KeyID" ,
430+ "x-client-state" ,
431+ "X-Forwarded-For" ,
432+ ] {
433+ assert_eq ! (
434+ sentry_header_value( name, "sensitive-value" ) ,
435+ REDACTED_HEADER_VALUE ,
436+ "expected `{name}` to be redacted"
437+ ) ;
438+ }
439+ }
440+
441+ #[ test]
442+ fn ordinary_header_values_pass_through ( ) {
443+ assert_eq ! (
444+ sentry_header_value( "content-type" , "application/json" ) ,
445+ "application/json"
446+ ) ;
447+ assert_eq ! ( sentry_header_value( "user-agent" , "Firefox" ) , "Firefox" ) ;
448+ }
449+ }
0 commit comments