relay_filter/
web_crawlers.rs

1//! Filters events coming from user agents known to be web crawlers.
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5
6use crate::{FilterConfig, FilterStatKey, Filterable};
7
8static WEB_CRAWLERS: Lazy<Regex> = Lazy::new(|| {
9    Regex::new(
10        r"(?ix)
11        Mediapartners-Google|
12        AdsBot-Google|
13        Googlebot|
14        FeedFetcher-Google|
15        BingBot|                    # Bing search
16        BingPreview|
17        Baiduspider|                # Baidu search
18        Slurp|                      # Yahoo
19        Sogou|                      # Sogou
20        facebook|                   # facebook
21        meta-|                      # meta/facebook
22        ia_archiver|                # Alexa
23        bots?[/\s\);]|              # Generic bot
24        spider[/\s\);]|             # Generic spider
25        Slack|                      # Slack - see https://api.slack.com/robots
26        Calypso\sAppCrawler|        # Google indexing bot
27        pingdom|                    # Pingdom
28        lyticsbot|                  # Lytics
29        AWS\sSecurity\sScanner|     # AWS Security Scanner causing DisallowedHost errors in Django, see
30                                    # https://forums.aws.amazon.com/thread.jspa?messageID=932404
31                                    # and https://github.com/getsentry/sentry-python/issues/641
32        HubSpot\sCrawler|           # HubSpot web crawler (web-crawlers@hubspot.com)
33        Bytespider|                 # Bytedance
34        Better\sUptime|             # Better Uptime
35        Cloudflare-Healthchecks|    # Cloudflare Health Checks
36        GTmetrix|                   # GTmetrix
37        BrightEdgeOnCrawl|          # BrightEdge - see https://www.brightedge.com/news/press-releases/brightedge-acquires-oncrawl-future-proof-web-30-strategies
38        ELB-HealthChecker|          # AWS Elastic Load Balancing Health Checks
39        naver.me/spd|               # Yeti/1.1 - naver.me
40        ClaudeBot|                  # Anthropic - see https://support.anthropic.com/en/articles/10023637-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler
41        CCBot|                      # CCBot - see https://commoncrawl.org/ccbot
42        OAI-SearchBot|              # OpenAI - see https://platform.openai.com/docs/bots
43        GPTBot                      # OpenAI - see https://platform.openai.com/docs/bots
44    "
45    )
46    .expect("Invalid web crawlers filter Regex")
47});
48
49static ALLOWED_WEB_CRAWLERS: Lazy<Regex> = Lazy::new(|| {
50    Regex::new(
51        r"(?ix)
52        Slackbot\s1\.\d+|            # Slack - see https://api.slack.com/robots
53        SentryUptimeBot              # Uptime Checker https://docs.sentry.io/product/alerts/uptime-monitoring/
54    ",
55    )
56    .expect("Invalid allowed web crawlers filter Regex")
57});
58
59/// Checks if the event originates from a known web crawler.
60fn matches(user_agent: Option<&str>) -> bool {
61    if let Some(user_agent) = user_agent {
62        WEB_CRAWLERS.is_match(user_agent) && !ALLOWED_WEB_CRAWLERS.is_match(user_agent)
63    } else {
64        false
65    }
66}
67
68/// Filters events originating from a known web crawler.
69pub fn should_filter<F: Filterable>(item: &F, config: &FilterConfig) -> Result<(), FilterStatKey> {
70    if !config.is_enabled {
71        return Ok(());
72    }
73
74    if matches(item.user_agent()) {
75        return Err(FilterStatKey::WebCrawlers);
76    }
77
78    Ok(())
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::testutils;
85
86    #[test]
87    fn test_filter_when_disabled() {
88        let evt = testutils::get_event_with_user_agent("Googlebot");
89        let filter_result = should_filter(&evt, &FilterConfig { is_enabled: false });
90        assert_eq!(
91            filter_result,
92            Ok(()),
93            "Event filtered although filter should have been disabled"
94        )
95    }
96
97    #[test]
98    fn test_filter_banned_user_agents() {
99        let user_agents = [
100            "Mediapartners-Google",
101            "AdsBot-Google",
102            "Googlebot",
103            "FeedFetcher-Google",
104            "BingBot",
105            "BingPreview",
106            "Baiduspider",
107            "Slurp",
108            "Sogou",
109            "facebook",
110            "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)",
111            "facebookcatalog/1.0",
112            "meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)",
113            "meta-externalfetcher/1.1",
114            "ia_archiver",
115            "bots ",
116            "bots;",
117            "bots)",
118            "spider ",
119            "spider;",
120            "spider)",
121            "Calypso AppCrawler",
122            "pingdom",
123            "lyticsbot",
124            "AWS Security Scanner",
125            "Mozilla/5.0 (Linux; Android 6.0.1; Calypso AppCrawler Build/MMB30Y; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.124 Mobile Safari/537.36",
126            "Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)",
127            "Slack-ImgProxy 0.19 (+https://api.slack.com/robots)",
128            "Twitterbot/1.0",
129            "FeedFetcher-Google; (+http://www.google.com/feedfetcher.html)",
130            "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
131            "AdsBot-Google (+http://www.google.com/adsbot.html)",
132            "Mozilla/5.0 (compatible; HubSpot Crawler; web-crawlers@hubspot.com)",
133            "Mozilla/5.0 (Linux; Android 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; Bytespider; spider-feedback@bytedance.com)",
134            "Better Uptime Bot Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
135            "Mozilla/5.0 (compatible;Cloudflare-Healthchecks/1.0;+https://www.cloudflare.com/; healthcheck-id: 0d1ca23e292c8c14)",
136            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 GTmetrix",
137            "Mozilla/5.0 (compatible; BrightEdgeOnCrawl/1.0; +http://www.oncrawl.com)",
138            "ELB-HealthChecker/2.0",
139            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko; compatible; Yeti/1.1; +https://naver.me/spd) Chrome/127.0.0.0 Safari/537.36",
140            "Mozilla/5.0; ClaudeBot",
141            "Mozilla/5.0; CCBot",
142            "; OAI-SearchBot/1.0; +https://openai.com/searchbot",
143            "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.1; +https://openai.com/gptbot",
144        ];
145
146        for banned_user_agent in &user_agents {
147            let event = testutils::get_event_with_user_agent(banned_user_agent);
148            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
149            assert_ne!(
150                filter_result,
151                Ok(()),
152                "Failed to filter web crawler with user agent '{banned_user_agent}'"
153            );
154        }
155    }
156
157    #[test]
158    fn test_dont_filter_normal_user_agents() {
159        let normal_user_agents = [
160            "some user agent",
161            "IE",
162            "ie",
163            "opera",
164            "safari",
165            "APIs-Google (+https://developers.google.com/webmasters/APIs-Google.html)",
166            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
167            "Slackbot 1.0(+https://api.slack.com/robots)",
168            "SentryUptimeBot/1.0 (+http://docs.sentry.io/product/alerts/uptime-monitoring/)",
169            "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot",
170        ];
171        for user_agent in &normal_user_agents {
172            let event = testutils::get_event_with_user_agent(user_agent);
173            let filter_result = should_filter(&event, &FilterConfig { is_enabled: true });
174            assert_eq!(
175                filter_result,
176                Ok(()),
177                "Failed benign user agent '{user_agent}'"
178            );
179        }
180    }
181}