Skip to main content

bulwark_security/security/
inspector_user_agent.rs

1use crate::request::context::RequestContext;
2use crate::security::inspector::{Inspector, InspectorFinding};
3use crate::security::FindingSeverity;
4use crate::BulwarkError;
5
6/// InspectorUserAgent
7///
8/// Mengecek User-Agent yang mencurigakan.
9/// Heuristic sederhana:
10/// - Kosong / tidak ada -> Medium
11/// - Mengandung keyword berisiko -> Medium
12/// - Terlalu panjang -> High
13pub struct InspectorUserAgent {
14    /// Panjang maksimum UA yang masih dianggap wajar
15    max_length: usize,
16
17    /// Daftar keyword mencurigakan (lowercase)
18    suspicious_keywords: Vec<&'static str>,
19}
20
21impl InspectorUserAgent {
22    pub fn new(max_length: usize, suspicious_keywords: Vec<&'static str>) -> Self {
23        Self {
24            max_length,
25            suspicious_keywords,
26        }
27    }
28}
29
30impl Inspector for InspectorUserAgent {
31    fn inspect(&self, ctx: &RequestContext) -> Result<Option<InspectorFinding>, BulwarkError> {
32        let ua = match ctx.headers.get("user-agent") {
33            Some(v) => v,
34            None => {
35                return Ok(Some(InspectorFinding::new(
36                    "inspector_user_agent",
37                    FindingSeverity::Medium,
38                    "missing user-agent header",
39                )));
40            }
41        };
42
43        // Terlalu panjang -> High
44        if ua.len() > self.max_length {
45            return Ok(Some(InspectorFinding::new(
46                "inspector_user_agent",
47                FindingSeverity::High,
48                format!(
49                    "user-agent length {} exceeds max {}",
50                    ua.len(),
51                    self.max_length
52                ),
53            )));
54        }
55
56        let ua_lower = ua.to_lowercase();
57
58        // Mengandung keyword mencurigakan -> Medium
59        for keyword in &self.suspicious_keywords {
60            if ua_lower.contains(keyword) {
61                return Ok(Some(InspectorFinding::new(
62                    "inspector_user_agent",
63                    FindingSeverity::Medium,
64                    format!("user-agent contains suspicious keyword `{}`", keyword),
65                )));
66            }
67        }
68
69        // Aman
70        Ok(None)
71    }
72}