diff --git a/CHANGELOG.md b/CHANGELOG.md index a9843f975..ec59d302c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Changelog +## Unreleased + - fix: Scope values should not override Event values (#446) - feat: Extend User inteface by adding Data, Name and Segment (#483) - feat: Make maximum amount of spans configurable (#460) +- feat: Add ClientOptions.SendDefaultPii #485 ## 0.14.0 diff --git a/client.go b/client.go index be8d26686..dd3f72008 100644 --- a/client.go +++ b/client.go @@ -134,6 +134,9 @@ type ClientOptions struct { // and if applicable, caught errors type and value. // If the match is found, then a whole event will be dropped. IgnoreErrors []string + // If this flag is enabled, certain personally identifiable information (PII) is added by active integrations. + // By default, no such data is sent. + SendDefaultPii bool // BeforeSend is called before error events are sent to Sentry. // Use it to mutate the event or return nil to discard the event. // See EventProcessor if you need to mutate transactions. diff --git a/fasthttp/sentryfasthttp_test.go b/fasthttp/sentryfasthttp_test.go index 0fbdbf627..cac7fae52 100644 --- a/fasthttp/sentryfasthttp_test.go +++ b/fasthttp/sentryfasthttp_test.go @@ -142,6 +142,7 @@ func TestIntegration(t *testing.T) { eventsCh := make(chan *sentry.Event, len(tests)) err := sentry.Init(sentry.ClientOptions{ + SendDefaultPii: true, BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { eventsCh <- event return event diff --git a/http/sentryhttp_test.go b/http/sentryhttp_test.go index 5a9ea530e..898e33777 100644 --- a/http/sentryhttp_test.go +++ b/http/sentryhttp_test.go @@ -156,6 +156,7 @@ func TestIntegration(t *testing.T) { eventsCh := make(chan *sentry.Event, len(tests)) err := sentry.Init(sentry.ClientOptions{ + SendDefaultPii: true, BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { eventsCh <- event return event diff --git a/interfaces.go b/interfaces.go index e1b8b46df..72e566a37 100644 --- a/interfaces.go +++ b/interfaces.go @@ -28,6 +28,15 @@ const ( LevelFatal Level = "fatal" ) +func getSensitiveHeaders() map[string]bool { + return map[string]bool{ + "Authorization": true, + "Cookie": true, + "X-Forwarded-For": true, + "X-Real-Ip": true, + } +} + // SdkInfo contains all metadata about about the SDK being used. type SdkInfo struct { Name string `json:"name,omitempty"` @@ -156,23 +165,37 @@ func NewRequest(r *http.Request) *Request { } url := fmt.Sprintf("%s://%s%s", protocol, r.Host, r.URL.Path) - // We read only the first Cookie header because of the specification: - // https://tools.ietf.org/html/rfc6265#section-5.4 - // When the user agent generates an HTTP request, the user agent MUST NOT - // attach more than one Cookie header field. - cookies := r.Header.Get("Cookie") - - headers := make(map[string]string, len(r.Header)) - for k, v := range r.Header { - headers[k] = strings.Join(v, ",") - } - headers["Host"] = r.Host - + var cookies string var env map[string]string - if addr, port, err := net.SplitHostPort(r.RemoteAddr); err == nil { - env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port} + headers := map[string]string{} + + if client := CurrentHub().Client(); client != nil { + if client.Options().SendDefaultPii { + // We read only the first Cookie header because of the specification: + // https://tools.ietf.org/html/rfc6265#section-5.4 + // When the user agent generates an HTTP request, the user agent MUST NOT + // attach more than one Cookie header field. + cookies = r.Header.Get("Cookie") + + for k, v := range r.Header { + headers[k] = strings.Join(v, ",") + } + + if addr, port, err := net.SplitHostPort(r.RemoteAddr); err == nil { + env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port} + } + } + } else { + sensitiveHeaders := getSensitiveHeaders() + for k, v := range r.Header { + if _, ok := sensitiveHeaders[k]; !ok { + headers[k] = strings.Join(v, ",") + } + } } + headers["Host"] = r.Host + return &Request{ URL: url, Method: r.Method, diff --git a/interfaces_test.go b/interfaces_test.go index ca48ae8ee..a232d6195 100644 --- a/interfaces_test.go +++ b/interfaces_test.go @@ -66,16 +66,34 @@ func TestUserMarshalJson(t *testing.T) { } func TestNewRequest(t *testing.T) { + currentHub.BindClient(&Client{ + options: ClientOptions{ + SendDefaultPii: true, + }, + }) + // Unbind the client afterwards, to not affect other tests + defer currentHub.stackTop().SetClient(nil) + const payload = `{"test_data": true}` - got := NewRequest(httptest.NewRequest("POST", "/test/?q=sentry", strings.NewReader(payload))) + r := httptest.NewRequest("POST", "/test/?q=sentry", strings.NewReader(payload)) + r.Header.Add("Authorization", "Bearer 1234567890") + r.Header.Add("Cookie", "foo=bar") + r.Header.Add("X-Forwarded-For", "127.0.0.1") + r.Header.Add("X-Real-Ip", "127.0.0.1") + + got := NewRequest(r) want := &Request{ URL: "http://example.com/test/", Method: "POST", Data: "", QueryString: "q=sentry", - Cookies: "", + Cookies: "foo=bar", Headers: map[string]string{ - "Host": "example.com", + "Authorization": "Bearer 1234567890", + "Cookie": "foo=bar", + "Host": "example.com", + "X-Forwarded-For": "127.0.0.1", + "X-Real-Ip": "127.0.0.1", }, Env: map[string]string{ "REMOTE_ADDR": "192.0.2.1", @@ -87,6 +105,31 @@ func TestNewRequest(t *testing.T) { } } +func TestNewRequestWithNoPii(t *testing.T) { + const payload = `{"test_data": true}` + r := httptest.NewRequest("POST", "/test/?q=sentry", strings.NewReader(payload)) + r.Header.Add("Authorization", "Bearer 1234567890") + r.Header.Add("Cookie", "foo=bar") + r.Header.Add("X-Forwarded-For", "127.0.0.1") + r.Header.Add("X-Real-Ip", "127.0.0.1") + + got := NewRequest(r) + want := &Request{ + URL: "http://example.com/test/", + Method: "POST", + Data: "", + QueryString: "q=sentry", + Cookies: "", + Headers: map[string]string{ + "Host": "example.com", + }, + Env: nil, + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("Request mismatch (-want +got):\n%s", diff) + } +} + func TestEventMarshalJSON(t *testing.T) { event := NewEvent() event.Spans = []*Span{{