1use crate::AuthContext;
7use pulseengine_mcp_protocol::Request;
8use regex::Regex;
9use serde_json::Value;
10use std::collections::{HashMap, HashSet};
11use thiserror::Error;
12use tracing::{debug, error, warn};
13
14#[derive(Debug, Error)]
16pub enum SecurityValidationError {
17 #[error("Request too large: {current} bytes exceeds limit of {limit} bytes")]
18 RequestTooLarge { current: usize, limit: usize },
19
20 #[error("Parameter value too large: {param} has {current} bytes, limit is {limit} bytes")]
21 ParameterTooLarge {
22 param: String,
23 current: usize,
24 limit: usize,
25 },
26
27 #[error("Too many parameters: {current} exceeds limit of {limit}")]
28 TooManyParameters { current: usize, limit: usize },
29
30 #[error("Invalid parameter name: {name}")]
31 InvalidParameterName { name: String },
32
33 #[error("Potential injection attack detected in parameter: {param}")]
34 InjectionDetected { param: String },
35
36 #[error("Malicious content detected: {reason}")]
37 MaliciousContent { reason: String },
38
39 #[error("Rate limit exceeded for method: {method}")]
40 RateLimitExceeded { method: String },
41
42 #[error("Unsupported method: {method}")]
43 UnsupportedMethod { method: String },
44}
45
46#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
48pub struct SecurityViolation {
49 pub violation_type: SecurityViolationType,
51
52 pub severity: SecuritySeverity,
54
55 pub description: String,
57
58 pub field: Option<String>,
60
61 pub value: Option<String>,
63
64 pub timestamp: chrono::DateTime<chrono::Utc>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
70pub enum SecurityViolationType {
71 SizeLimit,
72 ParameterLimit,
73 InjectionAttempt,
74 MaliciousContent,
75 InvalidFormat,
76 RateLimit,
77 UnauthorizedMethod,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
82pub enum SecuritySeverity {
83 Low,
84 Medium,
85 High,
86 Critical,
87}
88
89#[derive(Debug, Clone)]
91pub struct RequestLimitsConfig {
92 pub max_request_size: usize,
94
95 pub max_parameters: usize,
97
98 pub max_parameter_size: usize,
100
101 pub max_string_length: usize,
103
104 pub max_array_length: usize,
106
107 pub max_object_depth: usize,
109
110 pub max_object_keys: usize,
112}
113
114impl Default for RequestLimitsConfig {
115 fn default() -> Self {
116 Self {
117 max_request_size: 10 * 1024 * 1024, max_parameters: 100,
119 max_parameter_size: 1024 * 1024, max_string_length: 10000,
121 max_array_length: 1000,
122 max_object_depth: 10,
123 max_object_keys: 100,
124 }
125 }
126}
127
128#[derive(Debug, Clone)]
130pub struct RequestSecurityConfig {
131 pub enabled: bool,
133
134 pub limits: RequestLimitsConfig,
136
137 pub enable_injection_detection: bool,
139
140 pub enable_sanitization: bool,
142
143 pub allowed_methods: HashSet<String>,
145
146 pub blocked_methods: HashSet<String>,
148
149 pub enable_method_rate_limiting: bool,
151
152 pub method_rate_limits: HashMap<String, u32>,
154
155 pub log_violations: bool,
157
158 pub fail_on_violations: bool,
160}
161
162impl Default for RequestSecurityConfig {
163 fn default() -> Self {
164 let mut method_rate_limits = HashMap::new();
165 method_rate_limits.insert("tools/call".to_string(), 60); method_rate_limits.insert("resources/read".to_string(), 120); Self {
169 enabled: true,
170 limits: RequestLimitsConfig::default(),
171 enable_injection_detection: true,
172 enable_sanitization: true,
173 allowed_methods: HashSet::new(), blocked_methods: HashSet::new(),
175 enable_method_rate_limiting: false, method_rate_limits,
177 log_violations: true,
178 fail_on_violations: true,
179 }
180 }
181}
182
183pub struct InputSanitizer {
185 sql_patterns: Vec<Regex>,
187
188 xss_patterns: Vec<Regex>,
190
191 command_patterns: Vec<Regex>,
193
194 path_traversal_patterns: Vec<Regex>,
196}
197
198impl InputSanitizer {
199 pub fn new() -> Self {
201 Self {
202 sql_patterns: Self::build_sql_patterns(),
203 xss_patterns: Self::build_xss_patterns(),
204 command_patterns: Self::build_command_patterns(),
205 path_traversal_patterns: Self::build_path_traversal_patterns(),
206 }
207 }
208
209 fn build_sql_patterns() -> Vec<Regex> {
211 let patterns = [
212 r"(?i)(union\s+select|select\s+.*\s+from|insert\s+into|delete\s+from|drop\s+table)",
213 r"(?i)(exec\s*\(|execute\s*\(|sp_|xp_)",
214 r"(?i)(\bor\b\s+\d+\s*=\s*\d+|\band\b\s+\d+\s*=\s*\d+)",
215 r"(?i)(sleep\s*\(|benchmark\s*\(|waitfor\s+delay)",
216 r#"['";]\s*(\bunion\b|\bselect\b|\binsert\b|\bdelete\b|\bdrop\b)"#,
217 ];
218
219 patterns
220 .iter()
221 .filter_map(|pattern| Regex::new(pattern).ok())
222 .collect()
223 }
224
225 fn build_xss_patterns() -> Vec<Regex> {
227 let patterns = [
228 r"(?i)<script[^>]*>.*?</script>",
229 r"(?i)javascript:",
230 r"(?i)on\w+\s*=",
231 r"(?i)<iframe[^>]*>.*?</iframe>",
232 r"(?i)eval\s*\(",
233 ];
234
235 patterns
236 .iter()
237 .filter_map(|pattern| Regex::new(pattern).ok())
238 .collect()
239 }
240
241 fn build_command_patterns() -> Vec<Regex> {
243 let patterns = [
244 r"[;&|`$()]",
245 r"(?i)(cmd|powershell|bash|sh)\s",
246 r"\.\.\/",
247 r"(?i)(\bcat\b|\bls\b|\bpwd\b|\bwhoami\b|\bps\b|\btop\b)",
248 ];
249
250 patterns
251 .iter()
252 .filter_map(|pattern| Regex::new(pattern).ok())
253 .collect()
254 }
255
256 fn build_path_traversal_patterns() -> Vec<Regex> {
258 let patterns = [
259 r"\.\.\/",
260 r"\.\.\\",
261 r"%2e%2e%2f",
262 r"%2e%2e%5c",
263 r"(?i)\.\.[\\/]",
264 ];
265
266 patterns
267 .iter()
268 .filter_map(|pattern| Regex::new(pattern).ok())
269 .collect()
270 }
271
272 pub fn detect_injection(&self, value: &str) -> Vec<String> {
274 let mut violations = Vec::new();
275
276 for pattern in &self.sql_patterns {
278 if pattern.is_match(value) {
279 violations.push("SQL injection attempt detected".to_string());
280 break;
281 }
282 }
283
284 for pattern in &self.xss_patterns {
286 if pattern.is_match(value) {
287 violations.push("XSS attempt detected".to_string());
288 break;
289 }
290 }
291
292 for pattern in &self.command_patterns {
294 if pattern.is_match(value) {
295 violations.push("Command injection attempt detected".to_string());
296 break;
297 }
298 }
299
300 for pattern in &self.path_traversal_patterns {
302 if pattern.is_match(value) {
303 violations.push("Path traversal attempt detected".to_string());
304 break;
305 }
306 }
307
308 violations
309 }
310
311 pub fn sanitize_string(&self, value: &str) -> String {
313 let mut sanitized = value.to_string();
314
315 sanitized = sanitized.replace('\0', "");
317
318 sanitized = sanitized.replace('<', "<");
320 sanitized = sanitized.replace('>', ">");
321 sanitized = sanitized.replace('\"', """);
322 sanitized = sanitized.replace('\'', "'");
323
324 sanitized = sanitized
326 .chars()
327 .filter(|&c| !c.is_control() || c == '\t' || c == '\n' || c == '\r')
328 .collect();
329
330 sanitized
331 }
332}
333
334impl Default for InputSanitizer {
335 fn default() -> Self {
336 Self::new()
337 }
338}
339
340pub struct RequestSecurityValidator {
342 config: RequestSecurityConfig,
343 sanitizer: InputSanitizer,
344 violation_log: std::sync::Arc<std::sync::Mutex<Vec<SecurityViolation>>>,
345}
346
347impl RequestSecurityValidator {
348 pub fn new(config: RequestSecurityConfig) -> Self {
350 Self {
351 config,
352 sanitizer: InputSanitizer::new(),
353 violation_log: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
354 }
355 }
356
357 pub fn default() -> Self {
359 Self::new(RequestSecurityConfig::default())
360 }
361
362 pub async fn validate_request(
364 &self,
365 request: &Request,
366 auth_context: Option<&AuthContext>,
367 ) -> Result<(), SecurityValidationError> {
368 if !self.config.enabled {
369 return Ok(());
370 }
371
372 debug!("Validating request security for method: {}", request.method);
373
374 if let Some(context) = auth_context {
376 self.validate_user_specific_rules(request, context)?;
377 }
378
379 self.validate_method(&request.method)?;
381
382 let request_size = serde_json::to_string(request)
384 .map_err(|_| SecurityValidationError::MaliciousContent {
385 reason: "Request serialization failed".to_string(),
386 })?
387 .len();
388
389 if request_size > self.config.limits.max_request_size {
390 self.log_violation(SecurityViolation {
391 violation_type: SecurityViolationType::SizeLimit,
392 severity: SecuritySeverity::High,
393 description: format!(
394 "Request size {} exceeds limit {}",
395 request_size, self.config.limits.max_request_size
396 ),
397 field: None,
398 value: None,
399 timestamp: chrono::Utc::now(),
400 });
401
402 return Err(SecurityValidationError::RequestTooLarge {
403 current: request_size,
404 limit: self.config.limits.max_request_size,
405 });
406 }
407
408 self.validate_parameters(&request.params, "params")?;
410
411 if self.config.enable_injection_detection {
413 self.detect_injection_attempts(&request.params, "params")?;
414 }
415
416 debug!("Request passed security validation");
417 Ok(())
418 }
419
420 pub async fn sanitize_request(&self, mut request: Request) -> Request {
422 if !self.config.enabled || !self.config.enable_sanitization {
423 return request;
424 }
425
426 debug!("Sanitizing request parameters");
427 request.params = self.sanitize_value(&request.params);
428 request
429 }
430
431 fn validate_method(&self, method: &str) -> Result<(), SecurityValidationError> {
433 if self.config.blocked_methods.contains(method) {
435 return Err(SecurityValidationError::UnsupportedMethod {
436 method: method.to_string(),
437 });
438 }
439
440 if !self.config.allowed_methods.is_empty() && !self.config.allowed_methods.contains(method)
442 {
443 return Err(SecurityValidationError::UnsupportedMethod {
444 method: method.to_string(),
445 });
446 }
447
448 Ok(())
449 }
450
451 fn validate_parameters(
453 &self,
454 value: &Value,
455 path: &str,
456 ) -> Result<(), SecurityValidationError> {
457 self.validate_value_size(value, path)?;
458
459 match value {
460 Value::Object(obj) => {
461 if obj.len() > self.config.limits.max_object_keys {
462 return Err(SecurityValidationError::TooManyParameters {
463 current: obj.len(),
464 limit: self.config.limits.max_object_keys,
465 });
466 }
467
468 for (key, val) in obj {
469 let new_path = format!("{}.{}", path, key);
470 self.validate_parameters(val, &new_path)?;
471 }
472 }
473 Value::Array(arr) => {
474 if arr.len() > self.config.limits.max_array_length {
475 return Err(SecurityValidationError::TooManyParameters {
476 current: arr.len(),
477 limit: self.config.limits.max_array_length,
478 });
479 }
480
481 for (i, val) in arr.iter().enumerate() {
482 let new_path = format!("{}[{}]", path, i);
483 self.validate_parameters(val, &new_path)?;
484 }
485 }
486 Value::String(s) => {
487 if s.len() > self.config.limits.max_string_length {
488 return Err(SecurityValidationError::ParameterTooLarge {
489 param: path.to_string(),
490 current: s.len(),
491 limit: self.config.limits.max_string_length,
492 });
493 }
494 }
495 _ => {} }
497
498 Ok(())
499 }
500
501 fn validate_value_size(
503 &self,
504 value: &Value,
505 path: &str,
506 ) -> Result<(), SecurityValidationError> {
507 let size = serde_json::to_string(value)
508 .map_err(|_| SecurityValidationError::MaliciousContent {
509 reason: "Parameter serialization failed".to_string(),
510 })?
511 .len();
512
513 if size > self.config.limits.max_parameter_size {
514 return Err(SecurityValidationError::ParameterTooLarge {
515 param: path.to_string(),
516 current: size,
517 limit: self.config.limits.max_parameter_size,
518 });
519 }
520
521 Ok(())
522 }
523
524 fn detect_injection_attempts(
526 &self,
527 value: &Value,
528 path: &str,
529 ) -> Result<(), SecurityValidationError> {
530 match value {
531 Value::String(s) => {
532 let violations = self.sanitizer.detect_injection(s);
533 if !violations.is_empty() {
534 self.log_violation(SecurityViolation {
535 violation_type: SecurityViolationType::InjectionAttempt,
536 severity: SecuritySeverity::Critical,
537 description: violations.join(", "),
538 field: Some(path.to_string()),
539 value: Some(s.clone()),
540 timestamp: chrono::Utc::now(),
541 });
542
543 return Err(SecurityValidationError::InjectionDetected {
544 param: path.to_string(),
545 });
546 }
547 }
548 Value::Object(obj) => {
549 for (key, val) in obj {
550 let new_path = format!("{}.{}", path, key);
551 self.detect_injection_attempts(val, &new_path)?;
552 }
553 }
554 Value::Array(arr) => {
555 for (i, val) in arr.iter().enumerate() {
556 let new_path = format!("{}[{}]", path, i);
557 self.detect_injection_attempts(val, &new_path)?;
558 }
559 }
560 _ => {} }
562
563 Ok(())
564 }
565
566 fn sanitize_value(&self, value: &Value) -> Value {
568 match value {
569 Value::String(s) => Value::String(self.sanitizer.sanitize_string(s)),
570 Value::Object(obj) => {
571 let sanitized_obj: serde_json::Map<String, Value> = obj
572 .iter()
573 .map(|(k, v)| (k.clone(), self.sanitize_value(v)))
574 .collect();
575 Value::Object(sanitized_obj)
576 }
577 Value::Array(arr) => {
578 let sanitized_arr: Vec<Value> =
579 arr.iter().map(|v| self.sanitize_value(v)).collect();
580 Value::Array(sanitized_arr)
581 }
582 _ => value.clone(), }
584 }
585
586 fn log_violation(&self, violation: SecurityViolation) {
588 if self.config.log_violations {
589 match violation.severity {
590 SecuritySeverity::Critical => {
591 error!("Critical security violation: {}", violation.description)
592 }
593 SecuritySeverity::High => {
594 warn!("High security violation: {}", violation.description)
595 }
596 SecuritySeverity::Medium => {
597 warn!("Medium security violation: {}", violation.description)
598 }
599 SecuritySeverity::Low => {
600 debug!("Low security violation: {}", violation.description)
601 }
602 }
603 }
604
605 if let Ok(mut log) = self.violation_log.lock() {
606 log.push(violation);
607
608 if log.len() > 1000 {
610 log.drain(0..100);
611 }
612 }
613 }
614
615 pub fn get_violations(&self) -> Vec<SecurityViolation> {
617 self.violation_log
618 .lock()
619 .map(|log| log.clone())
620 .unwrap_or_default()
621 }
622
623 pub fn clear_violations(&self) {
625 if let Ok(mut log) = self.violation_log.lock() {
626 log.clear();
627 }
628 }
629
630 fn validate_user_specific_rules(
632 &self,
633 request: &Request,
634 auth_context: &AuthContext,
635 ) -> Result<(), SecurityValidationError> {
636 let user_limits = self.get_user_specific_limits(auth_context);
638
639 let request_size = serde_json::to_string(request)
641 .map_err(|_| SecurityValidationError::MaliciousContent {
642 reason: "Request serialization failed for user validation".to_string(),
643 })?
644 .len();
645
646 if request_size > user_limits.max_request_size {
647 self.log_violation(SecurityViolation {
648 violation_type: SecurityViolationType::SizeLimit,
649 severity: SecuritySeverity::High,
650 description: format!(
651 "User {} exceeded request size limit: {} > {}",
652 auth_context.user_id.as_deref().unwrap_or("unknown"),
653 request_size,
654 user_limits.max_request_size
655 ),
656 field: None,
657 value: None,
658 timestamp: chrono::Utc::now(),
659 });
660
661 return Err(SecurityValidationError::RequestTooLarge {
662 current: request_size,
663 limit: user_limits.max_request_size,
664 });
665 }
666
667 if let Some(restricted_methods) = self.get_restricted_methods_for_user(auth_context) {
669 if restricted_methods.contains(&request.method) {
670 self.log_violation(SecurityViolation {
671 violation_type: SecurityViolationType::UnauthorizedMethod,
672 severity: SecuritySeverity::Critical,
673 description: format!(
674 "User {} attempted to access restricted method: {}",
675 auth_context.user_id.as_deref().unwrap_or("unknown"),
676 request.method
677 ),
678 field: Some("method".to_string()),
679 value: Some(request.method.clone()),
680 timestamp: chrono::Utc::now(),
681 });
682
683 return Err(SecurityValidationError::UnsupportedMethod {
684 method: request.method.clone(),
685 });
686 }
687 }
688
689 if auth_context.user_id.is_none() {
691 self.validate_anonymous_user_request(request)?;
693 }
694
695 Ok(())
696 }
697
698 fn get_user_specific_limits(&self, auth_context: &AuthContext) -> RequestLimitsConfig {
700 use crate::models::Role;
701
702 let mut limits = self.config.limits.clone();
704
705 let has_admin_role = auth_context
707 .roles
708 .iter()
709 .any(|role| matches!(role, Role::Admin));
710 let has_operator_role = auth_context
711 .roles
712 .iter()
713 .any(|role| matches!(role, Role::Operator));
714 let has_device_role = auth_context
715 .roles
716 .iter()
717 .any(|role| matches!(role, Role::Device { .. }));
718
719 if has_device_role && !has_admin_role {
720 limits.max_request_size = std::cmp::min(limits.max_request_size, 64 * 1024); limits.max_parameter_size = std::cmp::min(limits.max_parameter_size, 8 * 1024); limits.max_string_length = std::cmp::min(limits.max_string_length, 1000);
724 limits.max_array_length = std::cmp::min(limits.max_array_length, 50);
725 limits.max_object_keys = std::cmp::min(limits.max_object_keys, 20);
726 } else if !has_admin_role && !has_operator_role {
727 limits.max_request_size = std::cmp::min(limits.max_request_size, 256 * 1024); limits.max_parameter_size = std::cmp::min(limits.max_parameter_size, 32 * 1024); limits.max_string_length = std::cmp::min(limits.max_string_length, 5000);
731 limits.max_array_length = std::cmp::min(limits.max_array_length, 200);
732 limits.max_object_keys = std::cmp::min(limits.max_object_keys, 50);
733 }
734 limits
737 }
738
739 fn get_restricted_methods_for_user(
741 &self,
742 auth_context: &AuthContext,
743 ) -> Option<HashSet<String>> {
744 use crate::models::Role;
745
746 let has_admin_role = auth_context
747 .roles
748 .iter()
749 .any(|role| matches!(role, Role::Admin));
750
751 if has_admin_role {
753 return None;
754 }
755
756 let mut restricted = HashSet::new();
757
758 let has_device_role = auth_context
760 .roles
761 .iter()
762 .any(|role| matches!(role, Role::Device { .. }));
763 if has_device_role {
764 restricted.insert("logging/setLevel".to_string());
766 restricted.insert("server/shutdown".to_string());
767 restricted.insert("auth/createKey".to_string());
768 restricted.insert("auth/revokeKey".to_string());
769 }
770
771 let has_monitor_role = auth_context
773 .roles
774 .iter()
775 .any(|role| matches!(role, Role::Monitor));
776 if has_monitor_role
777 && !auth_context
778 .roles
779 .iter()
780 .any(|role| matches!(role, Role::Operator))
781 {
782 restricted.insert("tools/call".to_string());
784 restricted.insert("resources/write".to_string());
785 }
786
787 if restricted.is_empty() {
788 None
789 } else {
790 Some(restricted)
791 }
792 }
793
794 fn validate_anonymous_user_request(
796 &self,
797 request: &Request,
798 ) -> Result<(), SecurityValidationError> {
799 self.detect_injection_attempts_strict(&request.params, "params")?;
801
802 let read_only_methods = [
804 "ping",
805 "initialize",
806 "resources/list",
807 "resources/read",
808 "tools/list",
809 "completion/complete",
810 ];
811
812 if !read_only_methods.contains(&request.method.as_str()) {
813 self.log_violation(SecurityViolation {
814 violation_type: SecurityViolationType::UnauthorizedMethod,
815 severity: SecuritySeverity::High,
816 description: format!(
817 "Anonymous user attempted non-read-only method: {}",
818 request.method
819 ),
820 field: Some("method".to_string()),
821 value: Some(request.method.clone()),
822 timestamp: chrono::Utc::now(),
823 });
824
825 return Err(SecurityValidationError::UnsupportedMethod {
826 method: request.method.clone(),
827 });
828 }
829
830 Ok(())
831 }
832
833 fn detect_injection_attempts_strict(
835 &self,
836 value: &Value,
837 path: &str,
838 ) -> Result<(), SecurityValidationError> {
839 match value {
840 Value::String(s) => {
841 let violations = self.sanitizer.detect_injection(s);
843
844 let suspicious_patterns = [
846 "eval", "exec", "system", "cmd", "shell", "script", "import", "require",
847 "include", "load",
848 ];
849
850 let lower_s = s.to_lowercase();
851 for pattern in &suspicious_patterns {
852 if lower_s.contains(pattern) {
853 self.log_violation(SecurityViolation {
854 violation_type: SecurityViolationType::InjectionAttempt,
855 severity: SecuritySeverity::Critical,
856 description: format!(
857 "Suspicious pattern '{}' detected in anonymous user request",
858 pattern
859 ),
860 field: Some(path.to_string()),
861 value: Some(s.clone()),
862 timestamp: chrono::Utc::now(),
863 });
864
865 return Err(SecurityValidationError::InjectionDetected {
866 param: path.to_string(),
867 });
868 }
869 }
870
871 if !violations.is_empty() {
872 self.log_violation(SecurityViolation {
873 violation_type: SecurityViolationType::InjectionAttempt,
874 severity: SecuritySeverity::Critical,
875 description: format!(
876 "Enhanced injection detection: {}",
877 violations.join(", ")
878 ),
879 field: Some(path.to_string()),
880 value: Some(s.clone()),
881 timestamp: chrono::Utc::now(),
882 });
883
884 return Err(SecurityValidationError::InjectionDetected {
885 param: path.to_string(),
886 });
887 }
888 }
889 Value::Object(obj) => {
890 for (key, val) in obj {
891 let new_path = format!("{}.{}", path, key);
892 self.detect_injection_attempts_strict(val, &new_path)?;
893 }
894 }
895 Value::Array(arr) => {
896 for (i, val) in arr.iter().enumerate() {
897 let new_path = format!("{}[{}]", path, i);
898 self.detect_injection_attempts_strict(val, &new_path)?;
899 }
900 }
901 _ => {} }
903
904 Ok(())
905 }
906}
907
908impl RequestSecurityConfig {
910 pub fn permissive() -> Self {
912 Self {
913 enabled: true,
914 limits: RequestLimitsConfig {
915 max_request_size: 100 * 1024 * 1024, max_parameters: 1000,
917 max_parameter_size: 10 * 1024 * 1024, max_string_length: 100_000,
919 max_array_length: 10_000,
920 max_object_depth: 20,
921 max_object_keys: 1000,
922 },
923 enable_injection_detection: false,
924 enable_sanitization: false,
925 allowed_methods: HashSet::new(),
926 blocked_methods: HashSet::new(),
927 enable_method_rate_limiting: false,
928 method_rate_limits: HashMap::new(),
929 log_violations: true,
930 fail_on_violations: false,
931 }
932 }
933
934 pub fn strict() -> Self {
936 let mut blocked_methods = HashSet::new();
937 blocked_methods.insert("logging/setLevel".to_string()); Self {
940 enabled: true,
941 limits: RequestLimitsConfig {
942 max_request_size: 1024 * 1024, max_parameters: 50,
944 max_parameter_size: 100 * 1024, max_string_length: 1000,
946 max_array_length: 100,
947 max_object_depth: 5,
948 max_object_keys: 20,
949 },
950 enable_injection_detection: true,
951 enable_sanitization: true,
952 allowed_methods: HashSet::new(),
953 blocked_methods,
954 enable_method_rate_limiting: true,
955 method_rate_limits: {
956 let mut limits = HashMap::new();
957 limits.insert("tools/call".to_string(), 30); limits.insert("resources/read".to_string(), 60); limits
960 },
961 log_violations: true,
962 fail_on_violations: true,
963 }
964 }
965}
966
967#[cfg(test)]
968mod tests {
969 use super::*;
970
971 #[test]
972 fn test_security_severity_ordering() {
973 assert!(SecuritySeverity::Low < SecuritySeverity::Medium);
974 assert!(SecuritySeverity::Medium < SecuritySeverity::High);
975 assert!(SecuritySeverity::High < SecuritySeverity::Critical);
976 }
977}