diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S604.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S604.py index 49d906508b..3ee5edd1f1 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S604.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S604.py @@ -22,3 +22,15 @@ foo(shell=lambda: 0) foo(shell=f"{b''}") x = 1 foo(shell=f"{x=}") +print(bool(dict(shell=f"{f""!s}")["shell"])) + +# Unknown truthiness +print(bool(dict(shell=f"{"x":.0}")["shell"])) + + +class C: + def __gt__(self, other): + return "" + + +print(bool(dict(shell=f"{C() > C()}")["shell"])) diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S604_S604.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S604_S604.py.snap index 798e916ba6..569e4819fc 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S604_S604.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S604_S604.py.snap @@ -90,4 +90,5 @@ S604 Function call with truthy `shell` parameter identified, security issue 23 | x = 1 24 | foo(shell=f"{x=}") | ^^^ +25 | print(bool(dict(shell=f"{f""!s}")["shell"])) | diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 9680d03ec3..8c604c4ed2 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -1376,7 +1376,9 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool { Expr::ListComp(_) => true, Expr::SetComp(_) => true, Expr::DictComp(_) => true, - Expr::Compare(_) => true, + // Compare can return any value, even empty string + // https://docs.python.org/3/reference/datamodel.html#object.__ge__ + Expr::Compare(_) => false, Expr::NumberLiteral(_) => true, Expr::BooleanLiteral(_) => true, Expr::NoneLiteral(_) => true, @@ -1418,12 +1420,16 @@ fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool { expr.value.iter().any(|part| match part { ast::FStringPart::Literal(string_literal) => !string_literal.is_empty(), ast::FStringPart::FString(f_string) => { - f_string.elements.iter().all(|element| match element { - InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(), - InterpolatedStringElement::Interpolation(f_string) => { - f_string.debug_text.is_some() || inner(&f_string.expression) - } - }) + !f_string.elements.is_empty() + && f_string.elements.iter().all(|element| match element { + InterpolatedStringElement::Literal(string_literal) => { + !string_literal.is_empty() + } + InterpolatedStringElement::Interpolation(f_string) => { + f_string.format_spec.is_none() + && (f_string.debug_text.is_some() || inner(&f_string.expression)) + } + }) } }) }