1use 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#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
14pub struct SecurityPatternParams {
15 pub area: Option<String>,
17 pub app_type: Option<String>,
19 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#[derive(Debug, Clone, Default)]
32pub struct SecurityPatternTool;
33
34impl SecurityPatternTool {
35 pub fn new() -> Self {
37 Self
38 }
39
40 #[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 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 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 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 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 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}