Skip to main content

security_rust/protocol/
cors.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3use regex::Regex;
4use std::sync::LazyLock;
5
6use crate::{regex_detect, AttackCategory, DetectionResult, Detector, Severity};
7
8static PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
9    vec![
10        Regex::new(r"(?i)Origin:\s*null").unwrap(),
11        Regex::new(r"(?i)Access-Control-Allow-Origin:\s*\*").unwrap(),
12        Regex::new(r"(?i)Access-Control-Allow-Credentials:\s*true").unwrap(),
13    ]
14});
15
16pub struct CorsDetector;
17
18impl Detector for CorsDetector {
19    fn name(&self) -> &'static str {
20        "cors"
21    }
22
23    fn detect(&self, input: &str) -> Option<DetectionResult> {
24        regex_detect(&PATTERNS, self.name(), AttackCategory::Protocol, Severity::Medium, "CORS bypass attempt detected", input)
25    }
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    fn assert_detected(input: &str) {
33        crate::test_helpers::assert_detected(
34            &CorsDetector,
35            input,
36            AttackCategory::Protocol,
37            Severity::Medium,
38        );
39    }
40
41    fn assert_clean(input: &str) {
42        crate::test_helpers::assert_clean(&CorsDetector, input);
43    }
44
45    #[test]
46    fn name_is_cors() {
47        assert_eq!(CorsDetector.name(), "cors");
48    }
49
50    #[test]
51    fn detects_null_origin() {
52        assert_detected("Origin: null");
53        assert_detected("Origin:null");
54    }
55
56    #[test]
57    fn detects_wildcard_allow_origin() {
58        assert_detected("Access-Control-Allow-Origin: *");
59        assert_detected("Access-Control-Allow-Origin:*");
60    }
61
62    #[test]
63    fn detects_allow_credentials() {
64        assert_detected("Access-Control-Allow-Credentials: true");
65    }
66
67    #[test]
68    fn detects_mixed_case() {
69        assert_detected("origin: NULL");
70        assert_detected("access-control-allow-origin: *");
71    }
72
73    #[test]
74    fn rejects_benign_headers() {
75        assert_clean("Origin: http://example.com");
76        assert_clean("Access-Control-Allow-Origin: https://example.com");
77        assert_clean("Access-Control-Allow-Credentials: false");
78        assert_clean("Origin: *");
79    }
80
81    #[test]
82    fn rejects_space_before_colon() {
83        assert_clean("Access-Control-Allow-Origin : *");
84    }
85
86    #[test]
87    fn rejects_empty_and_whitespace() {
88        assert_clean("");
89        assert_clean("   ");
90    }
91
92    #[test]
93    fn rejects_unicode_text() {
94        assert_clean("跨域配置说明,无攻击");
95    }
96}