diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S308.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S308.py new file mode 100644 index 0000000000000..45a335b00c3d7 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S308.py @@ -0,0 +1,22 @@ +from django.utils.safestring import mark_safe + + +def some_func(): + return mark_safe('') + + +@mark_safe +def some_func(): + return '' + + +from django.utils.html import mark_safe + + +def some_func(): + return mark_safe('') + + +@mark_safe +def some_func(): + return '' diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 3ceac945740fc..7786931f8fab0 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -247,6 +247,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::HardcodedPasswordDefault) { flake8_bandit::rules::hardcoded_password_default(checker, parameters); } + if checker.enabled(Rule::SuspiciousMarkSafeUsage) { + for decorator in decorator_list { + flake8_bandit::rules::suspicious_function_decorator(checker, decorator); + } + } if checker.enabled(Rule::PropertyWithParameters) { pylint::rules::property_with_parameters(checker, stmt, decorator_list, parameters); } diff --git a/crates/ruff_linter/src/rules/flake8_bandit/mod.rs b/crates/ruff_linter/src/rules/flake8_bandit/mod.rs index f8922655313be..ec2a462aafbbd 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/mod.rs @@ -46,6 +46,7 @@ mod tests { #[test_case(Rule::SubprocessWithoutShellEqualsTrue, Path::new("S603.py"))] #[test_case(Rule::SuspiciousPickleUsage, Path::new("S301.py"))] #[test_case(Rule::SuspiciousEvalUsage, Path::new("S307.py"))] + #[test_case(Rule::SuspiciousMarkSafeUsage, Path::new("S308.py"))] #[test_case(Rule::SuspiciousURLOpenUsage, Path::new("S310.py"))] #[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"))] #[test_case(Rule::SuspiciousTelnetlibImport, Path::new("S401.py"))] diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs index 2589b9514f2dd..312a55bedc053 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/suspicious_function_call.rs @@ -3,7 +3,7 @@ //! See: use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{self as ast, Expr, ExprCall}; +use ruff_python_ast::{self as ast, Decorator, Expr, ExprCall}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -848,7 +848,7 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) { // Eval ["" | "builtins", "eval"] => Some(SuspiciousEvalUsage.into()), // MarkSafe - ["django", "utils", "safestring", "mark_safe"] => Some(SuspiciousMarkSafeUsage.into()), + ["django", "utils", "safestring" | "html", "mark_safe"] => Some(SuspiciousMarkSafeUsage.into()), // URLOpen (`urlopen`, `urlretrieve`, `Request`) ["urllib", "request", "urlopen" | "urlretrieve" | "Request"] | ["six", "moves", "urllib", "request", "urlopen" | "urlretrieve" | "Request"] => { @@ -901,3 +901,27 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) { checker.diagnostics.push(diagnostic); } } + +/// S308 +pub(crate) fn suspicious_function_decorator(checker: &mut Checker, decorator: &Decorator) { + let Some(diagnostic_kind) = checker + .semantic() + .resolve_call_path(&decorator.expression) + .and_then(|call_path| { + match call_path.as_slice() { + // MarkSafe + ["django", "utils", "safestring" | "html", "mark_safe"] => { + Some(SuspiciousMarkSafeUsage.into()) + } + _ => None, + } + }) + else { + return; + }; + + let diagnostic = Diagnostic::new::(diagnostic_kind, decorator.range()); + if checker.enabled(diagnostic.kind.rule()) { + checker.diagnostics.push(diagnostic); + } +} diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S308_S308.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S308_S308.py.snap new file mode 100644 index 0000000000000..d2484ff7a57e7 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__S308_S308.py.snap @@ -0,0 +1,34 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs +--- +S308.py:5:12: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities + | +4 | def some_func(): +5 | return mark_safe('') + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308 + | + +S308.py:8:1: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities + | + 8 | @mark_safe + | ^^^^^^^^^^ S308 + 9 | def some_func(): +10 | return '' + | + +S308.py:17:12: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities + | +16 | def some_func(): +17 | return mark_safe('') + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S308 + | + +S308.py:20:1: S308 Use of `mark_safe` may expose cross-site scripting vulnerabilities + | +20 | @mark_safe + | ^^^^^^^^^^ S308 +21 | def some_func(): +22 | return '' + | + +