#![allow(clippy::unwrap_used)] #![allow(clippy::default_trait_access)] use fraiseql_core::db::{
WhereSqlGenerator,
where_clause::{WhereClause, WhereOperator},
};
use serde_json::json;
const OWASP_PAYLOADS: &[&str] = &[
"'; DROP TABLE users; --",
"'; DELETE FROM accounts WHERE '1'='1",
"'; TRUNCATE TABLE sensitive_data; --",
"'; UPDATE users SET admin=1; --",
"' OR '1'='1",
"' OR '1'='1' --",
"admin' OR '1'='1' /*",
"' OR 1=1 --",
"' OR 'a'='a",
"\" OR \"\"=\"\"",
"' UNION SELECT * FROM passwords --",
"' UNION SELECT username, password FROM admin --",
"1' UNION SELECT NULL, NULL, NULL --",
"' UNION ALL SELECT user, pass FROM users --",
"1; DROP TABLE users; --",
"1; DELETE FROM accounts; --",
"'; EXEC sp_executesql; --",
"admin'--",
"admin' #",
"admin'/*",
"' /**/OR/**/1=1 --",
"') OR ('1'='1",
"') OR 1=1 --",
"') AND ('1'='1",
"admin'%00",
"' OR 1=1%00--",
"admin\\'",
"\\\' OR \\'1\\'=\\'1",
"admin\\'; DROP TABLE users; --",
"' Or '1'='1",
"' oR '1'='1",
"' OR \"1\"=\"1",
"' /*!50000OR*/ '1'='1", "' and 1=1 --", "' AND (SELECT * FROM (SELECT(SLEEP(5)))a) --",
"' AND BENCHMARK(50000000, MD5('test')) --",
"' OR SLEEP(5) --",
];
fn assert_injection_safe(sql: &str, payload: &str, operator: &str) {
if payload.contains('\'') {
let escaped = payload.replace('\'', "''");
assert!(
sql.contains(&escaped),
"{operator}: Single quotes not properly escaped.\n Payload: {payload}\n SQL: {sql}"
);
}
let dangerous_keywords = [
"DROP TABLE",
"DELETE FROM",
"TRUNCATE",
"UPDATE",
"INSERT INTO",
"UNION SELECT",
"EXEC",
"EXECUTE",
"sp_executesql",
];
for keyword in dangerous_keywords {
if payload.contains(keyword) {
assert!(
sql.contains('\'') || sql.contains('"'),
"{operator}: Dangerous keyword '{keyword}' from payload must be in string literal"
);
}
}
if payload.contains(';') {
assert!(
!sql.contains("';") || sql.contains("''") || sql.contains('"'),
"{operator}: Unescaped semicolon could enable stacked queries"
);
}
}
#[test]
fn test_eq_operator_injection_safety() {
for payload in OWASP_PAYLOADS {
let clause = WhereClause::Field {
path: vec!["email".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("SQL generation should not fail");
assert_injection_safe(&sql, payload, "Eq");
}
}
#[test]
fn test_neq_operator_injection_safety() {
for payload in OWASP_PAYLOADS {
let clause = WhereClause::Field {
path: vec!["status".to_string()],
operator: WhereOperator::Neq,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("SQL generation should not fail");
assert_injection_safe(&sql, payload, "Neq");
}
}
#[test]
fn test_gt_operator_injection_safety() {
for payload in OWASP_PAYLOADS {
let clause = WhereClause::Field {
path: vec!["amount".to_string()],
operator: WhereOperator::Gt,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("SQL generation should not fail");
assert_injection_safe(&sql, payload, "Gt");
}
}
#[test]
fn test_contains_operator_injection_safety() {
for payload in OWASP_PAYLOADS {
let clause = WhereClause::Field {
path: vec!["description".to_string()],
operator: WhereOperator::Contains,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("SQL generation should not fail");
assert_injection_safe(&sql, payload, "Contains");
}
}
#[test]
fn test_startswith_operator_injection_safety() {
for payload in OWASP_PAYLOADS {
let clause = WhereClause::Field {
path: vec!["prefix".to_string()],
operator: WhereOperator::Startswith,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("SQL generation should not fail");
assert_injection_safe(&sql, payload, "Startswith");
}
}
#[test]
fn test_in_operator_injection_safety() {
for payload in OWASP_PAYLOADS {
let clause = WhereClause::Field {
path: vec!["id".to_string()],
operator: WhereOperator::In,
value: json!([payload]),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("SQL generation should not fail");
assert_injection_safe(&sql, payload, "In");
}
}
#[test]
fn test_parameter_pollution_injection_safety() {
let payloads = vec!["1' OR '1'='1", "admin' --", "' UNION SELECT NULL --"];
for payload in payloads {
let clause = WhereClause::Field {
path: vec!["user_id".to_string()],
operator: WhereOperator::In,
value: json!([payload, "legitimate_value", "another_value"]),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle multiple values");
assert_injection_safe(&sql, payload, "In with pollution");
}
}
#[test]
fn test_type_confusion_injection_safety() {
let payloads = vec!["1 OR 1=1", "1; DROP TABLE users", "1 AND SLEEP(5)"];
for payload in payloads {
let clause = WhereClause::Field {
path: vec!["user_id".to_string()],
operator: WhereOperator::Eq,
value: json!(payload), };
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle type confusion");
assert_injection_safe(&sql, payload, "Type confusion");
}
}
#[test]
fn test_nested_field_path_injection_safety() {
let payloads = vec!["'; DROP TABLE data; --", "' OR '1'='1"];
for payload in payloads {
let clause = WhereClause::Field {
path: vec!["profile".to_string(), "email".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle nested paths");
assert_injection_safe(&sql, payload, "Nested field");
}
}
#[test]
fn test_compound_condition_injection_safety() {
let payload = "'; DROP TABLE users; --";
let condition1 = WhereClause::Field {
path: vec!["field1".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let condition2 = WhereClause::Field {
path: vec!["field2".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let sql1 = WhereSqlGenerator::to_sql(&condition1).unwrap();
let sql2 = WhereSqlGenerator::to_sql(&condition2).unwrap();
assert_injection_safe(&sql1, payload, "Compound1");
assert_injection_safe(&sql2, payload, "Compound2");
}
#[test]
fn test_empty_string_injection_safety() {
let clause = WhereClause::Field {
path: vec!["field".to_string()],
operator: WhereOperator::Eq,
value: json!(""),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle empty string");
assert_injection_safe(&sql, "", "Empty string");
}
#[test]
fn test_long_payload_injection_safety() {
let long_payload = "'; DROP TABLE users; --".repeat(1000);
let clause = WhereClause::Field {
path: vec!["field".to_string()],
operator: WhereOperator::Eq,
value: json!(long_payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle long payload");
assert_injection_safe(&sql, &long_payload, "Long payload");
assert!(sql.len() < long_payload.len() * 2, "SQL should not explode in size");
}
#[test]
fn test_unicode_injection_safety() {
let payloads = vec![
"'; DROP TABLE users; -- ä½ å¥½",
"' OR '1'='1 🔓",
"admin' --🔓",
"' UNION SELECT NULL -- \u{202E}",
];
for payload in payloads {
let clause = WhereClause::Field {
path: vec!["field".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle unicode");
assert_injection_safe(&sql, payload, "Unicode");
}
}
#[test]
fn test_all_quote_variants_injection_safety() {
let quote_variants = vec![
"\"", "'", "`", "´", "′", "'", ];
for quote_char in quote_variants {
let payload = format!("{}1 OR 1=1{}", quote_char, quote_char);
let clause = WhereClause::Field {
path: vec!["field".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle quote variant");
assert_injection_safe(&sql, &payload, &format!("Quote variant: {}", quote_char));
}
}
#[test]
fn test_postgresql_specific_injection_safety() {
let payloads = vec![
"'; CREATE ROLE attacker; --",
"' || pg_sleep(5) --",
"' AND EXISTS(SELECT 1 FROM pg_tables) --",
];
for payload in payloads {
let clause = WhereClause::Field {
path: vec!["field".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle PG injection");
assert_injection_safe(&sql, payload, "PostgreSQL");
}
}
#[test]
fn test_mysql_specific_injection_safety() {
let payloads = vec![
"'; INTO OUTFILE '/tmp/shell.php' --",
"' UNION SELECT @@version --",
"' AND SLEEP(5) --",
];
for payload in payloads {
let clause = WhereClause::Field {
path: vec!["field".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle MySQL injection");
assert_injection_safe(&sql, payload, "MySQL");
}
}
#[test]
fn test_sqlserver_specific_injection_safety() {
let payloads = vec![
"'; EXEC xp_cmdshell; --",
"' UNION SELECT @@version --",
"' AND WAITFOR DELAY '00:00:05' --",
];
for payload in payloads {
let clause = WhereClause::Field {
path: vec!["field".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause).expect("Should handle SQL Server injection");
assert_injection_safe(&sql, payload, "SQL Server");
}
}
#[test]
fn test_comprehensive_owasp_injection_safety() {
let all_operators = vec![
WhereOperator::Eq,
WhereOperator::Neq,
WhereOperator::Gt,
WhereOperator::Gte,
WhereOperator::Lt,
WhereOperator::Lte,
WhereOperator::Contains,
WhereOperator::Icontains,
WhereOperator::Startswith,
WhereOperator::Istartswith,
WhereOperator::Endswith,
WhereOperator::Iendswith,
];
for payload in OWASP_PAYLOADS {
for operator in &all_operators {
let clause = WhereClause::Field {
path: vec!["test_field".to_string()],
operator: operator.clone(),
value: json!(payload),
};
let sql = WhereSqlGenerator::to_sql(&clause)
.unwrap_or_else(|_| panic!("Should handle payload: {}", payload));
assert_injection_safe(&sql, payload, &format!("{:?}", operator));
}
}
}
#[test]
fn test_injection_robustness_no_panic() {
let dangerous_chars = vec![
"'; --", "' OR '", "\"; --", "1; --", "'; DROP", "' UNION", "\"; DROP", "\n", "\r", "\0",
"\x00", "\\", "\\\\", "\\'", "\\\"",
];
for payload in dangerous_chars {
let clause = WhereClause::Field {
path: vec!["field".to_string()],
operator: WhereOperator::Eq,
value: json!(payload),
};
let _sql = WhereSqlGenerator::to_sql(&clause)
.unwrap_or_else(|_| panic!("Should safely handle: {}", payload));
}
}