Skip to main content

avalonia_mcp_tools/
security_pattern_tool.rs

1//! Security pattern tool - Security best practices generation
2//!
3//! This tool provides security patterns and best practices for AvaloniaUI applications.
4
5use avalonia_mcp_core::error::AvaloniaMcpError;
6use avalonia_mcp_core::markdown::MarkdownOutputBuilder;
7use rmcp::model::{CallToolResult, Content};
8use rmcp::tool;
9use serde::{Deserialize, Serialize};
10use schemars::JsonSchema;
11
12/// Security pattern tool parameters
13#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
14pub struct SecurityPatternParams {
15    /// Security concern area (e.g., "authentication", "data-protection", "xss-prevention")
16    pub area: Option<String>,
17    /// Application type (e.g., "enterprise", "consumer", "kiosk")
18    pub app_type: Option<String>,
19    /// Include code examples
20    pub include_examples: Option<bool>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
24pub struct DataSecurityParams {
25    pub security_area: Option<String>,
26    pub include_encryption: Option<bool>,
27    pub include_audit_logging: Option<bool>,
28}
29
30/// Security pattern tool for generating security best practices
31#[derive(Debug, Clone, Default)]
32pub struct SecurityPatternTool;
33
34impl SecurityPatternTool {
35    /// Create a new SecurityPatternTool instance
36    pub fn new() -> Self {
37        Self
38    }
39
40    /// Generate security patterns and best practices
41    #[tool(description = "Generate security patterns and best practices for AvaloniaUI applications. Covers authentication, data protection, XSS prevention, and secure coding practices.")]
42    pub async fn generate_security_pattern(
43        &self,
44        params: SecurityPatternParams,
45    ) -> Result<CallToolResult, AvaloniaMcpError> {
46        let area = params.area.as_deref().unwrap_or("general");
47        let include_examples = params.include_examples.unwrap_or(true);
48
49        tracing::info!(area, "Generating security patterns");
50
51        let output = match area {
52            "authentication" => self.generate_authentication_patterns(include_examples),
53            "data-protection" => self.generate_data_protection_patterns(include_examples),
54            "xss-prevention" => self.generate_xss_prevention_patterns(include_examples),
55            "secure-coding" => self.generate_secure_coding_patterns(include_examples),
56            _ => self.generate_general_security_patterns(include_examples),
57        };
58
59        Ok(CallToolResult::success(vec![Content::text(output)]))
60    }
61
62    #[tool(description = "Creates defensive data security patterns with proper encryption, sanitization, and audit logging for AvaloniaUI applications")]
63    pub async fn generate_data_security_pattern(
64        &self,
65        params: DataSecurityParams,
66    ) -> Result<CallToolResult, AvaloniaMcpError> {
67        let security_area = params.security_area.as_deref().unwrap_or("encryption").to_lowercase();
68        let include_encryption = params.include_encryption.unwrap_or(true);
69        let include_audit = params.include_audit_logging.unwrap_or(true);
70
71        let mut builder = MarkdownOutputBuilder::new()
72            .heading(1, "Data Security Patterns")
73            .heading(2, "Configuration")
74            .task_list(vec![
75                (true, format!("Area: {}", security_area)),
76                (true, format!("Encryption: {}", include_encryption)),
77                (true, format!("Audit Logging: {}", include_audit)),
78            ])
79            .heading(2, "Data Sanitization")
80            .code_block(
81                "csharp",
82                r#"public static class InputSanitizer
83{
84    public static string Sanitize(string input)
85    {
86        if (string.IsNullOrEmpty(input)) return input;
87        return WebUtility.HtmlEncode(input)
88            .Replace("'", "''")  // SQL injection prevention
89            .Trim();
90    }
91}"#,
92            );
93
94        if include_encryption {
95            builder = builder
96                .heading(2, "Encryption Implementation")
97                .code_block(
98                    "csharp",
99                    r#"// AES Encryption for sensitive data
100public class DataEncryptionService
101{
102    private readonly Aes _aes = Aes.Create();
103
104    public string Encrypt(string plainText)
105    {
106        var encryptor = _aes.CreateEncryptor();
107        var plainBytes = Encoding.UTF8.GetBytes(plainText);
108        var cipherBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
109        return Convert.ToBase64String(cipherBytes);
110    }
111
112    public string Decrypt(string cipherText)
113    {
114        var decryptor = _aes.CreateDecryptor();
115        var cipherBytes = Convert.FromBase64String(cipherText);
116        var plainBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
117        return Encoding.UTF8.GetString(plainBytes);
118    }
119}"#,
120                );
121        }
122
123        if include_audit {
124            builder = builder
125                .heading(2, "Audit Logging")
126                .code_block(
127                    "csharp",
128                    r#"public class AuditLogger
129{
130    private readonly ILogger<AuditLogger> _logger;
131
132    public void LogAccess(string resource, string action, string userId)
133    {
134        _logger.LogInformation(
135            "Audit: User {UserId} performed {Action} on {Resource} at {Timestamp}",
136            userId, action, resource, DateTime.UtcNow);
137    }
138
139    public void LogSecurityEvent(string eventType, string details, string severity = "info")
140    {
141        _logger.LogWarning(
142            "Security Event: {Type} - {Details} [{Severity}]",
143            eventType, details, severity);
144    }
145}"#,
146                );
147        }
148
149        builder = builder
150            .heading(2, "Security Best Practices")
151            .list(vec![
152                "Encrypt sensitive data at rest",
153                "Use parameterized queries",
154                "Implement input validation",
155                "Enable audit logging",
156                "Follow principle of least privilege",
157                "Regular security audits",
158            ]);
159
160        Ok(CallToolResult::success(vec![Content::text(builder.build())]))
161    }
162
163    /// Generate authentication patterns
164    fn generate_authentication_patterns(&self, include_examples: bool) -> String {
165        let mut builder = MarkdownOutputBuilder::new()
166            .heading(1, "Authentication Security Patterns")
167            .paragraph("Best practices for implementing secure authentication in AvaloniaUI applications.")
168            .heading(2, "Key Principles")
169            .list(vec![
170                "Never store credentials in plain text",
171                "Use secure credential storage (Windows Credential Manager, Keychain, etc.)",
172                "Implement multi-factor authentication for sensitive operations",
173                "Use token-based authentication with short expiration times",
174                "Implement account lockout after failed attempts",
175            ])
176            .heading(2, "Implementation Patterns");
177
178        if include_examples {
179            builder = builder
180                .heading(3, "Secure Credential Storage")
181                .code_block("csharp", r#"// Use platform-specific secure storage
182// Windows: CredentialManager
183// macOS: Keychain
184// Linux: libsecret
185
186public interface ISecureStorage
187{
188    Task StoreAsync(string key, string value);
189    Task<string?> RetrieveAsync(string key);
190    Task DeleteAsync(string key);
191}
192
193// Implementation using protected data
194public class SecureStorage : ISecureStorage
195{
196    public async Task StoreAsync(string key, string value)
197    {
198        var protectedData = ProtectedData.Protect(
199            Encoding.UTF8.GetBytes(value),
200            null,
201            DataProtectionScope.CurrentUser);
202        
203        await SaveToFileAsync(key, protectedData);
204    }
205}"#)
206                .heading(3, "Token-Based Authentication")
207                .code_block("csharp", r#"// Implement token refresh mechanism
208public class AuthenticationService
209{
210    private readonly ITokenService _tokenService;
211    private DateTime _tokenExpiry;
212    
213    public async Task<bool> AuthenticateAsync(string username, string password)
214    {
215        var token = await _tokenService.RequestTokenAsync(username, password);
216        _tokenExpiry = token.ExpiresAt.AddMinutes(-5); // Buffer time
217        
218        return true;
219    }
220    
221    public async Task<string> GetValidTokenAsync()
222    {
223        if (DateTime.UtcNow >= _tokenExpiry)
224        {
225            await RefreshTokenAsync();
226        }
227        
228        return _currentToken;
229    }
230}"#);
231        }
232
233        builder
234            .heading(2, "Security Checklist")
235            .task_list(vec![
236                (true, "Credentials encrypted at rest"),
237                (true, "Tokens have short expiration"),
238                (true, "HTTPS for all network communication"),
239                (true, "Account lockout implemented"),
240                (false, "Multi-factor authentication enabled"),
241            ])
242            .build()
243    }
244
245    /// Generate data protection patterns
246    fn generate_data_protection_patterns(&self, include_examples: bool) -> String {
247        let mut builder = MarkdownOutputBuilder::new()
248            .heading(1, "Data Protection Patterns")
249            .paragraph("Protect sensitive data in AvaloniaUI applications using encryption and secure storage.")
250            .heading(2, "Key Principles")
251            .list(vec![
252                "Encrypt sensitive data at rest",
253                "Use platform-specific secure storage APIs",
254                "Implement data classification (public, internal, confidential)",
255                "Clear sensitive data from memory after use",
256                "Use secure random number generation for keys",
257            ])
258            .heading(2, "Implementation Patterns");
259
260        if include_examples {
261            builder = builder
262                .heading(3, "Data Encryption")
263                .code_block("csharp", r#"// Use AES encryption for sensitive data
264public class DataProtectionService
265{
266    private readonly Aes _aes;
267    
268    public DataProtectionService()
269    {
270        _aes = Aes.Create();
271        _aes.KeySize = 256;
272        _aes.Mode = CipherMode.GCM; // Authenticated encryption
273    }
274    
275    public byte[] Encrypt(byte[] plaintext, byte[] key, byte[] iv)
276    {
277        using var transform = _aes.CreateEncryptor(key, iv);
278        return transform.TransformFinalBlock(plaintext, 0, plaintext.Length);
279    }
280    
281    public byte[] Decrypt(byte[] ciphertext, byte[] key, byte[] iv)
282    {
283        using var transform = _aes.CreateDecryptor(key, iv);
284        return transform.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
285    }
286}"#)
287                .heading(3, "Secure Memory Handling")
288                .code_block("csharp", r#"// Clear sensitive data from memory
289public class SecureString : IDisposable
290{
291    private byte[] _data;
292    
293    public void Dispose()
294    {
295        if (_data != null)
296        {
297            // Overwrite with zeros
298            Array.Clear(_data, 0, _data.Length);
299            _data = null;
300        }
301    }
302}"#);
303        }
304
305        builder
306            .heading(2, "Security Checklist")
307            .task_list(vec![
308                (true, "Sensitive data encrypted"),
309                (true, "Keys stored securely"),
310                (true, "Memory cleared after use"),
311                (false, "Data classification implemented"),
312                (false, "Audit logging enabled"),
313            ])
314            .build()
315    }
316
317    /// Generate XSS prevention patterns
318    fn generate_xss_prevention_patterns(&self, include_examples: bool) -> String {
319        let mut builder = MarkdownOutputBuilder::new()
320            .heading(1, "XSS Prevention Patterns")
321            .paragraph("Prevent Cross-Site Scripting attacks in AvaloniaUI applications.")
322            .heading(2, "Key Principles")
323            .list(vec![
324                "Never trust user input",
325                "Encode all output displayed to users",
326                "Use Content Security Policy (CSP) headers",
327                "Validate and sanitize HTML input",
328                "Use parameterized queries for database access",
329            ])
330            .heading(2, "Implementation Patterns");
331
332        if include_examples {
333            builder = builder
334                .heading(3, "Input Validation")
335                .code_block("csharp", r#"// Validate and sanitize user input
336public class InputSanitizer
337{
338    private static readonly Regex ScriptPattern = 
339        new Regex("<script.*?>.*?</script>", RegexOptions.IgnoreCase | RegexOptions.Singleline);
340    
341    public static string Sanitize(string input)
342    {
343        if (string.IsNullOrEmpty(input))
344            return input;
345        
346        // Remove script tags
347        var sanitized = ScriptPattern.Replace(input, string.Empty);
348        
349        // HTML encode remaining content
350        return System.Net.WebUtility.HtmlEncode(sanitized);
351    }
352}"#)
353                .heading(3, "Safe Text Display")
354                .code_block("csharp", r#"// Use TextBlock instead of HtmlTextBlock for user content
355<StackPanel>
356    <!-- Safe: TextBlock automatically escapes content -->
357    <TextBlock Text="{Binding UserInput}" />
358    
359    <!-- Dangerous: Avoid rendering raw HTML from users -->
360    <!-- <local:HtmlTextBlock Html="{Binding UserHtml}" /> -->
361</StackPanel>"#);
362        }
363
364        builder
365            .heading(2, "Security Checklist")
366            .task_list(vec![
367                (true, "All user input validated"),
368                (true, "Output encoded before display"),
369                (true, "No raw HTML from users"),
370                (false, "CSP headers configured"),
371                (false, "Security headers implemented"),
372            ])
373            .build()
374    }
375
376    /// Generate secure coding patterns
377    fn generate_secure_coding_patterns(&self, include_examples: bool) -> String {
378        let mut builder = MarkdownOutputBuilder::new()
379            .heading(1, "Secure Coding Patterns")
380            .paragraph("General secure coding practices for AvaloniaUI applications.")
381            .heading(2, "Key Principles")
382            .list(vec![
383                "Follow principle of least privilege",
384                "Implement defense in depth",
385                "Use secure defaults",
386                "Log security events",
387                "Keep dependencies updated",
388                "Perform regular security audits",
389            ])
390            .heading(2, "Implementation Patterns");
391
392        if include_examples {
393            builder = builder
394                .heading(3, "Exception Handling")
395                .code_block("csharp", r#"// Don't expose internal errors to users
396public class SecureExceptionHandler
397{
398    public static async Task HandleAsync(Exception ex)
399    {
400        // Log full details internally
401        Logger.LogError(ex, "An error occurred");
402        
403        // Show generic message to user
404        await ShowUserMessageAsync(
405            "An unexpected error occurred. Please try again.");
406        
407        // Never expose:
408        // - Stack traces
409        // - Connection strings
410        // - Internal paths
411        // - Version information
412    }
413}"#)
414                .heading(3, "Secure File Access")
415                .code_block("csharp", r#"// Validate file paths to prevent directory traversal
416public class SecureFileAccess
417{
418    private readonly string _baseDirectory;
419    
420    public async Task<string> ReadFileAsync(string relativePath)
421    {
422        // Validate and normalize path
423        var fullPath = Path.GetFullPath(
424            Path.Combine(_baseDirectory, relativePath));
425        
426        // Ensure path is within base directory
427        if (!fullPath.StartsWith(_baseDirectory))
428        {
429            throw new SecurityException(
430                "Access denied: Path traversal detected");
431        }
432        
433        return await File.ReadAllTextAsync(fullPath);
434    }
435}"#);
436        }
437
438        builder
439            .heading(2, "Security Checklist")
440            .task_list(vec![
441                (true, "Exceptions handled securely"),
442                (true, "File paths validated"),
443                (true, "Dependencies updated"),
444                (true, "Security events logged"),
445                (false, "Penetration testing performed"),
446            ])
447            .build()
448    }
449
450    /// Generate general security patterns
451    fn generate_general_security_patterns(&self, include_examples: bool) -> String {
452        let mut builder = MarkdownOutputBuilder::new()
453            .heading(1, "General Security Patterns")
454            .paragraph("Comprehensive security guidance for AvaloniaUI applications.")
455            .heading(2, "Security Domains")
456            .list(vec![
457                "Authentication & Authorization",
458                "Data Protection & Encryption",
459                "Input Validation & Output Encoding",
460                "Secure Communication",
461                "Error Handling & Logging",
462                "Dependency Management",
463            ])
464            .heading(2, "Quick Start Security Checklist")
465            .task_list(vec![
466                (false, "Implement secure authentication"),
467                (false, "Encrypt sensitive data"),
468                (false, "Validate all user input"),
469                (false, "Use HTTPS for network calls"),
470                (false, "Implement secure error handling"),
471                (false, "Enable security logging"),
472                (false, "Update all dependencies"),
473                (false, "Perform security code review"),
474            ])
475            .heading(2, "Recommended Security Tools")
476            .list(vec![
477                "OWASP ZAP - Security testing",
478                "SonarQube - Code quality and security",
479                "Dependency-Check - Vulnerable dependency scanning",
480                "dotnet-scan - .NET vulnerability scanning",
481            ]);
482
483        if include_examples {
484            builder = builder
485                .heading(2, "Security Configuration Template")
486                .code_block("csharp", r#"// App security configuration
487public class SecurityConfiguration
488{
489    // Authentication
490    public int TokenExpirationMinutes { get; set; } = 30;
491    public int MaxLoginAttempts { get; set; } = 5;
492    public TimeSpan LockoutDuration { get; set; } = TimeSpan.FromMinutes(15);
493    
494    // Encryption
495    public int EncryptionKeySize { get; set; } = 256;
496    public string EncryptionAlgorithm { get; set; } = "AES-GCM";
497    
498    // Network
499    public bool RequireHttps { get; set; } = true;
500    public string[] AllowedOrigins { get; set; } = Array.Empty<string>();
501    
502    // Logging
503    public bool LogSecurityEvents { get; set; } = true;
504    public LogLevel SecurityLogLevel { get; set; } = LogLevel.Warning;
505}"#);
506        }
507
508        builder
509            .heading(2, "Next Steps")
510            .numbered_list(vec![
511                "Review authentication patterns",
512                "Implement data protection",
513                "Add input validation",
514                "Configure secure logging",
515                "Schedule security audit",
516            ])
517            .build()
518    }
519}
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524
525    #[tokio::test]
526    async fn test_generate_security_pattern_general() {
527        let tool = SecurityPatternTool::new();
528        let params = SecurityPatternParams {
529            area: None,
530            app_type: None,
531            include_examples: Some(true),
532        };
533
534        let result = tool.generate_security_pattern(params).await.unwrap();
535        assert!(result.is_error.is_none() || result.is_error == Some(false));
536        assert!(result.content.len() > 0);
537    }
538
539    #[tokio::test]
540    async fn test_generate_security_pattern_authentication() {
541        let tool = SecurityPatternTool::new();
542        let params = SecurityPatternParams {
543            area: Some("authentication".to_string()),
544            app_type: None,
545            include_examples: Some(true),
546        };
547
548        let result = tool.generate_security_pattern(params).await.unwrap();
549        assert!(result.is_error.is_none() || result.is_error == Some(false));
550    }
551
552    #[tokio::test]
553    async fn test_generate_security_pattern_no_examples() {
554        let tool = SecurityPatternTool::new();
555        let params = SecurityPatternParams {
556            area: Some("general".to_string()),
557            app_type: None,
558            include_examples: Some(false),
559        };
560
561        let result = tool.generate_security_pattern(params).await.unwrap();
562        assert!(result.is_error.is_none() || result.is_error == Some(false));
563    }
564
565    #[tokio::test]
566    async fn test_generate_data_security_pattern_success() {
567        let tool = SecurityPatternTool::new();
568        let params = DataSecurityParams {
569            security_area: Some("encryption".to_string()),
570            include_encryption: Some(true),
571            include_audit_logging: Some(true),
572        };
573        let result = tool.generate_data_security_pattern(params).await.unwrap();
574        assert!(result.is_error.is_none() || result.is_error == Some(false));
575        assert!(result.content.len() > 0);
576    }
577
578    #[tokio::test]
579    async fn test_generate_data_security_pattern_no_encryption() {
580        let tool = SecurityPatternTool::new();
581        let params = DataSecurityParams {
582            security_area: Some("sanitization".to_string()),
583            include_encryption: Some(false),
584            include_audit_logging: Some(true),
585        };
586        let result = tool.generate_data_security_pattern(params).await.unwrap();
587        assert!(result.is_error.is_none() || result.is_error == Some(false));
588    }
589
590    #[tokio::test]
591    async fn test_generate_data_security_pattern_no_audit() {
592        let tool = SecurityPatternTool::new();
593        let params = DataSecurityParams {
594            security_area: Some("encryption".to_string()),
595            include_encryption: Some(true),
596            include_audit_logging: Some(false),
597        };
598        let result = tool.generate_data_security_pattern(params).await.unwrap();
599        assert!(result.is_error.is_none() || result.is_error == Some(false));
600    }
601
602    #[tokio::test]
603    async fn test_generate_data_security_pattern_defaults() {
604        let tool = SecurityPatternTool::new();
605        let params = DataSecurityParams {
606            security_area: None,
607            include_encryption: None,
608            include_audit_logging: None,
609        };
610        let result = tool.generate_data_security_pattern(params).await.unwrap();
611        assert!(result.is_error.is_none() || result.is_error == Some(false));
612    }
613
614    #[tokio::test]
615    async fn test_generate_data_security_pattern_minimal() {
616        let tool = SecurityPatternTool::new();
617        let params = DataSecurityParams {
618            security_area: Some("general".to_string()),
619            include_encryption: Some(false),
620            include_audit_logging: Some(false),
621        };
622        let result = tool.generate_data_security_pattern(params).await.unwrap();
623        assert!(result.is_error.is_none() || result.is_error == Some(false));
624    }
625}