🦀 Security Best Practices in TuskLang for Rust
Security Best Practices in TuskLang for Rust
Security best practices in TuskLang for Rust leverage Rust's memory safety, type safety, and ownership system to provide robust security while maintaining the power and flexibility of TuskLang's configuration and scripting capabilities.
Input Validation and Sanitization
// Secure input validation with TuskLang
pub struct SecureInputValidator {
validation_rules: std::collections::HashMap<String, ValidationRule>,
sanitizers: std::collections::HashMap<String, Box<dyn Sanitizer + Send + Sync>>,
}#[derive(Clone)]
pub struct ValidationRule {
pub pattern: String,
pub min_length: Option<usize>,
pub max_length: Option<usize>,
pub allowed_chars: Option<String>,
pub forbidden_patterns: Vec<String>,
}
pub trait Sanitizer {
fn sanitize(&self, input: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>>;
}
impl SecureInputValidator {
pub fn new() -> Self {
let mut validator = Self {
validation_rules: std::collections::HashMap::new(),
sanitizers: std::collections::HashMap::new(),
};
// Load validation rules from TuskLang config
validator.load_validation_rules();
validator.register_default_sanitizers();
validator
}
fn load_validation_rules(&mut self) {
let rules = @config.get("security.validation_rules", serde_json::json!({}));
for (rule_name, rule_data) in rules.as_object().unwrap() {
let rule = ValidationRule {
pattern: rule_data["pattern"].as_str().unwrap().to_string(),
min_length: rule_data["min_length"].as_u64().map(|l| l as usize),
max_length: rule_data["max_length"].as_u64().map(|l| l as usize),
allowed_chars: rule_data["allowed_chars"].as_str().map(|s| s.to_string()),
forbidden_patterns: rule_data["forbidden_patterns"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect(),
};
self.validation_rules.insert(rule_name.clone(), rule);
}
}
fn register_default_sanitizers(&mut self) {
// HTML sanitizer
self.sanitizers.insert("html".to_string(), Box::new(HtmlSanitizer));
// SQL injection sanitizer
self.sanitizers.insert("sql".to_string(), Box::new(SqlSanitizer));
// XSS sanitizer
self.sanitizers.insert("xss".to_string(), Box::new(XssSanitizer));
// Path traversal sanitizer
self.sanitizers.insert("path".to_string(), Box::new(PathSanitizer));
}
pub fn validate_and_sanitize(&self, input: &str, rule_name: &str, sanitizer_name: Option<&str>)
-> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Validate input
self.validate_input(input, rule_name)?;
// Sanitize input if sanitizer specified
if let Some(sanitizer_name) = sanitizer_name {
if let Some(sanitizer) = self.sanitizers.get(sanitizer_name) {
return sanitizer.sanitize(input);
}
}
Ok(input.to_string())
}
pub fn validate_input(&self, input: &str, rule_name: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let rule = self.validation_rules.get(rule_name)
.ok_or_else(|| format!("Validation rule '{}' not found", rule_name))?;
// Check length constraints
if let Some(min_length) = rule.min_length {
if input.len() < min_length {
return Err(format!("Input too short, minimum length: {}", min_length).into());
}
}
if let Some(max_length) = rule.max_length {
if input.len() > max_length {
return Err(format!("Input too long, maximum length: {}", max_length).into());
}
}
// Check pattern
let regex = regex::Regex::new(&rule.pattern)?;
if !regex.is_match(input) {
return Err(format!("Input does not match pattern: {}", rule.pattern).into());
}
// Check forbidden patterns
for forbidden_pattern in &rule.forbidden_patterns {
let forbidden_regex = regex::Regex::new(forbidden_pattern)?;
if forbidden_regex.is_match(input) {
return Err(format!("Input contains forbidden pattern: {}", forbidden_pattern).into());
}
}
// Check allowed characters
if let Some(ref allowed_chars) = rule.allowed_chars {
for ch in input.chars() {
if !allowed_chars.contains(ch) {
return Err(format!("Input contains forbidden character: {}", ch).into());
}
}
}
Ok(())
}
}
// Sanitizer implementations
pub struct HtmlSanitizer;
impl Sanitizer for HtmlSanitizer {
fn sanitize(&self, input: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Remove potentially dangerous HTML tags and attributes
let sanitized = input
.replace("<script>", "")
.replace("</script>", "")
.replace("<iframe>", "")
.replace("</iframe>", "")
.replace("javascript:", "")
.replace("onclick=", "")
.replace("onload=", "")
.replace("onerror=", "");
Ok(sanitized)
}
}
pub struct SqlSanitizer;
impl Sanitizer for SqlSanitizer {
fn sanitize(&self, input: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Remove SQL injection patterns
let sanitized = input
.replace("'", "''")
.replace(";", "")
.replace("--", "")
.replace("/*", "")
.replace("*/", "")
.replace("DROP", "")
.replace("DELETE", "")
.replace("INSERT", "")
.replace("UPDATE", "");
Ok(sanitized)
}
}
pub struct XssSanitizer;
impl Sanitizer for XssSanitizer {
fn sanitize(&self, input: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Remove XSS patterns
let sanitized = input
.replace("<script", "")
.replace("javascript:", "")
.replace("vbscript:", "")
.replace("onclick", "")
.replace("onload", "")
.replace("onerror", "")
.replace("onmouseover", "");
Ok(sanitized)
}
}
pub struct PathSanitizer;
impl Sanitizer for PathSanitizer {
fn sanitize(&self, input: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Prevent path traversal attacks
let sanitized = input
.replace("..", "")
.replace("~", "")
.replace("//", "/")
.trim_start_matches('/');
Ok(sanitized)
}
}
// Usage
let validator = SecureInputValidator::new();
// Validate and sanitize user input
let user_input = "<script>alert('xss')</script>John Doe";
let sanitized = validator.validate_and_sanitize(user_input, "username", Some("html"))?;
Secure Configuration Management
// Secure configuration management with encryption
pub struct SecureConfigManager {
encryption_key: Vec<u8>,
encrypted_configs: std::collections::HashMap<String, Vec<u8>>,
}impl SecureConfigManager {
pub fn new(encryption_key: &[u8]) -> Self {
Self {
encryption_key: encryption_key.to_vec(),
encrypted_configs: std::collections::HashMap::new(),
}
}
pub fn load_secure_config(&mut self, config_path: &str) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
// Load encrypted configuration
let encrypted_data = @file.read_bytes(config_path)?;
// Decrypt configuration
let decrypted_data = self.decrypt_config(&encrypted_data)?;
// Parse configuration
let config: serde_json::Value = serde_json::from_slice(&decrypted_data)?;
// Validate configuration
self.validate_config(&config)?;
Ok(config)
}
pub fn save_secure_config(&mut self, config: &serde_json::Value, config_path: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Validate configuration before saving
self.validate_config(config)?;
// Serialize configuration
let config_data = serde_json::to_vec(config)?;
// Encrypt configuration
let encrypted_data = self.encrypt_config(&config_data)?;
// Save encrypted configuration
@file.write_bytes(config_path, &encrypted_data)?;
Ok(())
}
fn encrypt_config(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
let key = Key::from_slice(&self.encryption_key);
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(b"unique nonce"); // In production, use a random nonce
let encrypted = cipher.encrypt(nonce, data)?;
Ok(encrypted)
}
fn decrypt_config(&self, encrypted_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
let key = Key::from_slice(&self.encryption_key);
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(b"unique nonce");
let decrypted = cipher.decrypt(nonce, encrypted_data)?;
Ok(decrypted)
}
fn validate_config(&self, config: &serde_json::Value) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Check for sensitive data exposure
let sensitive_keys = ["password", "secret", "key", "token", "credential"];
for key in &sensitive_keys {
if let Some(value) = config.get(key) {
if value.as_str().unwrap_or("").contains("plaintext") {
return Err(format!("Sensitive data '{}' should not be in plaintext", key).into());
}
}
}
// Validate configuration structure
if let Some(database) = config.get("database") {
if let Some(url) = database.get("url") {
if !url.as_str().unwrap_or("").starts_with("postgresql://") {
return Err("Invalid database URL format".into());
}
}
}
Ok(())
}
pub fn get_secure_value(&self, config: &serde_json::Value, key: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let value = config.get(key)
.ok_or_else(|| format!("Configuration key '{}' not found", key))?;
if let Some(encrypted_value) = value.as_str() {
if encrypted_value.starts_with("encrypted:") {
// Decrypt the value
let encrypted_data = base64::decode(&encrypted_value[10..])?;
let decrypted_data = self.decrypt_config(&encrypted_data)?;
return Ok(String::from_utf8(decrypted_data)?);
}
}
Ok(value.as_str().unwrap_or("").to_string())
}
}
// Usage
let encryption_key = @env.get_secure("CONFIG_ENCRYPTION_KEY")?;
let mut config_manager = SecureConfigManager::new(encryption_key.as_bytes());
// Load secure configuration
let config = config_manager.load_secure_config("secure_config.enc")?;
// Get secure values
let db_password = config_manager.get_secure_value(&config, "database.password")?;
let api_key = config_manager.get_secure_value(&config, "api.secret_key")?;
Authentication and Authorization
// Secure authentication system
pub struct SecureAuthManager {
password_hasher: Argon2,
jwt_secret: Vec<u8>,
session_store: std::sync::Arc<tokio::sync::RwLock<std::collections::HashMap<String, Session>>>,
}#[derive(Clone)]
pub struct Session {
pub user_id: u32,
pub permissions: Vec<String>,
pub expires_at: std::time::Instant,
pub ip_address: String,
pub user_agent: String,
}
impl SecureAuthManager {
pub fn new(jwt_secret: &[u8]) -> Self {
Self {
password_hasher: Argon2::default(),
jwt_secret: jwt_secret.to_vec(),
session_store: std::sync::Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
}
}
pub fn hash_password(&self, password: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let salt = SaltString::generate(&mut OsRng);
let password_hash = self.password_hasher.hash_password(password.as_bytes(), &salt)?;
Ok(password_hash.to_string())
}
pub fn verify_password(&self, password: &str, hash: &str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
let parsed_hash = PasswordHash::new(hash)?;
let result = self.password_hasher.verify_password(password.as_bytes(), &parsed_hash);
Ok(result.is_ok())
}
pub fn create_session(&self, user_id: u32, permissions: Vec<String>, ip_address: &str, user_agent: &str)
-> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let session_id = @uuid.generate();
let session = Session {
user_id,
permissions,
expires_at: std::time::Instant::now() + std::time::Duration::from_secs(3600), // 1 hour
ip_address: ip_address.to_string(),
user_agent: user_agent.to_string(),
};
// Store session
self.session_store.write().await.insert(session_id.clone(), session);
// Create JWT token
let claims = Claims {
sub: user_id.to_string(),
exp: (std::time::SystemTime::now() + std::time::Duration::from_secs(3600))
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as usize,
iat: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as usize,
};
let token = encode(&Header::default(), &claims, &self.jwt_secret)?;
Ok(token)
}
pub fn validate_session(&self, token: &str, ip_address: &str, user_agent: &str)
-> Result<Option<Session>, Box<dyn std::error::Error + Send + Sync>> {
// Verify JWT token
let claims: Claims = decode(token, &self.jwt_secret, &Validation::default())?.claims;
// Check if session exists and is valid
if let Some(session) = self.session_store.read().await.get(&claims.sub) {
if session.expires_at > std::time::Instant::now() &&
session.ip_address == ip_address &&
session.user_agent == user_agent {
return Ok(Some(session.clone()));
}
}
Ok(None)
}
pub fn check_permission(&self, session: &Session, required_permission: &str) -> bool {
session.permissions.contains(&required_permission.to_string())
}
pub fn revoke_session(&self, user_id: u32) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut sessions = self.session_store.write().await;
sessions.retain(|_, session| session.user_id != user_id);
Ok(())
}
}
// JWT Claims structure
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
exp: usize,
iat: usize,
}
// Usage
let jwt_secret = @env.get_secure("JWT_SECRET")?;
let auth_manager = SecureAuthManager::new(jwt_secret.as_bytes());
// Hash password
let password_hash = auth_manager.hash_password("secure_password")?;
// Verify password
let is_valid = auth_manager.verify_password("secure_password", &password_hash)?;
// Create session
let session_token = auth_manager.create_session(
123,
vec!["read".to_string(), "write".to_string()],
"192.168.1.1",
"Mozilla/5.0..."
)?;
// Validate session
if let Some(session) = auth_manager.validate_session(&session_token, "192.168.1.1", "Mozilla/5.0...")? {
if auth_manager.check_permission(&session, "write") {
// Allow write operation
}
}
Secure Database Operations
// Secure database operations with parameterized queries
pub struct SecureDatabase {
connection_pool: deadpool_postgres::Pool,
query_validator: QueryValidator,
}pub struct QueryValidator {
allowed_tables: std::collections::HashSet<String>,
allowed_operations: std::collections::HashSet<String>,
max_query_length: usize,
}
impl SecureDatabase {
pub fn new(pool: deadpool_postgres::Pool) -> Self {
let mut validator = QueryValidator {
allowed_tables: std::collections::HashSet::new(),
allowed_operations: std::collections::HashSet::new(),
max_query_length: 10000,
};
// Load allowed tables and operations from TuskLang config
validator.load_security_rules();
Self {
connection_pool: pool,
query_validator: validator,
}
}
pub async fn secure_query(&self, query: &str, params: &[serde_json::Value])
-> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
// Validate query
self.query_validator.validate_query(query)?;
// Sanitize parameters
let sanitized_params = self.sanitize_parameters(params)?;
// Execute query with parameterized statement
let conn = self.connection_pool.get().await?;
let stmt = conn.prepare(query).await?;
let result = conn.query(&stmt, &sanitized_params).await?;
Ok(serde_json::to_value(result)?)
}
pub async fn secure_execute(&self, query: &str, params: &[serde_json::Value])
-> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
// Validate query
self.query_validator.validate_query(query)?;
// Check for dangerous operations
self.query_validator.validate_operation(query)?;
// Sanitize parameters
let sanitized_params = self.sanitize_parameters(params)?;
// Execute query
let conn = self.connection_pool.get().await?;
let stmt = conn.prepare(query).await?;
let result = conn.execute(&stmt, &sanitized_params).await?;
Ok(result)
}
fn sanitize_parameters(&self, params: &[serde_json::Value]) -> Result<Vec<serde_json::Value>, Box<dyn std::error::Error + Send + Sync>> {
let mut sanitized = Vec::new();
for param in params {
let sanitized_param = match param {
serde_json::Value::String(s) => {
// Sanitize string parameters
let sanitized = s
.replace("'", "''")
.replace(";", "")
.replace("--", "")
.replace("/*", "")
.replace("*/", "");
serde_json::Value::String(sanitized)
}
_ => param.clone(),
};
sanitized.push(sanitized_param);
}
Ok(sanitized)
}
}
impl QueryValidator {
fn load_security_rules(&mut self) {
let rules = @config.get("security.database_rules", serde_json::json!({}));
// Load allowed tables
if let Some(tables) = rules.get("allowed_tables") {
for table in tables.as_array().unwrap() {
self.allowed_tables.insert(table.as_str().unwrap().to_string());
}
}
// Load allowed operations
if let Some(operations) = rules.get("allowed_operations") {
for operation in operations.as_array().unwrap() {
self.allowed_operations.insert(operation.as_str().unwrap().to_string());
}
}
// Load max query length
if let Some(max_length) = rules.get("max_query_length") {
self.max_query_length = max_length.as_u64().unwrap() as usize;
}
}
fn validate_query(&self, query: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Check query length
if query.len() > self.max_query_length {
return Err("Query too long".into());
}
// Check for SQL injection patterns
let dangerous_patterns = [
"DROP TABLE", "DELETE FROM", "TRUNCATE TABLE",
"ALTER TABLE", "CREATE TABLE", "DROP DATABASE",
"UNION SELECT", "EXEC", "EXECUTE",
];
let query_upper = query.to_uppercase();
for pattern in &dangerous_patterns {
if query_upper.contains(pattern) {
return Err(format!("Dangerous SQL pattern detected: {}", pattern).into());
}
}
// Validate table names
for table in &self.allowed_tables {
if query_upper.contains(&format!("FROM {}", table.to_uppercase())) ||
query_upper.contains(&format!("JOIN {}", table.to_uppercase())) {
return Ok(());
}
}
Err("No allowed tables found in query".into())
}
fn validate_operation(&self, query: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let query_upper = query.to_uppercase();
for operation in &self.allowed_operations {
if query_upper.starts_with(&operation.to_uppercase()) {
return Ok(());
}
}
Err("Operation not allowed".into())
}
}
// Usage
let db = SecureDatabase::new(connection_pool);
// Secure query with parameterized statement
let result = db.secure_query(
"SELECT * FROM users WHERE id = $1 AND status = $2",
&[serde_json::json!(123), serde_json::json!("active")]
).await?;
// Secure execute
let affected_rows = db.secure_execute(
"UPDATE users SET last_login = NOW() WHERE id = $1",
&[serde_json::json!(123)]
).await?;
Secure File Operations
// Secure file operations with path validation
pub struct SecureFileManager {
allowed_paths: std::collections::HashSet<std::path::PathBuf>,
max_file_size: usize,
allowed_extensions: std::collections::HashSet<String>,
}impl SecureFileManager {
pub fn new() -> Self {
let mut manager = Self {
allowed_paths: std::collections::HashSet::new(),
max_file_size: 10 1024 1024, // 10MB
allowed_extensions: std::collections::HashSet::new(),
};
// Load security rules from TuskLang config
manager.load_security_rules();
manager
}
fn load_security_rules(&mut self) {
let rules = @config.get("security.file_rules", serde_json::json!({}));
// Load allowed paths
if let Some(paths) = rules.get("allowed_paths") {
for path in paths.as_array().unwrap() {
self.allowed_paths.insert(std::path::PathBuf::from(path.as_str().unwrap()));
}
}
// Load max file size
if let Some(max_size) = rules.get("max_file_size") {
self.max_file_size = max_size.as_u64().unwrap() as usize;
}
// Load allowed extensions
if let Some(extensions) = rules.get("allowed_extensions") {
for ext in extensions.as_array().unwrap() {
self.allowed_extensions.insert(ext.as_str().unwrap().to_string());
}
}
}
pub fn secure_read(&self, file_path: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Validate file path
let path = self.validate_path(file_path)?;
// Check file size
let metadata = std::fs::metadata(&path)?;
if metadata.len() as usize > self.max_file_size {
return Err("File too large".into());
}
// Check file extension
if let Some(extension) = path.extension() {
let ext = extension.to_string_lossy().to_lowercase();
if !self.allowed_extensions.contains(&ext) {
return Err(format!("File extension '{}' not allowed", ext).into());
}
}
// Read file content
let content = @file.read(&path.to_string_lossy())?;
// Sanitize content
let sanitized_content = self.sanitize_content(&content)?;
Ok(sanitized_content)
}
pub fn secure_write(&self, file_path: &str, content: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Validate file path
let path = self.validate_path(file_path)?;
// Check content size
if content.len() > self.max_file_size {
return Err("Content too large".into());
}
// Sanitize content before writing
let sanitized_content = self.sanitize_content(content)?;
// Write file atomically
let temp_path = path.with_extension("tmp");
@file.write(&temp_path.to_string_lossy(), &sanitized_content)?;
std::fs::rename(temp_path, path)?;
Ok(())
}
fn validate_path(&self, file_path: &str) -> Result<std::path::PathBuf, Box<dyn std::error::Error + Send + Sync>> {
let path = std::path::PathBuf::from(file_path);
// Prevent path traversal
if path.components().any(|component| {
matches!(component, std::path::Component::ParentDir)
}) {
return Err("Path traversal not allowed".into());
}
// Check if path is in allowed directory
let canonical_path = path.canonicalize()?;
for allowed_path in &self.allowed_paths {
if canonical_path.starts_with(allowed_path) {
return Ok(canonical_path);
}
}
Err("Path not in allowed directory".into())
}
fn sanitize_content(&self, content: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Remove potentially dangerous content
let sanitized = content
.replace("<?php", "")
.replace("<?=", "")
.replace("<script", "")
.replace("javascript:", "")
.replace("vbscript:", "");
Ok(sanitized)
}
}
// Usage
let file_manager = SecureFileManager::new();
// Secure file read
let content = file_manager.secure_read("/var/www/uploads/document.txt")?;
// Secure file write
file_manager.secure_write("/var/www/uploads/report.txt", "Report content")?;
Secure Logging
// Secure logging with sensitive data filtering
pub struct SecureLogger {
sensitive_patterns: Vec<regex::Regex>,
log_encryption: Option<SecureLogEncryption>,
}pub struct SecureLogEncryption {
encryption_key: Vec<u8>,
}
impl SecureLogger {
pub fn new() -> Self {
let mut logger = Self {
sensitive_patterns: Vec::new(),
log_encryption: None,
};
// Load sensitive patterns from TuskLang config
logger.load_sensitive_patterns();
// Setup log encryption if enabled
if @config.get("security.log_encryption.enabled", false) {
let encryption_key = @env.get_secure("LOG_ENCRYPTION_KEY")?;
logger.log_encryption = Some(SecureLogEncryption {
encryption_key: encryption_key.as_bytes().to_vec(),
});
}
logger
}
fn load_sensitive_patterns(&mut self) {
let patterns = @config.get("security.sensitive_patterns", serde_json::json!([]));
for pattern in patterns.as_array().unwrap() {
let regex = regex::Regex::new(pattern.as_str().unwrap()).unwrap();
self.sensitive_patterns.push(regex);
}
}
pub fn secure_log(&self, level: &str, message: &str, data: Option<serde_json::Value>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Sanitize message
let sanitized_message = self.sanitize_message(message);
// Sanitize data
let sanitized_data = if let Some(data) = data {
Some(self.sanitize_data(data)?)
} else {
None
};
// Create log entry
let log_entry = serde_json::json!({
"timestamp": @date.now().to_rfc3339(),
"level": level,
"message": sanitized_message,
"data": sanitized_data,
});
// Encrypt log entry if encryption is enabled
let final_entry = if let Some(ref encryption) = self.log_encryption {
let encrypted = encryption.encrypt(&serde_json::to_string(&log_entry)?)?;
serde_json::json!({ "encrypted": true, "data": base64::encode(encrypted) })
} else {
log_entry
};
// Write to log file
@file.append("secure.log", &serde_json::to_string(&final_entry)?)?;
Ok(())
}
fn sanitize_message(&self, message: &str) -> String {
let mut sanitized = message.to_string();
for pattern in &self.sensitive_patterns {
sanitized = pattern.replace_all(&sanitized, "[REDACTED]").to_string();
}
sanitized
}
fn sanitize_data(&self, data: serde_json::Value) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
match data {
serde_json::Value::Object(mut map) => {
for (key, value) in map.iter_mut() {
*value = self.sanitize_data(value.clone())?;
}
Ok(serde_json::Value::Object(map))
}
serde_json::Value::Array(mut arr) => {
for item in arr.iter_mut() {
*item = self.sanitize_data(item.clone())?;
}
Ok(serde_json::Value::Array(arr))
}
serde_json::Value::String(s) => {
Ok(serde_json::Value::String(self.sanitize_message(&s)))
}
_ => Ok(data),
}
}
}
impl SecureLogEncryption {
fn encrypt(&self, data: &str) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
let key = Key::from_slice(&self.encryption_key);
let cipher = Aes256Gcm::new(key);
let nonce = Nonce::from_slice(b"log nonce 12"); // In production, use a random nonce
let encrypted = cipher.encrypt(nonce, data.as_bytes())?;
Ok(encrypted)
}
}
// Usage
let secure_logger = SecureLogger::new();
// Secure logging with sensitive data
secure_logger.secure_log("INFO", "User login successful", Some(serde_json::json!({
"user_id": 123,
"email": "user@example.com",
"password": "secret123", // This will be redacted
"ip_address": "192.168.1.1"
})))?;
This comprehensive guide covers Rust-specific security best practices, ensuring robust security while maintaining the power and flexibility of TuskLang's capabilities.