diff --git a/crates/qbx_lua_analysis/src/checks/security.rs b/crates/qbx_lua_analysis/src/checks/security.rs index 3ef8303..38383c9 100644 --- a/crates/qbx_lua_analysis/src/checks/security.rs +++ b/crates/qbx_lua_analysis/src/checks/security.rs @@ -66,11 +66,8 @@ impl Security<'_, '_> { // A parameter table next to the query means values are bound; what is concatenated then is // structure (a WHERE clause, a column list), which placeholders cannot express. let binds_parameters = args.get(1).is_some_and(|arg| !matches!(arg.kind, ExprKind::Function(_))); - if binds_parameters { - return; - } let built = match &query.unparen().kind { - ExprKind::Binary { op: BinOp::Concat, .. } => has_dynamic_part(query), + ExprKind::Binary { op: BinOp::Concat, .. } => !binds_parameters && has_dynamic_part(query), ExprKind::MethodCall { base, method, args, .. } if method.text == "format" && !args.is_empty() => { let only_identifiers = |text: &str| text.matches("%s").count() == text.matches("`%s`").count(); base.unparen().as_string().is_some_and(|text| !only_identifiers(text)) diff --git a/crates/qbx_lua_analysis/tests/rules.rs b/crates/qbx_lua_analysis/tests/rules.rs index 94ac2de..eaad85d 100644 --- a/crates/qbx_lua_analysis/tests/rules.rs +++ b/crates/qbx_lua_analysis/tests/rules.rs @@ -331,6 +331,9 @@ fn sql_must_use_placeholders() { lint("local id = 1\nMySQL.query.await(('SELECT * FROM players WHERE id = %s'):format(id))"), ["security/sql-concatenation"] ); + assert_eq!( + lint("local tableName, tableColumn, plateColumn, plate = 'a', 'b', 'c', 'd'\nMySQL.update.await(\n('UPDATE %s SET %s = 0 WHERE %s = ?'):format(tableName, tableColumn, plateColumn), { plate })"), ["security/sql-concatenation"] + ); assert_eq!(lint("local id = 1\nMySQL.query('SELECT * FROM players WHERE id = ?', { id })"), Vec::<&str>::new()); assert_eq!(lint("MySQL.query('SELECT * FROM ' .. 'players')"), Vec::<&str>::new()); assert_eq!(