Skip to content

Commit 9ed621f

Browse files
authored
Sanitize shortid when storing metrics (#1434)
2 parents 48229f7 + 1a2491d commit 9ed621f

2 files changed

Lines changed: 86 additions & 1 deletion

File tree

payjoin-mailroom/src/lib.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,4 +573,77 @@ mod tests {
573573
assert!(metric_names.contains(&TOTAL_CONNECTIONS), "missing total_connections");
574574
assert!(metric_names.contains(&ACTIVE_CONNECTIONS), "missing active_connections");
575575
}
576+
577+
#[tokio::test]
578+
async fn middleware_sanitizes_short_id_in_metrics() {
579+
use axum::body::Body;
580+
use axum::http::Request;
581+
use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData};
582+
use tower::ServiceExt;
583+
584+
let exporter = InMemoryMetricExporter::default();
585+
let reader = PeriodicReader::builder(exporter.clone()).build();
586+
let provider = SdkMeterProvider::builder().with_reader(reader).build();
587+
588+
let tempdir = tempdir().unwrap();
589+
let config = Config::new(
590+
"[::]:0".parse().expect("valid listener address"),
591+
tempdir.path().to_path_buf(),
592+
Duration::from_secs(2),
593+
None,
594+
);
595+
596+
let sentinel_tag = generate_sentinel_tag();
597+
let metrics = MetricsService::new(Some(provider.clone()));
598+
let services = Services {
599+
directory: init_directory(&config, sentinel_tag, &metrics).await.unwrap(),
600+
relay: crate::ohttp_relay::Service::new(sentinel_tag).await,
601+
metrics,
602+
#[cfg(feature = "access-control")]
603+
geoip: None,
604+
};
605+
606+
let app = build_app(services);
607+
608+
let short_id = payjoin::directory::ShortId([0u8; 8]).to_string();
609+
let uri = format!("/{short_id}");
610+
let request = Request::builder().method("GET").uri(&uri).body(Body::empty()).unwrap();
611+
let _response = ServiceExt::<Request<Body>>::oneshot(app, request).await.unwrap();
612+
613+
provider.force_flush().expect("flush failed");
614+
615+
let finished = exporter.get_finished_metrics().expect("metrics");
616+
println!("finished: {:?}", finished);
617+
let endpoint_attrs: Vec<String> = finished
618+
.iter()
619+
.flat_map(|rm| rm.scope_metrics())
620+
.flat_map(|sm| sm.metrics())
621+
.filter(|m| m.name() == HTTP_REQUESTS)
622+
.flat_map(|m| match m.data() {
623+
AggregatedMetrics::U64(MetricData::Sum(sum)) => sum
624+
.data_points()
625+
.flat_map(|dp| dp.attributes())
626+
.filter_map(|kv| {
627+
if kv.key.as_str() == "endpoint" {
628+
Some(kv.value.to_string())
629+
} else {
630+
None
631+
}
632+
})
633+
.collect::<Vec<_>>(),
634+
_ => vec![],
635+
})
636+
.collect();
637+
638+
println!("endpoint_attrs: {:?}", endpoint_attrs);
639+
640+
assert!(
641+
endpoint_attrs.iter().all(|ep| ep == "/{mailbox}"),
642+
"short ID must be sanitized in metrics, got: {endpoint_attrs:?}"
643+
);
644+
assert!(
645+
endpoint_attrs.iter().all(|ep| !ep.contains(&short_id)),
646+
"actual short ID value must not appear in metrics"
647+
);
648+
}
576649
}

payjoin-mailroom/src/middleware.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub async fn track_metrics(
3939
next: Next,
4040
) -> Response {
4141
let method = req.method().to_string();
42-
let path = req.uri().path().to_string();
42+
let path = sanitize_short_id(req.uri().path());
4343

4444
let response = next.run(req).await;
4545
let status = response.status().as_u16();
@@ -49,6 +49,18 @@ pub async fn track_metrics(
4949
response
5050
}
5151

52+
fn sanitize_short_id(path: &str) -> String {
53+
// This function ensures that ShortID isn't recorded in the metrics
54+
const BECH32_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
55+
match path.strip_prefix('/') {
56+
Some(segment)
57+
if segment.len() == 13
58+
&& segment.bytes().all(|b| BECH32_CHARSET.contains(&b.to_ascii_lowercase())) =>
59+
"/{mailbox}".to_string(),
60+
_ => path.to_string(),
61+
}
62+
}
63+
5264
pub async fn track_connections(
5365
metrics: axum::extract::State<MetricsService>,
5466
req: Request,

0 commit comments

Comments
 (0)