systemprompt_analytics/services/behavioral_detector/
mod.rs1mod 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 pub const GHOST_SESSION: i32 = 35;
18}
19
20pub mod thresholds {
21 pub const REQUEST_COUNT_LIMIT: i64 = 50;
22 pub const PAGE_COVERAGE_PERCENT: f64 = 60.0;
23 pub const FINGERPRINT_SESSION_LIMIT: i64 = 5;
24 pub const PAGES_PER_MINUTE_LIMIT: f64 = 5.0;
25 pub const TIMING_VARIANCE_MIN: f64 = 0.1;
26 pub const CHROME_MIN_VERSION: i32 = 120;
27 pub const FIREFOX_MIN_VERSION: i32 = 120;
28 pub const NO_JS_MIN_REQUESTS: i64 = 3;
29 pub const GHOST_SESSION_MIN_AGE_SECONDS: i64 = 30;
30}
31
32#[derive(Debug, Clone, Copy, Default)]
33pub struct BehavioralBotDetector;
34
35impl BehavioralBotDetector {
36 pub const fn new() -> Self {
37 Self
38 }
39
40 pub fn analyze(input: &BehavioralAnalysisInput) -> BehavioralAnalysisResult {
41 let mut signals = Vec::new();
42 let mut score = 0;
43
44 Self::check_high_request_count(input, &mut score, &mut signals);
45 Self::check_high_page_coverage(input, &mut score, &mut signals);
46 Self::check_sequential_navigation(input, &mut score, &mut signals);
47 Self::check_multiple_fingerprint_sessions(input, &mut score, &mut signals);
48 Self::check_regular_timing(input, &mut score, &mut signals);
49 Self::check_high_pages_per_minute(input, &mut score, &mut signals);
50 Self::check_outdated_browser(input, &mut score, &mut signals);
51 Self::check_no_javascript_events(input, &mut score, &mut signals);
52 Self::check_ghost_session(input, &mut score, &mut signals);
53
54 let is_suspicious = score >= BEHAVIORAL_BOT_THRESHOLD;
55 let reason = is_suspicious.then(|| {
56 signals
57 .iter()
58 .map(|s| s.signal_type.to_string())
59 .collect::<Vec<_>>()
60 .join(", ")
61 });
62
63 BehavioralAnalysisResult {
64 score,
65 is_suspicious,
66 signals,
67 reason,
68 }
69 }
70}