Skip to content

Commit

Permalink
Merge branch 'main' into feat/io-overrides-integration
Browse files Browse the repository at this point in the history
  • Loading branch information
denrase committed Mar 27, 2023
2 parents b020ac6 + 24f71aa commit dd7de03
Show file tree
Hide file tree
Showing 43 changed files with 1,896 additions and 135 deletions.
2 changes: 1 addition & 1 deletion .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ targets:
pub:sentry_logging:
pub:sentry_dio:
pub:sentry_file:
#pub:sentry_sqflite:
pub:sentry_sqflite:
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## Unreleased

### Features

- Sanitize sensitive data from URLs (span desc, span data, crumbs, client errors) ([#1327](https://github.com/getsentry/sentry-dart/pull/1327))

### Dependencies

- Bump Cocoa SDK from v8.3.1 to v8.3.3 ([#1350](https://github.com/getsentry/sentry-dart/pull/1350), [#1355](https://github.com/getsentry/sentry-dart/pull/1355))
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#833)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.3.1...8.3.3)

## Fixes

- Sync missing properties to the Native SDKs ([#1354](https://github.com/getsentry/sentry-dart/pull/1354))

## 7.2.0

### Features
Expand Down
5 changes: 5 additions & 0 deletions dart/lib/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@ export 'src/exception_stacktrace_extractor.dart';
// Isolates
export 'src/sentry_isolate_extension.dart';
export 'src/sentry_isolate.dart';
// URL
// ignore: invalid_export_of_internal_element
export 'src/utils/http_sanitizer.dart';
// ignore: invalid_export_of_internal_element
export 'src/utils/url_details.dart';
1 change: 1 addition & 0 deletions dart/lib/sentry_io.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// ignore: invalid_export_of_internal_element
export 'sentry.dart';
export 'src/sentry_attachment/io_sentry_attachment.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ class WebEnricherEventProcessor implements EnricherEventProcessor {

header.putIfAbsent('User-Agent', () => _window.navigator.userAgent);

return (request ?? SentryRequest()).copyWith(
url: request?.url ?? _window.location.toString(),
headers: header,
);
final url = request?.url ?? _window.location.toString();
return (request ?? SentryRequest(url: url))
.copyWith(
headers: header,
)
.sanitized();
}

SentryDevice _getDevice(SentryDevice? device) {
Expand Down
9 changes: 8 additions & 1 deletion dart/lib/src/http_client/breadcrumb_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:http/http.dart';
import '../protocol.dart';
import '../hub.dart';
import '../hub_adapter.dart';
import '../utils/url_details.dart';
import '../utils/http_sanitizer.dart';

/// A [http](https://pub.dev/packages/http)-package compatible HTTP client
/// which records requests as breadcrumbs.
Expand Down Expand Up @@ -75,15 +77,20 @@ class BreadcrumbClient extends BaseClient {
} finally {
stopwatch.stop();

final urlDetails =
HttpSanitizer.sanitizeUrl(request.url.toString()) ?? UrlDetails();

var breadcrumb = Breadcrumb.http(
level: requestHadException ? SentryLevel.error : SentryLevel.info,
url: request.url,
url: Uri.parse(urlDetails.urlOrFallback),
method: request.method,
statusCode: statusCode,
reason: reason,
requestDuration: stopwatch.elapsed,
requestBodySize: request.contentLength,
responseBodySize: responseBodySize,
httpQuery: urlDetails.query,
httpFragment: urlDetails.fragment,
);

await _hub.addBreadcrumb(breadcrumb);
Expand Down
16 changes: 14 additions & 2 deletions dart/lib/src/http_client/tracing_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import '../hub_adapter.dart';
import '../protocol.dart';
import '../tracing.dart';
import '../utils/tracing_utils.dart';
import '../utils/http_sanitizer.dart';

/// A [http](https://pub.dev/packages/http)-package compatible HTTP client
/// which adds support to Sentry Performance feature.
Expand All @@ -19,17 +20,28 @@ class TracingClient extends BaseClient {
@override
Future<StreamedResponse> send(BaseRequest request) async {
// see https://develop.sentry.dev/sdk/performance/#header-sentry-trace

final urlDetails = HttpSanitizer.sanitizeUrl(request.url.toString());

var description = request.method;
if (urlDetails != null) {
description += ' ${urlDetails.urlOrFallback}';
}

final currentSpan = _hub.getSpan();
var span = currentSpan?.startChild(
'http.client',
description: '${request.method} ${request.url}',
description: description,
);

// if the span is NoOp, we dont want to attach headers
// if the span is NoOp, we don't want to attach headers
if (span is NoOpSentrySpan) {
span = null;
}

span?.setData('method', request.method);
urlDetails?.applyToSpan(span);

StreamedResponse? response;
try {
if (span != null) {
Expand Down
6 changes: 5 additions & 1 deletion dart/lib/src/protocol/breadcrumb.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:meta/meta.dart';
import '../utils.dart';
import '../protocol.dart';

/// Structed data to describe more information pior to the event captured.
/// Structured data to describe more information prior to the event captured.
/// See `Sentry.captureEvent()`.
///
/// The outgoing JSON representation is:
Expand Down Expand Up @@ -47,6 +47,8 @@ class Breadcrumb {

// Size of the response body in bytes
int? responseBodySize,
String? httpQuery,
String? httpFragment,
}) {
return Breadcrumb(
type: 'http',
Expand All @@ -61,6 +63,8 @@ class Breadcrumb {
if (requestDuration != null) 'duration': requestDuration.toString(),
if (requestBodySize != null) 'request_body_size': requestBodySize,
if (responseBodySize != null) 'response_body_size': responseBodySize,
if (httpQuery != null) 'http.query': httpQuery,
if (httpFragment != null) 'http.fragment': httpFragment,
},
);
}
Expand Down
25 changes: 7 additions & 18 deletions dart/lib/src/protocol/sentry_request.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:meta/meta.dart';

import '../utils/iterable_extension.dart';
import '../utils/http_sanitizer.dart';

/// The Request interface contains information on a HTTP request related to the event.
/// In client SDKs, this can be an outgoing request, or the request that rendered the current web page.
Expand Down Expand Up @@ -92,31 +93,18 @@ class SentryRequest {
@Deprecated('Will be removed in v8. Use [data] instead')
Map<String, String>? other,
}) {
// As far as I can tell there's no way to get the uri without the query part
// so we replace it with an empty string.
final urlWithoutQuery = uri
.replace(query: '', fragment: '')
.toString()
.replaceAll('?', '')
.replaceAll('#', '');

// Future proof, Dio does not support it yet and even if passing in the path,
// the parsing of the uri returns empty.
final query = uri.query.isEmpty ? null : uri.query;
final fragment = uri.fragment.isEmpty ? null : uri.fragment;

return SentryRequest(
url: urlWithoutQuery,
fragment: fragment,
queryString: query,
url: uri.toString(),
method: method,
cookies: cookies,
data: data,
headers: headers,
env: env,
queryString: uri.query,
fragment: uri.fragment,
// ignore: deprecated_member_use_from_same_package
other: other,
);
).sanitized();
}

/// Deserializes a [SentryRequest] from JSON [Map].
Expand Down Expand Up @@ -162,12 +150,13 @@ class SentryRequest {
Map<String, String>? env,
@Deprecated('Will be removed in v8. Use [data] instead')
Map<String, String>? other,
bool removeCookies = false,
}) =>
SentryRequest(
url: url ?? this.url,
method: method ?? this.method,
queryString: queryString ?? this.queryString,
cookies: cookies ?? this.cookies,
cookies: removeCookies ? null : cookies ?? this.cookies,
data: data ?? _data,
headers: headers ?? _headers,
env: env ?? _env,
Expand Down
6 changes: 2 additions & 4 deletions dart/lib/src/sentry_tracer.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'dart:async';

import 'package:intl/intl.dart';
import 'package:meta/meta.dart';

import '../sentry.dart';
import 'sentry_tracer_finish_status.dart';
import 'utils/sample_rate_format.dart';

@internal
class SentryTracer extends ISentrySpan {
Expand Down Expand Up @@ -349,9 +349,7 @@ class SentryTracer extends ISentrySpan {
if (!isValidSampleRate(sampleRate)) {
return null;
}
// requires intl package
final formatter = NumberFormat('#.################');
return formatter.format(sampleRate);
return sampleRate != null ? SampleRateFormat().format(sampleRate) : null;
}

bool _isHighQualityTransactionName(SentryTransactionNameSource source) {
Expand Down
105 changes: 105 additions & 0 deletions dart/lib/src/utils/http_sanitizer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import 'package:meta/meta.dart';

import '../protocol.dart';
import 'url_details.dart';

@internal
class HttpSanitizer {
static final RegExp _authRegExp = RegExp("(.+://)(.*@)(.*)");
static final List<String> _securityHeaders = [
"X-FORWARDED-FOR",
"AUTHORIZATION",
"COOKIE",
"SET-COOKIE",
"X-API-KEY",
"X-REAL-IP",
"REMOTE-ADDR",
"FORWARDED",
"PROXY-AUTHORIZATION",
"X-CSRF-TOKEN",
"X-CSRFTOKEN",
"X-XSRF-TOKEN"
];

/// Parse and sanitize url data for sentry.io
static UrlDetails? sanitizeUrl(String? url) {
if (url == null) {
return null;
}

final queryIndex = url.indexOf('?');
final fragmentIndex = url.indexOf('#');

if (queryIndex > -1 && fragmentIndex > -1 && fragmentIndex < queryIndex) {
// url considered malformed because of fragment position
return UrlDetails();
} else {
try {
final uri = Uri.parse(url);
final urlWithAuthRemoved = _urlWithAuthRemoved(uri._url());
return UrlDetails(
url: urlWithAuthRemoved.isEmpty ? null : urlWithAuthRemoved,
query: uri.query.isEmpty ? null : uri.query,
fragment: uri.fragment.isEmpty ? null : uri.fragment);
} catch (_) {
return null;
}
}
}

static Map<String, String>? sanitizedHeaders(Map<String, String>? headers) {
if (headers == null) {
return null;
}
final sanitizedHeaders = <String, String>{};
headers.forEach((key, value) {
if (!_securityHeaders.contains(key.toUpperCase())) {
sanitizedHeaders[key] = value;
}
});
return sanitizedHeaders;
}

static String _urlWithAuthRemoved(String url) {
final userInfoMatch = _authRegExp.firstMatch(url);
if (userInfoMatch != null && userInfoMatch.groupCount == 3) {
final userInfoString = userInfoMatch.group(2) ?? '';
final replacementString = userInfoString.contains(":")
? "[Filtered]:[Filtered]@"
: "[Filtered]@";
return '${userInfoMatch.group(1) ?? ''}$replacementString${userInfoMatch.group(3) ?? ''}';
} else {
return url;
}
}
}

extension UriPath on Uri {
String _url() {
var buffer = '';
if (scheme.isNotEmpty) {
buffer += '$scheme://';
}
if (userInfo.isNotEmpty) {
buffer += '$userInfo@';
}
buffer += host;
if (path.isNotEmpty) {
buffer += path;
}
return buffer;
}
}

extension SanitizedSentryRequest on SentryRequest {
SentryRequest sanitized() {
final urlDetails = HttpSanitizer.sanitizeUrl(url) ?? UrlDetails();
return copyWith(
url: urlDetails.urlOrFallback,
queryString: urlDetails.query,
fragment: urlDetails.fragment,
headers: HttpSanitizer.sanitizedHeaders(headers),
removeCookies: true,
);
}
}
Loading

0 comments on commit dd7de03

Please sign in to comment.