bulwark_security/security/
inspector_user_agent.rs1use crate::request::context::RequestContext;
2use crate::security::inspector::{Inspector, InspectorFinding};
3use crate::security::FindingSeverity;
4use crate::BulwarkError;
5
6pub struct InspectorUserAgent {
14 max_length: usize,
16
17 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 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 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 Ok(None)
71 }
72}