Skip to content

Commit de272ca

Browse files
committed
sentry: remove Authorization header values before send
1 parent 0472c45 commit de272ca

File tree

3 files changed

+96
-4
lines changed

3 files changed

+96
-4
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,6 @@ diesel = { version = "=2.2.9", features = ["r2d2"] }
147147
googletest = "=0.14.0"
148148
insta = { version = "=1.42.2", features = ["glob", "json", "redactions"] }
149149
regex = "=1.11.1"
150+
sentry = { version = "=0.37.0", features = ["test"] }
150151
tokio = "=1.44.2"
151152
zip = { version = "=2.6.1", default-features = false, features = ["deflate"] }

src/config/sentry.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crates_io_env_vars::{required_var, var, var_parsed};
33
use sentry::IntoDsn;
44
use sentry::types::Dsn;
55

6+
#[cfg_attr(test, derive(Default))]
67
pub struct SentryConfig {
78
pub dsn: Option<Dsn>,
89
pub environment: Option<String>,

src/sentry/mod.rs

+94-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::config::SentryConfig;
2+
use http::header::AUTHORIZATION;
23
use sentry::protocol::Event;
34
use sentry::{ClientInitGuard, ClientOptions, TransactionContext};
45
use std::sync::Arc;
@@ -20,6 +21,10 @@ pub fn init() -> Option<ClientInitGuard> {
2021
}
2122
};
2223

24+
Some(sentry::init(options(config)))
25+
}
26+
27+
fn options(config: SentryConfig) -> ClientOptions {
2328
let traces_sampler = move |ctx: &TransactionContext| -> f32 {
2429
if let Some(sampled) = ctx.sampled() {
2530
return if sampled { 1.0 } else { 0.0 };
@@ -53,16 +58,22 @@ pub fn init() -> Option<ClientInitGuard> {
5358
};
5459

5560
let before_send = |mut event: Event<'static>| {
56-
// Remove cookies from the request to avoid sending sensitive
57-
// information like the `cargo_session`.
5861
if let Some(request) = &mut event.request {
62+
// Remove cookies from the request to avoid sending sensitive information like the
63+
// `cargo_session`.
5964
request.cookies.take();
65+
66+
// Also remove `Authorization`, just so it never even gets sent to Sentry, even if
67+
// they're redacting it downstream.
68+
request
69+
.headers
70+
.retain(|name, _value| AUTHORIZATION != name.as_str());
6071
}
6172

6273
Some(event)
6374
};
6475

65-
let opts = ClientOptions {
76+
ClientOptions {
6677
auto_session_tracking: true,
6778
dsn: config.dsn,
6879
environment: config.environment.map(Into::into),
@@ -71,7 +82,86 @@ pub fn init() -> Option<ClientInitGuard> {
7182
session_mode: sentry::SessionMode::Request,
7283
traces_sampler: Some(Arc::new(traces_sampler)),
7384
..Default::default()
85+
}
86+
}
87+
88+
#[cfg(test)]
89+
mod tests {
90+
use super::*;
91+
92+
use sentry::{
93+
capture_error,
94+
protocol::{Request, SpanStatus, Url},
95+
start_transaction,
7496
};
7597

76-
Some(sentry::init(opts))
98+
#[test]
99+
fn test_redaction() -> anyhow::Result<()> {
100+
let req = Request {
101+
url: Some(Url::parse("https://crates.io/api/v1/foo")?),
102+
method: Some("GET".into()),
103+
data: None,
104+
cookies: Some("cargo_session=foobar".into()),
105+
headers: [
106+
("Authorization", "secret"),
107+
("authorization", "another secret"),
108+
("Accept", "application/json"),
109+
]
110+
.into_iter()
111+
.map(|(k, v)| (k.to_string(), v.to_string()))
112+
.collect(),
113+
query_string: None,
114+
env: Default::default(),
115+
};
116+
let err = std::io::Error::new(std::io::ErrorKind::Other, "error");
117+
118+
let opts = options(SentryConfig::default());
119+
let event_req = req.clone();
120+
let mut events = sentry::test::with_captured_events_options(
121+
move || {
122+
let scope_req = event_req.clone();
123+
sentry::configure_scope(|scope| {
124+
// This is straight up replicated from the implementation of SentryHttpFuture,
125+
// and is how requests are attached by the Tower middleware.
126+
scope.add_event_processor(move |mut event| {
127+
if event.request.is_none() {
128+
event.request = Some(scope_req.clone());
129+
}
130+
Some(event)
131+
});
132+
});
133+
134+
let ctx = TransactionContext::new("test", "http.server");
135+
let txn = start_transaction(ctx);
136+
txn.set_request(event_req);
137+
txn.set_status(SpanStatus::InternalError);
138+
139+
capture_error(&err);
140+
141+
txn.finish();
142+
},
143+
opts,
144+
);
145+
146+
// OK, so there should be exactly one event, and it should match `req` except that its
147+
// cookies are removed and its headers have been cleaned of all Authorization values. Let's
148+
// see what we actually have.
149+
assert_eq!(events.len(), 1);
150+
let event = assert_some!(events.pop());
151+
let event_req = assert_some!(event.request);
152+
153+
// Things that shouldn't change.
154+
assert_eq!(&req.url, &event_req.url);
155+
assert_eq!(&req.method, &event_req.method);
156+
assert_eq!(&req.data, &event_req.data);
157+
assert_eq!(&req.query_string, &event_req.query_string);
158+
assert_eq!(&req.env, &event_req.env);
159+
160+
// Things that should.
161+
assert_none!(&event_req.cookies);
162+
assert_eq!(event_req.headers.len(), 1);
163+
assert_some!(event_req.headers.get("Accept"));
164+
165+
Ok(())
166+
}
77167
}

0 commit comments

Comments
 (0)