pub mod request_security;
pub use request_security::{
InputSanitizer, RequestLimitsConfig, RequestSecurityConfig, RequestSecurityValidator,
SecuritySeverity, SecurityValidationError, SecurityViolation, SecurityViolationType,
};
#[cfg(test)]
mod tests {
use super::*;
use pulseengine_mcp_protocol::Request;
use serde_json::json;
#[test]
fn test_security_module_exports() {
let config = RequestSecurityConfig::default();
assert!(config.limits.max_request_size > 0);
assert!(config.limits.max_parameters > 0);
let _sanitizer = InputSanitizer::new();
let violation = SecurityViolation {
violation_type: SecurityViolationType::SizeLimit,
severity: SecuritySeverity::High,
description: "Test violation".to_string(),
field: None,
value: None,
timestamp: chrono::Utc::now(),
};
assert_eq!(violation.violation_type, SecurityViolationType::SizeLimit);
assert_eq!(violation.severity, SecuritySeverity::High);
}
#[test]
fn test_security_severity_ordering() {
assert!(SecuritySeverity::Critical > SecuritySeverity::High);
assert!(SecuritySeverity::High > SecuritySeverity::Medium);
assert!(SecuritySeverity::Medium > SecuritySeverity::Low);
assert!(SecuritySeverity::Medium > SecuritySeverity::Low);
}
#[test]
fn test_security_violation_types() {
let violation_types = vec![
SecurityViolationType::SizeLimit,
SecurityViolationType::ParameterLimit,
SecurityViolationType::InjectionAttempt,
SecurityViolationType::MaliciousContent,
SecurityViolationType::InvalidFormat,
SecurityViolationType::RateLimit,
SecurityViolationType::UnauthorizedMethod,
];
for violation_type in violation_types {
let violation = SecurityViolation {
violation_type: violation_type.clone(),
severity: SecuritySeverity::Medium,
description: format!("Test {:?}", violation_type),
field: None,
value: None,
timestamp: chrono::Utc::now(),
};
assert_eq!(violation.violation_type, violation_type);
assert!(!violation.description.is_empty());
}
}
#[tokio::test]
async fn test_request_security_validator() {
let config = RequestSecurityConfig::default();
let validator = RequestSecurityValidator::new(config);
let valid_request = Request {
jsonrpc: "2.0".to_string(),
method: "tools/list".to_string(),
id: Some(pulseengine_mcp_protocol::NumberOrString::Number(1)),
params: json!({}),
};
let result = validator.validate_request(&valid_request, None).await;
assert!(result.is_ok());
let large_params = (0..1000)
.map(|i| (format!("param_{}", i), json!(i)))
.collect::<serde_json::Map<_, _>>();
let large_request = Request {
jsonrpc: "2.0".to_string(),
method: "tools/call".to_string(),
id: Some(pulseengine_mcp_protocol::NumberOrString::Number(2)),
params: json!(large_params),
};
let result = validator.validate_request(&large_request, None).await;
if result.is_err() {
match result.unwrap_err() {
SecurityValidationError::TooManyParameters { current, limit } => {
assert!(current > limit);
}
_ => panic!("Expected TooManyParameters error"),
}
}
}
#[test]
fn test_input_sanitizer() {
let sanitizer = InputSanitizer::new();
let normal_input = "hello world";
let sanitized = sanitizer.sanitize_string(normal_input);
assert_eq!(sanitized, normal_input);
let suspicious_input = "<script>alert('xss')</script>";
let sanitized = sanitizer.sanitize_string(suspicious_input);
assert!(sanitized != suspicious_input || sanitized.is_empty());
let long_input = "a".repeat(10000);
let sanitized = sanitizer.sanitize_string(&long_input);
assert!(sanitized.len() <= long_input.len());
}
#[test]
fn test_request_limits_config() {
let config = RequestLimitsConfig {
max_request_size: 1024,
max_parameters: 10,
max_parameter_size: 512,
max_string_length: 100,
max_array_length: 50,
max_object_depth: 5,
max_object_keys: 20,
};
assert_eq!(config.max_request_size, 1024);
assert_eq!(config.max_parameters, 10);
assert_eq!(config.max_string_length, 100);
assert_eq!(config.max_array_length, 50);
assert_eq!(config.max_object_depth, 5);
}
#[test]
fn test_security_config_presets() {
let permissive = RequestSecurityConfig::permissive();
let default = RequestSecurityConfig::default();
let strict = RequestSecurityConfig::strict();
assert!(strict.limits.max_request_size <= default.limits.max_request_size);
assert!(strict.limits.max_parameters <= default.limits.max_parameters);
assert!(permissive.limits.max_request_size >= default.limits.max_request_size);
assert!(permissive.limits.max_parameters >= default.limits.max_parameters);
}
#[test]
fn test_security_validation_error_types() {
let errors = vec![
SecurityValidationError::RequestTooLarge {
current: 1000,
limit: 500,
},
SecurityValidationError::TooManyParameters {
current: 50,
limit: 20,
},
SecurityValidationError::InjectionDetected {
param: "test_param".to_string(),
},
SecurityValidationError::MaliciousContent {
reason: "test malicious content".to_string(),
},
];
for error in errors {
let error_string = error.to_string();
assert!(!error_string.is_empty());
assert!(error_string.len() > 5);
}
}
#[tokio::test]
async fn test_security_integration() {
let config = RequestSecurityConfig::strict();
let validator = RequestSecurityValidator::new(config);
let sanitizer = InputSanitizer::new();
let suspicious_request = Request {
jsonrpc: "2.0".to_string(),
method: "tools/call".to_string(),
id: Some(pulseengine_mcp_protocol::NumberOrString::Number(1)),
params: json!({
"name": "test_tool",
"arguments": {
"input": "<script>alert('xss')</script>",
"data": "x".repeat(10000), }
}),
};
let validation_result = validator.validate_request(&suspicious_request, None).await;
if let Ok(_) = validation_result {
if let Some(args) = suspicious_request.params.get("arguments") {
if let Some(input) = args.get("input").and_then(|v| v.as_str()) {
let sanitized = sanitizer.sanitize_string(input);
assert!(sanitized != input || sanitized.is_empty());
}
}
}
}
}