Skip to content

Commit 937ddc2

Browse files
committed
chore: Improvements
1 parent 7176e41 commit 937ddc2

File tree

5 files changed

+140
-131
lines changed

5 files changed

+140
-131
lines changed

src/models/relayer/rpc_config.rs

Lines changed: 1 addition & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! including URLs and weights for load balancing.
55
66
use crate::constants::DEFAULT_RPC_WEIGHT;
7+
use crate::utils::mask_url;
78
use eyre::eyre;
89
use serde::{
910
de::Error as DeError, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer,
@@ -155,49 +156,6 @@ impl RpcConfig {
155156
}
156157
}
157158

158-
/// Masks a URL by showing only the scheme and host, hiding the path and query parameters.
159-
///
160-
/// This is used to safely display RPC URLs in API responses without exposing
161-
/// sensitive API keys that are often embedded in the URL path or query string.
162-
///
163-
/// # Examples
164-
/// - `https://eth-mainnet.g.alchemy.com/v2/abc123` → `https://eth-mainnet.g.alchemy.com/***`
165-
/// - `https://mainnet.infura.io/v3/PROJECT_ID` → `https://mainnet.infura.io/***`
166-
/// - `http://localhost:8545` → `http://localhost:8545` (no path to mask)
167-
/// - `invalid-url` → `***` (fallback for unparseable URLs)
168-
pub fn mask_url(url: &str) -> String {
169-
// Find the scheme separator "://"
170-
let Some(scheme_end) = url.find("://") else {
171-
// No valid scheme, mask entirely for safety
172-
return "***".to_string();
173-
};
174-
175-
// Find where the host ends (first "/" after "://")
176-
let host_start = scheme_end + 3; // Skip "://"
177-
let rest = &url[host_start..];
178-
179-
// Find the first "/" which marks the start of the path
180-
if let Some(path_start) = rest.find('/') {
181-
// Check if there's actually content in the path (not just "/")
182-
let path_and_beyond = &rest[path_start..];
183-
if path_and_beyond.len() > 1 || url.contains('?') {
184-
// There's a path or query to mask
185-
let host_end = host_start + path_start;
186-
format!("{}/***", &url[..host_end])
187-
} else {
188-
// Just a trailing "/" with no real path content
189-
url.to_string()
190-
}
191-
} else if url.contains('?') {
192-
// No path but has query parameters - mask those
193-
let query_start = url.find('?').unwrap();
194-
format!("{}?***", &url[..query_start])
195-
} else {
196-
// No path or query to mask, return original
197-
url.to_string()
198-
}
199-
}
200-
201159
/// RPC configuration with masked URL for API responses.
202160
///
203161
/// This type is used in API responses to prevent exposing sensitive API keys
@@ -705,90 +663,6 @@ mod tests {
705663
assert_eq!(urls[0].url, " https://rpc.example.com ");
706664
}
707665

708-
// =========================================================================
709-
// Tests for mask_url function
710-
// =========================================================================
711-
712-
#[test]
713-
fn test_mask_url_alchemy_with_api_key() {
714-
let url = "https://eth-mainnet.g.alchemy.com/v2/abc123xyz";
715-
let masked = super::mask_url(url);
716-
assert_eq!(masked, "https://eth-mainnet.g.alchemy.com/***");
717-
}
718-
719-
#[test]
720-
fn test_mask_url_infura_with_project_id() {
721-
let url = "https://mainnet.infura.io/v3/my-project-id";
722-
let masked = super::mask_url(url);
723-
assert_eq!(masked, "https://mainnet.infura.io/***");
724-
}
725-
726-
#[test]
727-
fn test_mask_url_quicknode_with_api_key() {
728-
let url = "https://my-node.quiknode.pro/secret-api-key/";
729-
let masked = super::mask_url(url);
730-
assert_eq!(masked, "https://my-node.quiknode.pro/***");
731-
}
732-
733-
#[test]
734-
fn test_mask_url_localhost_no_path() {
735-
// No path to mask, should return original
736-
let url = "http://localhost:8545";
737-
let masked = super::mask_url(url);
738-
assert_eq!(masked, "http://localhost:8545");
739-
}
740-
741-
#[test]
742-
fn test_mask_url_localhost_with_trailing_slash() {
743-
// Just a trailing slash with no real path content
744-
let url = "http://localhost:8545/";
745-
let masked = super::mask_url(url);
746-
assert_eq!(masked, "http://localhost:8545/");
747-
}
748-
749-
#[test]
750-
fn test_mask_url_with_query_params() {
751-
let url = "https://rpc.example.com/v1?api_key=secret123&network=mainnet";
752-
let masked = super::mask_url(url);
753-
assert_eq!(masked, "https://rpc.example.com/***");
754-
}
755-
756-
#[test]
757-
fn test_mask_url_query_params_no_path() {
758-
let url = "https://rpc.example.com?api_key=secret123";
759-
let masked = super::mask_url(url);
760-
assert_eq!(masked, "https://rpc.example.com?***");
761-
}
762-
763-
#[test]
764-
fn test_mask_url_invalid_url_no_scheme() {
765-
// Invalid URL without scheme should be fully masked for safety
766-
let url = "invalid-url";
767-
let masked = super::mask_url(url);
768-
assert_eq!(masked, "***");
769-
}
770-
771-
#[test]
772-
fn test_mask_url_empty_string() {
773-
let url = "";
774-
let masked = super::mask_url(url);
775-
assert_eq!(masked, "***");
776-
}
777-
778-
#[test]
779-
fn test_mask_url_with_port_and_path() {
780-
let url = "https://rpc.example.com:8080/api/v1/secret";
781-
let masked = super::mask_url(url);
782-
assert_eq!(masked, "https://rpc.example.com:8080/***");
783-
}
784-
785-
#[test]
786-
fn test_mask_url_ankr_with_api_key() {
787-
let url = "https://rpc.ankr.com/eth/my-api-key-here";
788-
let masked = super::mask_url(url);
789-
assert_eq!(masked, "https://rpc.ankr.com/***");
790-
}
791-
792666
// =========================================================================
793667
// Tests for MaskedRpcConfig
794668
// =========================================================================

src/repositories/network/network_in_memory.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ impl Repository<NetworkRepoModel, String> for InMemoryNetworkRepository {
9999

100100
if !store.contains_key(&id) {
101101
return Err(RepositoryError::NotFound(format!(
102-
"Network with id {} not found",
103-
id
102+
"Network with id {id} not found"
104103
)));
105104
}
106105

src/services/provider/evm/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use async_trait::async_trait;
3232
use eyre::Result;
3333
use reqwest::ClientBuilder as ReqwestClientBuilder;
3434
use serde_json;
35-
use tracing::info;
35+
use tracing::debug;
3636

3737
use super::rpc_selector::RpcSelector;
3838
use super::{retry_rpc_call, ProviderConfig, RetryConfig};
@@ -41,6 +41,7 @@ use crate::{
4141
BlockResponse, EvmTransactionData, RpcConfig, TransactionError, TransactionReceipt, U256,
4242
},
4343
services::provider::{is_retriable_error, should_mark_provider_failed},
44+
utils::mask_url,
4445
};
4546

4647
#[cfg(test)]
@@ -200,7 +201,7 @@ impl EvmProvider {
200201

201202
/// Initialize a provider for a given URL
202203
fn initialize_provider(&self, url: &str) -> Result<EvmProviderType, ProviderError> {
203-
info!("Initializing provider for URL: {url}");
204+
debug!("Initializing provider for URL: {}", mask_url(url));
204205
let rpc_url = url
205206
.parse()
206207
.map_err(|e| ProviderError::NetworkConfiguration(format!("Invalid URL format: {e}")))?;

src/utils/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,8 @@ pub use json_rpc_error::*;
4747
mod error_sanitization;
4848
pub use error_sanitization::*;
4949

50+
mod url;
51+
pub use url::*;
52+
5053
#[cfg(test)]
5154
pub mod mocks;

src/utils/url.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//! URL utility functions.
2+
//!
3+
//! This module provides utility functions for working with URLs,
4+
//! including masking sensitive information from URLs.
5+
6+
/// Masks a URL by showing only the scheme and host, hiding the path and query parameters.
7+
///
8+
/// This is used to safely display RPC URLs in API responses and logs without exposing
9+
/// sensitive API keys that are often embedded in the URL path or query string.
10+
///
11+
/// # Examples
12+
/// - `https://eth-mainnet.g.alchemy.com/v2/abc123` → `https://eth-mainnet.g.alchemy.com/***`
13+
/// - `https://mainnet.infura.io/v3/PROJECT_ID` → `https://mainnet.infura.io/***`
14+
/// - `http://localhost:8545` → `http://localhost:8545` (no path to mask)
15+
/// - `invalid-url` → `***` (fallback for unparseable URLs)
16+
pub fn mask_url(url: &str) -> String {
17+
// Find the scheme separator "://"
18+
let Some(scheme_end) = url.find("://") else {
19+
// No valid scheme, mask entirely for safety
20+
return "***".to_string();
21+
};
22+
23+
// Find where the host ends (first "/" after "://")
24+
let host_start = scheme_end + 3; // Skip "://"
25+
let rest = &url[host_start..];
26+
27+
// Find the first "/" which marks the start of the path
28+
if let Some(path_start) = rest.find('/') {
29+
// Check if there's actually content in the path (not just "/")
30+
let path_and_beyond = &rest[path_start..];
31+
if path_and_beyond.len() > 1 || url.contains('?') {
32+
// There's a path or query to mask
33+
let host_end = host_start + path_start;
34+
format!("{}/***", &url[..host_end])
35+
} else {
36+
// Just a trailing "/" with no real path content
37+
url.to_string()
38+
}
39+
} else if url.contains('?') {
40+
// No path but has query parameters - mask those
41+
let query_start = url.find('?').unwrap();
42+
format!("{}?***", &url[..query_start])
43+
} else {
44+
// No path or query to mask, return original
45+
url.to_string()
46+
}
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use super::*;
52+
53+
#[test]
54+
fn test_mask_url_alchemy_with_api_key() {
55+
let url = "https://eth-mainnet.g.alchemy.com/v2/abc123xyz";
56+
let masked = mask_url(url);
57+
assert_eq!(masked, "https://eth-mainnet.g.alchemy.com/***");
58+
}
59+
60+
#[test]
61+
fn test_mask_url_infura_with_project_id() {
62+
let url = "https://mainnet.infura.io/v3/my-project-id";
63+
let masked = mask_url(url);
64+
assert_eq!(masked, "https://mainnet.infura.io/***");
65+
}
66+
67+
#[test]
68+
fn test_mask_url_quicknode_with_api_key() {
69+
let url = "https://my-node.quiknode.pro/secret-api-key/";
70+
let masked = mask_url(url);
71+
assert_eq!(masked, "https://my-node.quiknode.pro/***");
72+
}
73+
74+
#[test]
75+
fn test_mask_url_localhost_no_path() {
76+
// No path to mask, should return original
77+
let url = "http://localhost:8545";
78+
let masked = mask_url(url);
79+
assert_eq!(masked, "http://localhost:8545");
80+
}
81+
82+
#[test]
83+
fn test_mask_url_localhost_with_trailing_slash() {
84+
// Just a trailing slash with no real path content
85+
let url = "http://localhost:8545/";
86+
let masked = mask_url(url);
87+
assert_eq!(masked, "http://localhost:8545/");
88+
}
89+
90+
#[test]
91+
fn test_mask_url_with_query_params() {
92+
let url = "https://rpc.example.com/v1?api_key=secret123&network=mainnet";
93+
let masked = mask_url(url);
94+
assert_eq!(masked, "https://rpc.example.com/***");
95+
}
96+
97+
#[test]
98+
fn test_mask_url_query_params_no_path() {
99+
let url = "https://rpc.example.com?api_key=secret123";
100+
let masked = mask_url(url);
101+
assert_eq!(masked, "https://rpc.example.com?***");
102+
}
103+
104+
#[test]
105+
fn test_mask_url_invalid_url_no_scheme() {
106+
// Invalid URL without scheme should be fully masked for safety
107+
let url = "invalid-url";
108+
let masked = mask_url(url);
109+
assert_eq!(masked, "***");
110+
}
111+
112+
#[test]
113+
fn test_mask_url_empty_string() {
114+
let url = "";
115+
let masked = mask_url(url);
116+
assert_eq!(masked, "***");
117+
}
118+
119+
#[test]
120+
fn test_mask_url_with_port_and_path() {
121+
let url = "https://rpc.example.com:8080/api/v1/secret";
122+
let masked = mask_url(url);
123+
assert_eq!(masked, "https://rpc.example.com:8080/***");
124+
}
125+
126+
#[test]
127+
fn test_mask_url_ankr_with_api_key() {
128+
let url = "https://rpc.ankr.com/eth/my-api-key-here";
129+
let masked = mask_url(url);
130+
assert_eq!(masked, "https://rpc.ankr.com/***");
131+
}
132+
}

0 commit comments

Comments
 (0)