Skip to main content

systemprompt_analytics/services/behavioral_detector/
mod.rs

1mod checks;
2mod types;
3
4pub use types::{BehavioralAnalysisInput, BehavioralAnalysisResult, BehavioralSignal, SignalType};
5
6pub const BEHAVIORAL_BOT_THRESHOLD: i32 = 30;
7
8pub mod scoring {
9    pub const HIGH_REQUEST_COUNT: i32 = 30;
10    pub const HIGH_PAGE_COVERAGE: i32 = 25;
11    pub const SEQUENTIAL_NAVIGATION: i32 = 20;
12    pub const MULTIPLE_FINGERPRINT_SESSIONS: i32 = 20;
13    pub const REGULAR_TIMING: i32 = 15;
14    pub const HIGH_PAGES_PER_MINUTE: i32 = 15;
15    pub const OUTDATED_BROWSER: i32 = 25;
16    pub const NO_JAVASCRIPT_EVENTS: i32 = 20;
17}
18
19pub mod thresholds {
20    pub const REQUEST_COUNT_LIMIT: i64 = 50;
21    pub const PAGE_COVERAGE_PERCENT: f64 = 60.0;
22    pub const FINGERPRINT_SESSION_LIMIT: i64 = 5;
23    pub const PAGES_PER_MINUTE_LIMIT: f64 = 5.0;
24    pub const TIMING_VARIANCE_MIN: f64 = 0.1;
25    pub const CHROME_MIN_VERSION: i32 = 120;
26    pub const FIREFOX_MIN_VERSION: i32 = 120;
27    pub const NO_JS_MIN_REQUESTS: i64 = 3;
28}
29
30#[derive(Debug, Clone, Copy, Default)]
31pub struct BehavioralBotDetector;
32
33impl BehavioralBotDetector {
34    pub const fn new() -> Self {
35        Self
36    }
37
38    pub fn analyze(input: &BehavioralAnalysisInput) -> BehavioralAnalysisResult {
39        let mut signals = Vec::new();
40        let mut score = 0;
41
42        Self::check_high_request_count(input, &mut score, &mut signals);
43        Self::check_high_page_coverage(input, &mut score, &mut signals);
44        Self::check_sequential_navigation(input, &mut score, &mut signals);
45        Self::check_multiple_fingerprint_sessions(input, &mut score, &mut signals);
46        Self::check_regular_timing(input, &mut score, &mut signals);
47        Self::check_high_pages_per_minute(input, &mut score, &mut signals);
48        Self::check_outdated_browser(input, &mut score, &mut signals);
49        Self::check_no_javascript_events(input, &mut score, &mut signals);
50
51        let is_suspicious = score >= BEHAVIORAL_BOT_THRESHOLD;
52        let reason = is_suspicious.then(|| {
53            signals
54                .iter()
55                .map(|s| s.signal_type.to_string())
56                .collect::<Vec<_>>()
57                .join(", ")
58        });
59
60        BehavioralAnalysisResult {
61            score,
62            is_suspicious,
63            signals,
64            reason,
65        }
66    }
67}