diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py index 620a18c038..666db2f0dd 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S608.py @@ -179,3 +179,10 @@ query63 = t""" """ query64 = f"update {t"{table}"} set var = {t"{var}"}" query65 = t"update {f"{table}"} set var = {f"{var}"}" + +# Implicit string concatenation with binary operation +def query66(): + return ( + "select * " + "from \"" + schema + "\"" + ) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs index 34134d96c7..cbd1b75c0a 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs @@ -73,7 +73,7 @@ pub(crate) fn hardcoded_sql_expression(checker: &Checker, expr: &Expr) { if is_explicit_concatenation(expr) != Some(true) { return; } - checker.generator().expr(expr) + concatenated_binary_string(expr) } // "select * from table where val = %s" % ... Expr::BinOp(ast::ExprBinOp { @@ -136,6 +136,34 @@ fn concatenated_f_string(expr: &ast::ExprFString, locator: &Locator) -> String { .collect() } +/// Concatenates the contents of a binary string expression. +/// +/// ## Example +/// +/// ```python +/// "select * " "from table" + var + ";" +/// ``` +/// +/// becomes `select * from table {} ;`. +fn concatenated_binary_string(expr: &Expr) -> String { + match expr { + Expr::StringLiteral(string_literal) => string_literal.value.to_str().to_string(), + Expr::BinOp(ast::ExprBinOp { + left, + op: Operator::Add, + right, + .. + }) => { + format!( + "{}{}", + concatenated_binary_string(left), + concatenated_binary_string(right) + ) + } + _ => " {} ".to_string(), + } +} + /// Returns `Some(true)` if an expression appears to be an explicit string concatenation, /// `Some(false)` if it's _not_ an explicit concatenation, and `None` if it's ambiguous. fn is_explicit_concatenation(expr: &Expr) -> Option { diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap index 09918609c0..78cfa378bc 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S608_S608.py.snap @@ -672,3 +672,14 @@ S608 Possible SQL injection vector through string-based query construction | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 181 | query65 = t"update {f"{table}"} set var = {f"{var}"}" | + +S608 Possible SQL injection vector through string-based query construction + --> S608.py:186:9 + | +184 | def query66(): +185 | return ( +186 | / "select * " +187 | | "from \"" + schema + "\"" + | |_________________________________^ +188 | ) + |