diff --git a/CHANGELOG.md b/CHANGELOG.md index 65c580e1..38b19b3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ - feat(log): support kv feature of log (#851) by @lcian - Attributes added to a `log` record using the `kv` feature are now recorded as attributes on the log sent to Sentry. +### Behavioral changes + +- feat: filter username and password in URLs ([#864](https://github.com/getsentry/sentry-rust/pull/864)) by @lcian + - Usernames and passwords that could be contained in URLs captured when using the Actix Web or axum integration are now always filtered out. + - If the `Request` is created manually by the user, then these fields are not filtered out. + - This information was already filtered by Relay, but should also be filtered by the SDK itself as a first line of defense. + ### Fixes - docs: match description of `debug` option with behavior since PR #820 ([#860](https://github.com/getsentry/sentry-rust/pull/860)) by @AlexTMjugador diff --git a/Cargo.lock b/Cargo.lock index 2a13bf16..d164347d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3255,6 +3255,7 @@ dependencies = [ "serde_json", "thiserror 2.0.12", "tokio", + "url", "uuid", ] diff --git a/sentry-actix/src/lib.rs b/sentry-actix/src/lib.rs index 84a33b3b..ede7d749 100644 --- a/sentry-actix/src/lib.rs +++ b/sentry-actix/src/lib.rs @@ -88,7 +88,7 @@ use futures_util::future::{ok, Future, Ready}; use futures_util::{FutureExt as _, TryStreamExt as _}; use sentry_core::protocol::{self, ClientSdkPackage, Event, Request}; -use sentry_core::utils::is_sensitive_header; +use sentry_core::utils::{is_sensitive_header, scrub_pii_from_url}; use sentry_core::MaxRequestBodySize; use sentry_core::{Hub, SentryFutureExt}; @@ -428,7 +428,8 @@ fn sentry_request_from_http(request: &ServiceRequest, with_pii: bool) -> Request request.uri() ) .parse() - .ok(), + .ok() + .map(scrub_pii_from_url), method: Some(request.method().to_string()), headers: request .headers() diff --git a/sentry-core/Cargo.toml b/sentry-core/Cargo.toml index b7994cc4..999564b5 100644 --- a/sentry-core/Cargo.toml +++ b/sentry-core/Cargo.toml @@ -32,6 +32,7 @@ rand = { version = "0.9.0", optional = true } sentry-types = { version = "0.41.0", path = "../sentry-types" } serde = { version = "1.0.104", features = ["derive"] } serde_json = { version = "1.0.46" } +url = { version = "2.1.1" } uuid = { version = "1.0.0", features = ["v4", "serde"], optional = true } [dev-dependencies] diff --git a/sentry-core/src/utils.rs b/sentry-core/src/utils.rs index b78adba3..8f1333fc 100644 --- a/sentry-core/src/utils.rs +++ b/sentry-core/src/utils.rs @@ -10,8 +10,23 @@ const SENSITIVE_HEADERS_UPPERCASE: &[&str] = &[ "X_API_KEY", ]; +const PII_REPLACEMENT: &str = "[Filtered]"; + /// Determines if the HTTP header with the given name shall be considered as potentially carrying /// sensitive data. pub fn is_sensitive_header(name: &str) -> bool { SENSITIVE_HEADERS_UPPERCASE.contains(&name.to_ascii_uppercase().replace("-", "_").as_str()) } + +/// Scrub PII (username and password) from the given URL. +pub fn scrub_pii_from_url(mut url: url::Url) -> url::Url { + // the set calls will fail and return an error if the URL is relative + // in those cases, just ignore the errors + if !url.username().is_empty() { + let _ = url.set_username(PII_REPLACEMENT); + } + if url.password().is_some() { + let _ = url.set_password(Some(PII_REPLACEMENT)); + } + url +} diff --git a/sentry-tower/src/http.rs b/sentry-tower/src/http.rs index 92cc034b..c8ff949e 100644 --- a/sentry-tower/src/http.rs +++ b/sentry-tower/src/http.rs @@ -5,7 +5,7 @@ use std::task::{Context, Poll}; use http::{header, uri, Request, Response, StatusCode}; use pin_project::pinned_drop; -use sentry_core::utils::is_sensitive_header; +use sentry_core::utils::{is_sensitive_header, scrub_pii_from_url}; use sentry_core::{protocol, Hub}; use tower_layer::Layer; use tower_service::Service; @@ -187,7 +187,7 @@ where fn call(&mut self, request: Request) -> Self::Future { let sentry_req = sentry_core::protocol::Request { method: Some(request.method().to_string()), - url: get_url_from_request(&request), + url: get_url_from_request(&request).map(scrub_pii_from_url), headers: request .headers() .into_iter()