Skip to main content

synapse_pingora/interrogator/
mod.rs

1//! Interrogator System - Progressive Challenge Escalation
2//!
3//! Implements a multi-level challenge system for suspicious actors:
4//! 1. Cookie Challenge - Silent tracking cookie
5//! 2. JS PoW Challenge - Proof-of-work computation
6//! 3. CAPTCHA Challenge - Human verification (stub)
7//! 4. Tarpit - Progressive delays (already implemented)
8//! 5. Block - Hard block with custom page
9//!
10//! # Architecture
11//!
12//! The system uses a progressive escalation model where each failed challenge
13//! or continued suspicious behavior moves the actor to a harder challenge level.
14//! The ProgressionManager orchestrates the challenge selection based on actor
15//! history and current risk score.
16//!
17//! ```text
18//! +----------+     +-------------+     +---------+     +--------+     +-------+
19//! | Cookie   | --> | JS PoW      | --> | CAPTCHA | --> | Tarpit | --> | Block |
20//! | (silent) |     | (compute)   |     | (human) |     | (slow) |     | (hard)|
21//! +----------+     +-------------+     +---------+     +--------+     +-------+
22//!     Level 1         Level 2          Level 3        Level 4       Level 5
23//! ```
24//!
25//! # Feature Flags
26//!
27//! - `ENABLE_COOKIE_CHALLENGE=true`: Enable cookie tracking
28//! - `ENABLE_JS_CHALLENGE=true`: Enable JavaScript PoW challenges
29//! - `ENABLE_CAPTCHA=false`: CAPTCHA is stubbed (future work)
30//! - `ENABLE_TARPIT=true`: Enable progressive delays
31//!
32//! # Dual-Running Mode
33//!
34//! Headers injected for observability:
35//! - `X-Challenge-Level`: Current challenge level for actor
36//! - `X-Challenge-Type`: Type of challenge issued (cookie/js/captcha/tarpit/block)
37
38pub mod captcha_manager;
39pub mod cookie_manager;
40pub mod injection_tracker;
41pub mod js_challenge_manager;
42pub mod progression_manager;
43
44pub use captcha_manager::{CaptchaChallenge, CaptchaConfig, CaptchaManager, CaptchaStats};
45pub use cookie_manager::{CookieChallenge, CookieConfig, CookieError, CookieManager, CookieStats};
46pub use injection_tracker::{
47    HeadlessIndicators, InjectionRecord, InjectionSummary, InjectionTracker,
48    InjectionTrackerConfig, InjectionTrackerStats,
49};
50pub use js_challenge_manager::{
51    JsChallenge, JsChallengeConfig, JsChallengeManager, JsChallengeStats,
52};
53pub use progression_manager::{
54    ActorChallengeState, ChallengeLevel, ProgressionConfig, ProgressionManager, ProgressionStats,
55    ProgressionStatsSnapshot,
56};
57
58/// Response to present to the actor
59#[derive(Debug, Clone)]
60pub enum ChallengeResponse {
61    /// No challenge needed, allow request
62    Allow,
63    /// Set a tracking cookie
64    Cookie {
65        name: String,
66        value: String,
67        max_age: u64,
68        http_only: bool,
69        secure: bool,
70    },
71    /// Present JavaScript proof-of-work challenge
72    JsChallenge {
73        html: String,
74        expected_solution: String,
75        expires_at: u64,
76    },
77    /// Present CAPTCHA (stub - returns HTML placeholder)
78    Captcha { html: String, session_id: String },
79    /// Apply tarpit delay
80    Tarpit { delay_ms: u64 },
81    /// Block with custom page
82    Block { html: String, status_code: u16 },
83}
84
85/// Result of validating a challenge response
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum ValidationResult {
88    Valid,
89    Invalid(String),
90    Expired,
91    NotFound,
92}
93
94/// Trait for challenge implementations
95pub trait Interrogator: Send + Sync {
96    /// Name of this interrogator
97    fn name(&self) -> &'static str;
98
99    /// Challenge level (1-5, lower = softer)
100    fn challenge_level(&self) -> u8;
101
102    /// Generate a challenge for the actor
103    fn generate_challenge(&self, actor_id: &str) -> ChallengeResponse;
104
105    /// Validate a challenge response
106    fn validate_response(&self, actor_id: &str, response: &str) -> ValidationResult;
107
108    /// Check if actor should escalate to next level
109    fn should_escalate(&self, actor_id: &str) -> bool;
110}