relay_filter/
localhost.rs

1//! Implements filtering for events originating from the localhost
2
3use crate::{FilterConfig, FilterStatKey, Filterable};
4
5const LOCAL_IPS: &[&str] = &["127.0.0.1", "::1"];
6const LOCAL_DOMAINS: &[&str] = &["127.0.0.1", "localhost"];
7
8const FORWARDED_HOST_HEADER: &str = "X-Forwarded-Host";
9
10const HOST_HEADER: &str = "Host";
11
12/// Check if the event originates from the local host.
13fn matches<F: Filterable>(item: &F) -> bool {
14    if let Some(ip_addr) = item.ip_addr() {
15        if LOCAL_IPS.contains(&ip_addr) {
16            return true;
17        }
18    }
19
20    if let Some(url) = item.url() {
21        if url.scheme() == "file" {
22            return true;
23        }
24
25        if let Some(host) = url.host_str() {
26            for &local_domain in LOCAL_DOMAINS {
27                if host_matches_or_is_subdomain_of(host, local_domain) {
28                    return true;
29                }
30            }
31        }
32    }
33
34    for header_name in [HOST_HEADER, FORWARDED_HOST_HEADER] {
35        if let Some(header) = item.header(header_name) {
36            if let Some(domain_part) = header.split(":").next() {
37                // header values here will usually look like "localhost:3000" or "127.0.0.1:8080"
38                if LOCAL_DOMAINS.contains(&domain_part) {
39                    return true;
40                }
41            }
42        }
43    }
44
45    false
46}
47
48fn host_matches_or_is_subdomain_of(host: &str, domain: &str) -> bool {
49    host.strip_suffix(domain)
50        .is_some_and(|s| s.is_empty() || s.ends_with('.'))
51}
52
53/// Filters events originating from the local host.
54pub fn should_filter<F: Filterable>(item: &F, config: &FilterConfig) -> Result<(), FilterStatKey> {
55    if !config.is_enabled {
56        return Ok(());
57    }
58    if matches(item) {
59        return Err(FilterStatKey::Localhost);
60    }
61    Ok(())
62}
63
64#[cfg(test)]
65mod tests {
66    use relay_event_schema::protocol::{
67        Event, HeaderName, HeaderValue, Headers, IpAddr, PairList, Request, User,
68    };
69    use relay_protocol::Annotated;
70
71    use super::*;
72
73    fn get_event_with_ip_addr(val: &str) -> Event {
74        Event {
75            user: Annotated::from(User {
76                ip_address: Annotated::from(IpAddr(val.to_string())),
77                ..User::default()
78            }),
79            ..Event::default()
80        }
81    }
82
83    fn get_event_with_domain(val: &str) -> Event {
84        Event {
85            request: Annotated::from(Request {
86                url: Annotated::from(format!("http://{val}:8080/")),
87                ..Request::default()
88            }),
89            ..Event::default()
90        }
91    }
92
93    fn get_event_with_url(val: &str) -> Event {
94        Event {
95            request: Annotated::from(Request {
96                url: Annotated::from(val.to_string()),
97                ..Request::default()
98            }),
99            ..Event::default()
100        }
101    }
102
103    fn get_event_with_header(key: &str, value: &str) -> Event {
104        Event {
105            request: Annotated::from(Request {
106                headers: Annotated::from(Headers(PairList(vec![Annotated::new((
107                    Annotated::new(HeaderName::new(key)),
108                    Annotated::new(HeaderValue::new(value)),
109                ))]))),
110                ..Default::default()
111            }),
112            ..Event::default()
113        }
114    }
115
116    #[test]
117    fn test_dont_filter_when_disabled() {
118        for event in &[
119            get_event_with_ip_addr("127.0.0.1"),
120            get_event_with_domain("localhost"),
121        ] {
122            let filter_result = should_filter(event, &FilterConfig { is_enabled: false });
123            assert_eq!(
124                filter_result,
125                Ok(()),
126                "Event filtered although filter should have been disabled."
127            );
128        }
129    }
130
131    #[test]
132    fn test_filter_local_ip() {
133        for ip_addr in &["127.0.0.1", "::1"] {
134            let event = get_event_with_ip_addr(ip_addr);
135            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
136            assert_ne!(
137                filter_result,
138                Ok(()),
139                "Failed to filter address '{ip_addr}'"
140            );
141        }
142    }
143
144    #[test]
145    fn test_dont_filter_non_local_ip() {
146        for ip_addr in &["133.12.12.1", "2001:db8:0:0:0:ff00:42:8329"] {
147            let event = get_event_with_ip_addr(ip_addr);
148            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
149            assert_eq!(
150                filter_result,
151                Ok(()),
152                "Filtered valid ip address '{ip_addr}'"
153            );
154        }
155    }
156
157    #[test]
158    fn test_dont_filter_missing_ip_or_domains() {
159        let event = Event::default();
160        let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
161        assert_eq!(
162            filter_result,
163            Ok(()),
164            "Filtered event with no ip address and no domain."
165        );
166    }
167
168    #[test]
169    fn test_filter_local_domains() {
170        for domain in &[
171            "127.0.0.1",
172            "localhost",
173            "foo.localhost",
174            "foo.bar.baz.localhost",
175        ] {
176            let event = get_event_with_domain(domain);
177            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
178            assert_ne!(filter_result, Ok(()), "Failed to filter domain '{domain}'");
179        }
180    }
181
182    #[test]
183    fn test_dont_filter_non_local_domains() {
184        for domain in &[
185            "my.dom.com",
186            "123.123.123.44",
187            "localhost.com",
188            "foolocalhost",
189            "localhostbar",
190            "alocalhostgoesintoabar",
191        ] {
192            let event = get_event_with_domain(domain);
193            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
194            assert_eq!(
195                filter_result,
196                Ok(()),
197                "Filtered perfectly valid domain '{domain}'"
198            );
199        }
200    }
201
202    #[test]
203    fn test_filter_file_urls() {
204        let url = "file:///Users/Maisey/work/squirrelchasers/src/leaderboard.html";
205        let event = get_event_with_url(url);
206        let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
207        assert_ne!(
208            filter_result,
209            Ok(()),
210            "Failed to filter event with url '{url}'"
211        );
212    }
213
214    #[test]
215    fn test_dont_filter_non_file_urls() {
216        let url = "http://www.squirrelchasers.com/leaderboard";
217        let event = get_event_with_url(url);
218        let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
219        assert_eq!(filter_result, Ok(()), "Filtered valid url '{url}'");
220    }
221
222    #[test]
223    fn test_filter_forwarded_host_header() {
224        for host_value in ["localhost:3000", "127.0.0.1:3000", "localhost"] {
225            let event = get_event_with_header(FORWARDED_HOST_HEADER, host_value);
226            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
227            assert_eq!(filter_result, Err(FilterStatKey::Localhost))
228        }
229    }
230
231    #[test]
232    fn test_filter_request_host_header() {
233        for host_value in ["localhost:3000", "127.0.0.1:3000", "localhost"] {
234            let event = get_event_with_header(HOST_HEADER, host_value);
235            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
236            assert_eq!(filter_result, Err(FilterStatKey::Localhost))
237        }
238    }
239
240    #[test]
241    fn test_filter_request_subdomain_host_header() {
242        for domain in ["localhost.sentry.io", "localhost.sentry.io:3000"] {
243            let event = get_event_with_header(HOST_HEADER, domain);
244            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
245            assert_eq!(filter_result, Ok(()))
246        }
247    }
248
249    #[test]
250    fn test_filter_request_subdomain_forwarded_host_header() {
251        for domain in ["localhost.sentry.io", "localhost.sentry.io:3000"] {
252            let event = get_event_with_header(FORWARDED_HOST_HEADER, domain);
253            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
254            assert_eq!(filter_result, Ok(()))
255        }
256    }
257}