Skip to main content

security_rust/
lib.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! Rust 编写的攻击检测库:32 个无状态检测器(注入 / 协议 / 数据 / 文件四类)
4//! + 三个有状态模块(会话安全、限流封禁、风险评分)。`[dependencies]` 只有 `regex`。
5//!
6//! ```text
7//!       (o)(o)
8//!    ⌕┬─────────┬!     甲哨 Sentri
9//!     │ · · · · │       只报告,不拦截
10//!     └──┬───┬──┘       32 detectors / 4 categories
11//!       /     \         deps = regex ×1
12//! ```
13//!
14//! 项目宠物 **甲哨 Sentri**:32 片甲是 32 个检测器,左钳的放大镜负责看,右钳的
15//! 告示牌负责报,但两只钳子都不替调用方做决定(唯一的例外是
16//! [`SessionGuard`])。形象见 [`pet`] 模块。
17//!
18//! ```
19//! use security_rust::Scanner;
20//!
21//! let results = Scanner::default().scan("<script>alert(1)</script>");
22//! assert_eq!(results[0].attack_type, "xss");
23//! ```
24
25use regex::Regex;
26
27pub mod data;
28pub mod file;
29pub mod injection;
30pub mod pet;
31pub mod protocol;
32pub mod result;
33pub mod scanner;
34pub mod score;
35pub mod session;
36pub mod throttle;
37
38pub use result::{AttackCategory, DetectionResult, Severity};
39pub use scanner::{Scanner, ScannerBuilder};
40pub use score::{RiskAssessment, RiskLevel, assess};
41pub use session::{
42    Decision, LoginPoint, MemoryStore, RequestContext, SessionConfig, SessionError, SessionGuard,
43    SessionRecord, SessionStore, SessionThreat, SessionVerdict, StoreError,
44};
45pub use throttle::{
46    MemoryThrottleStore, Throttle, ThrottleConfig, ThrottleDecision, ThrottleOutcome, ThrottleStore,
47};
48
49pub trait Detector: Send + Sync {
50    fn name(&self) -> &'static str;
51    fn detect(&self, input: &str) -> Option<DetectionResult>;
52}
53
54pub(crate) fn regex_detect(
55    patterns: &[Regex],
56    name: &'static str,
57    category: AttackCategory,
58    severity: Severity,
59    message: &'static str,
60    input: &str,
61) -> Option<DetectionResult> {
62    for re in patterns {
63        if let Some(m) = re.find(input) {
64            return Some(DetectionResult {
65                attack_type: name.to_string(),
66                category,
67                severity,
68                matched_pattern: m.as_str().to_string(),
69                offset: m.start(),
70                message: message.into(),
71            });
72        }
73    }
74    None
75}
76
77#[cfg(test)]
78pub(crate) mod test_helpers {
79    use super::*;
80
81    pub(crate) fn assert_detected<D: Detector>(
82        d: &D,
83        input: &str,
84        category: AttackCategory,
85        severity: Severity,
86    ) {
87        let r = d.detect(input).expect("expected detection");
88        assert_eq!(r.attack_type, d.name());
89        assert_eq!(r.category, category);
90        assert_eq!(r.severity, severity);
91        assert!(!r.matched_pattern.is_empty(), "matched_pattern empty");
92        assert!(
93            r.offset <= input.len(),
94            "offset {} > len {}",
95            r.offset,
96            input.len()
97        );
98        assert_eq!(
99            &input[r.offset..r.offset + r.matched_pattern.len()],
100            r.matched_pattern
101        );
102        assert!(!r.message.is_empty());
103    }
104
105    pub(crate) fn assert_clean<D: Detector>(d: &D, input: &str) {
106        assert!(d.detect(input).is_none(), "not detected: {input:?}");
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn detector_trait_object_is_send_sync() {
116        let detector: Box<dyn Detector> = Box::new(injection::XssDetector);
117        assert_eq!(detector.name(), "xss");
118    }
119
120    #[test]
121    fn detector_name_is_static_str() {
122        let name: &'static str = injection::XssDetector.name();
123        assert_eq!(name, "xss");
124    }
125}