diff --git a/README.md b/README.md index c4d3445c8e..a6e0502d94 100644 --- a/README.md +++ b/README.md @@ -467,6 +467,19 @@ For more, see [pep8-naming](https://pypi.org/project/pep8-naming/0.13.2/) on PyP | N817 | CamelcaseImportedAsAcronym | Camelcase `...` imported as acronym `...` | | | N818 | ErrorSuffixOnExceptionName | Exception name `...` should be named with an Error suffix | | +### flake8-bandit + +For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on PyPI. + +| Code | Name | Message | Fix | +| ---- | ---- | ------- | --- | +| S101 | AssertUsed | Use of `assert` detected | | +| S102 | ExecUsed | Use of `exec` detected | | +| S104 | HardcodedBindAllInterfaces | Possible binding to all interfaces | | +| S105 | HardcodedPasswordString | Possible hardcoded password: `'...'` | | +| S106 | HardcodedPasswordFuncArg | Possible hardcoded password: `'...'` | | +| S107 | HardcodedPasswordDefault | Possible hardcoded password: `'...'` | | + ### flake8-comprehensions For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/3.10.1/) on PyPI. @@ -686,6 +699,7 @@ including: - [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-annotations`](https://pypi.org/project/flake8-annotations/) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) +- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (21/32) - [`flake8-2020`](https://pypi.org/project/flake8-2020/) - [`pyupgrade`](https://pypi.org/project/pyupgrade/) (15/34) @@ -709,6 +723,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [`flake8-print`](https://pypi.org/project/flake8-print/) - [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-annotations`](https://pypi.org/project/flake8-annotations/) +- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (21/32) - [`flake8-2020`](https://pypi.org/project/flake8-2020/) diff --git a/resources/test/fixtures/S101.py b/resources/test/fixtures/S101.py new file mode 100644 index 0000000000..3fd80d6bd3 --- /dev/null +++ b/resources/test/fixtures/S101.py @@ -0,0 +1,11 @@ +# Error +assert True + +def fn(): + x = 1 + + # Error + assert x == 1 + + # Error + assert x == 2 diff --git a/resources/test/fixtures/S102.py b/resources/test/fixtures/S102.py new file mode 100644 index 0000000000..4c346d5545 --- /dev/null +++ b/resources/test/fixtures/S102.py @@ -0,0 +1,5 @@ +def fn(): + # Error + exec('x = 2') + +exec('y = 3') diff --git a/resources/test/fixtures/S104.py b/resources/test/fixtures/S104.py new file mode 100644 index 0000000000..3bbab01871 --- /dev/null +++ b/resources/test/fixtures/S104.py @@ -0,0 +1,19 @@ +def func(address): + print(address) + + +# OK +"OK" + +# Error +"0.0.0.0" +'0.0.0.0' + + +# Error +func("0.0.0.0") + + +def my_func(): + x = "0.0.0.0" + print(x) diff --git a/resources/test/fixtures/S105.py b/resources/test/fixtures/S105.py new file mode 100644 index 0000000000..426610274c --- /dev/null +++ b/resources/test/fixtures/S105.py @@ -0,0 +1,53 @@ +d = {} + +# OK +safe = "s3cr3t" +password = True +password = safe +password is True +password == 1 +d["safe"] = "s3cr3t" + +# Errors +password = "s3cr3t" +_pass = "s3cr3t" +passwd = "s3cr3t" +pwd = "s3cr3t" +secret = "s3cr3t" +token = "s3cr3t" +secrete = "s3cr3t" +safe = password = "s3cr3t" +password = safe = "s3cr3t" + +d["password"] = "s3cr3t" +d["pass"] = "s3cr3t" +d["passwd"] = "s3cr3t" +d["pwd"] = "s3cr3t" +d["secret"] = "s3cr3t" +d["token"] = "s3cr3t" +d["secrete"] = "s3cr3t" +safe = d["password"] = "s3cr3t" +d["password"] = safe = "s3cr3t" + + +class MyClass: + password = "s3cr3t" + safe = password + + +MyClass.password = "s3cr3t" +MyClass._pass = "s3cr3t" +MyClass.passwd = "s3cr3t" +MyClass.pwd = "s3cr3t" +MyClass.secret = "s3cr3t" +MyClass.token = "s3cr3t" +MyClass.secrete = "s3cr3t" + +password == "s3cr3t" +_pass == "s3cr3t" +passwd == "s3cr3t" +pwd == "s3cr3t" +secret == "s3cr3t" +token == "s3cr3t" +secrete == "s3cr3t" +password == safe == "s3cr3t" diff --git a/resources/test/fixtures/S106.py b/resources/test/fixtures/S106.py new file mode 100644 index 0000000000..fd83a752e9 --- /dev/null +++ b/resources/test/fixtures/S106.py @@ -0,0 +1,13 @@ +def func(pos, password): + pass + + +string = "Hello World" + +# OK +func("s3cr3t") +func(1, password=string) +func(pos="s3cr3t", password=string) + +# Error +func(1, password="s3cr3t") diff --git a/resources/test/fixtures/S107.py b/resources/test/fixtures/S107.py new file mode 100644 index 0000000000..a954e8de0c --- /dev/null +++ b/resources/test/fixtures/S107.py @@ -0,0 +1,30 @@ +def ok(first, default="default"): + pass + + +def default(first, password="default"): + pass + + +def ok_posonly(first, /, pos, default="posonly"): + pass + + +def default_posonly(first, /, pos, password="posonly"): + pass + + +def ok_kwonly(first, *, default="kwonly"): + pass + + +def default_kwonly(first, *, password="kwonly"): + pass + + +def ok_all(first, /, pos, default="posonly", *, kwonly="kwonly"): + pass + + +def default_all(first, /, pos, secret="posonly", *, password="kwonly"): + pass diff --git a/src/check_ast.rs b/src/check_ast.rs index 44cbfaeb82..71a5da8b16 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -32,7 +32,7 @@ use crate::settings::Settings; use crate::source_code_locator::SourceCodeLocator; use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope}; use crate::{ - docstrings, flake8_2020, flake8_annotations, flake8_bugbear, flake8_builtins, + docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade, }; @@ -350,6 +350,12 @@ where flake8_bugbear::plugins::cached_instance_method(self, decorator_list); } + if self.settings.enabled.contains(&CheckCode::S107) { + self.add_checks( + flake8_bandit::plugins::hardcoded_password_default(args).into_iter(), + ); + } + self.check_builtin_shadowing(name, Range::from_located(stmt), true); // Visit the decorators and arguments, but avoid the body, which will be @@ -803,6 +809,9 @@ where if self.settings.enabled.contains(&CheckCode::B011) { flake8_bugbear::plugins::assert_false(self, stmt, test, msg); } + if self.settings.enabled.contains(&CheckCode::S101) { + self.add_check(flake8_bandit::plugins::assert_used(stmt)); + } } StmtKind::With { items, .. } | StmtKind::AsyncWith { items, .. } => { if self.settings.enabled.contains(&CheckCode::B017) { @@ -843,6 +852,13 @@ where if self.settings.enabled.contains(&CheckCode::B003) { flake8_bugbear::plugins::assignment_to_os_environ(self, targets); } + if self.settings.enabled.contains(&CheckCode::S105) { + if let Some(check) = + flake8_bandit::plugins::assign_hardcoded_password_string(value, targets) + { + self.add_check(check); + } + } } StmtKind::AnnAssign { value, .. } => { if self.settings.enabled.contains(&CheckCode::E731) { @@ -1108,6 +1124,16 @@ where self, args, keywords, ); } + if self.settings.enabled.contains(&CheckCode::S102) { + if let Some(check) = flake8_bandit::plugins::exec_used(expr, func) { + self.add_check(check); + } + } + if self.settings.enabled.contains(&CheckCode::S106) { + self.add_checks( + flake8_bandit::plugins::hardcoded_password_func_arg(keywords).into_iter(), + ); + } // flake8-comprehensions if self.settings.enabled.contains(&CheckCode::C400) { @@ -1456,6 +1482,16 @@ where { flake8_2020::plugins::compare(self, left, ops, comparators); } + + if self.settings.enabled.contains(&CheckCode::S105) { + self.add_checks( + flake8_bandit::plugins::compare_to_hardcoded_password_string( + left, + comparators, + ) + .into_iter(), + ); + } } ExprKind::Constant { value: Constant::Str(value), @@ -1465,6 +1501,14 @@ where self.deferred_string_annotations .push((Range::from_located(expr), value)); } + if self.settings.enabled.contains(&CheckCode::S104) { + if let Some(check) = flake8_bandit::plugins::hardcoded_bind_all_interfaces( + value, + &Range::from_located(expr), + ) { + self.add_check(check); + } + } } ExprKind::Lambda { args, .. } => { // Visit the arguments, but avoid the body, which will be deferred. diff --git a/src/checks.rs b/src/checks.rs index 34fc6ab016..aabdc0c7f1 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -220,6 +220,13 @@ pub enum CheckCode { N818, // isort I001, + // flake8-bandit + S101, + S102, + S104, + S105, + S106, + S107, // Ruff RUF001, RUF002, @@ -236,6 +243,7 @@ pub enum CheckCategory { Pydocstyle, Pyupgrade, PEP8Naming, + Flake8Bandit, Flake8Comprehensions, Flake8Bugbear, Flake8Builtins, @@ -253,6 +261,7 @@ impl CheckCategory { CheckCategory::Pycodestyle => "pycodestyle", CheckCategory::Pyflakes => "Pyflakes", CheckCategory::Isort => "isort", + CheckCategory::Flake8Bandit => "flake8-bandit", CheckCategory::Flake8Builtins => "flake8-builtins", CheckCategory::Flake8Bugbear => "flake8-bugbear", CheckCategory::Flake8Comprehensions => "flake8-comprehensions", @@ -291,6 +300,7 @@ impl CheckCategory { CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"), CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"), CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"), + CheckCategory::Flake8Bandit => Some("https://pypi.org/project/flake8-bandit/4.1.1/"), CheckCategory::Ruff => None, CheckCategory::Meta => None, } @@ -509,6 +519,13 @@ pub enum CheckKind { ErrorSuffixOnExceptionName(String), // isort UnsortedImports, + // flake8-bandit + AssertUsed, + ExecUsed, + HardcodedBindAllInterfaces, + HardcodedPasswordString(String), + HardcodedPasswordFuncArg(String), + HardcodedPasswordDefault(String), // Ruff AmbiguousUnicodeCharacterString(char, char), AmbiguousUnicodeCharacterDocstring(char, char), @@ -773,6 +790,13 @@ impl CheckCode { CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()), // isort CheckCode::I001 => CheckKind::UnsortedImports, + // flake8-bandit + CheckCode::S101 => CheckKind::AssertUsed, + CheckCode::S102 => CheckKind::ExecUsed, + CheckCode::S104 => CheckKind::HardcodedBindAllInterfaces, + CheckCode::S105 => CheckKind::HardcodedPasswordString("...".to_string()), + CheckCode::S106 => CheckKind::HardcodedPasswordFuncArg("...".to_string()), + CheckCode::S107 => CheckKind::HardcodedPasswordDefault("...".to_string()), // Ruff CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'), CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'), @@ -965,6 +989,12 @@ impl CheckCode { CheckCode::N817 => CheckCategory::PEP8Naming, CheckCode::N818 => CheckCategory::PEP8Naming, CheckCode::I001 => CheckCategory::Isort, + CheckCode::S101 => CheckCategory::Flake8Bandit, + CheckCode::S102 => CheckCategory::Flake8Bandit, + CheckCode::S104 => CheckCategory::Flake8Bandit, + CheckCode::S105 => CheckCategory::Flake8Bandit, + CheckCode::S106 => CheckCategory::Flake8Bandit, + CheckCode::S107 => CheckCategory::Flake8Bandit, CheckCode::RUF001 => CheckCategory::Ruff, CheckCode::RUF002 => CheckCategory::Ruff, CheckCode::RUF003 => CheckCategory::Ruff, @@ -1171,6 +1201,13 @@ impl CheckKind { CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818, // isort CheckKind::UnsortedImports => &CheckCode::I001, + // flake8-bandit + CheckKind::AssertUsed => &CheckCode::S101, + CheckKind::ExecUsed => &CheckCode::S102, + CheckKind::HardcodedBindAllInterfaces => &CheckCode::S104, + CheckKind::HardcodedPasswordString(..) => &CheckCode::S105, + CheckKind::HardcodedPasswordFuncArg(..) => &CheckCode::S106, + CheckKind::HardcodedPasswordDefault(..) => &CheckCode::S107, // Ruff CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001, CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002, @@ -1778,6 +1815,21 @@ impl CheckKind { } // isort CheckKind::UnsortedImports => "Import block is un-sorted or un-formatted".to_string(), + // flake8-bandit + CheckKind::AssertUsed => "Use of `assert` detected".to_string(), + CheckKind::ExecUsed => "Use of `exec` detected".to_string(), + CheckKind::HardcodedBindAllInterfaces => { + "Possible binding to all interfaces".to_string() + } + CheckKind::HardcodedPasswordString(string) => { + format!("Possible hardcoded password: `'{string}'`") + } + CheckKind::HardcodedPasswordFuncArg(string) => { + format!("Possible hardcoded password: `'{string}'`") + } + CheckKind::HardcodedPasswordDefault(string) => { + format!("Possible hardcoded password: `'{string}'`") + } // Ruff CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => { format!( diff --git a/src/checks_gen.rs b/src/checks_gen.rs index 19f0c8e63f..ed7631f98a 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -245,6 +245,15 @@ pub enum CheckCodePrefix { RUF001, RUF002, RUF003, + S, + S1, + S10, + S101, + S102, + S104, + S105, + S106, + S107, T, T2, T20, @@ -988,6 +997,36 @@ impl CheckCodePrefix { CheckCodePrefix::RUF001 => vec![CheckCode::RUF001], CheckCodePrefix::RUF002 => vec![CheckCode::RUF002], CheckCodePrefix::RUF003 => vec![CheckCode::RUF003], + CheckCodePrefix::S => vec![ + CheckCode::S101, + CheckCode::S102, + CheckCode::S104, + CheckCode::S105, + CheckCode::S106, + CheckCode::S107, + ], + CheckCodePrefix::S1 => vec![ + CheckCode::S101, + CheckCode::S102, + CheckCode::S104, + CheckCode::S105, + CheckCode::S106, + CheckCode::S107, + ], + CheckCodePrefix::S10 => vec![ + CheckCode::S101, + CheckCode::S102, + CheckCode::S104, + CheckCode::S105, + CheckCode::S106, + CheckCode::S107, + ], + CheckCodePrefix::S101 => vec![CheckCode::S101], + CheckCodePrefix::S102 => vec![CheckCode::S102], + CheckCodePrefix::S104 => vec![CheckCode::S104], + CheckCodePrefix::S105 => vec![CheckCode::S105], + CheckCodePrefix::S106 => vec![CheckCode::S106], + CheckCodePrefix::S107 => vec![CheckCode::S107], CheckCodePrefix::T => vec![CheckCode::T201, CheckCode::T203], CheckCodePrefix::T2 => vec![CheckCode::T201, CheckCode::T203], CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203], @@ -1299,6 +1338,15 @@ impl CheckCodePrefix { CheckCodePrefix::I0 => PrefixSpecificity::Hundreds, CheckCodePrefix::I00 => PrefixSpecificity::Tens, CheckCodePrefix::I001 => PrefixSpecificity::Explicit, + CheckCodePrefix::S => PrefixSpecificity::Category, + CheckCodePrefix::S1 => PrefixSpecificity::Hundreds, + CheckCodePrefix::S10 => PrefixSpecificity::Tens, + CheckCodePrefix::S101 => PrefixSpecificity::Explicit, + CheckCodePrefix::S102 => PrefixSpecificity::Explicit, + CheckCodePrefix::S104 => PrefixSpecificity::Explicit, + CheckCodePrefix::S105 => PrefixSpecificity::Explicit, + CheckCodePrefix::S106 => PrefixSpecificity::Explicit, + CheckCodePrefix::S107 => PrefixSpecificity::Explicit, CheckCodePrefix::M => PrefixSpecificity::Category, CheckCodePrefix::M0 => PrefixSpecificity::Hundreds, CheckCodePrefix::M00 => PrefixSpecificity::Tens, diff --git a/src/flake8_bandit/helpers.rs b/src/flake8_bandit/helpers.rs new file mode 100644 index 0000000000..351cf12645 --- /dev/null +++ b/src/flake8_bandit/helpers.rs @@ -0,0 +1,22 @@ +use rustpython_ast::{Constant, Expr, ExprKind}; + +const PASSWORD_NAMES: [&str; 7] = [ + "password", "pass", "passwd", "pwd", "secret", "token", "secrete", +]; + +pub fn string_literal(expr: &Expr) -> Option<&str> { + match &expr.node { + ExprKind::Constant { + value: Constant::Str(string), + .. + } => Some(string), + _ => None, + } +} + +// Maybe use regex for this? +pub fn matches_password_name(string: &str) -> bool { + PASSWORD_NAMES + .iter() + .any(|name| string.to_lowercase().contains(name)) +} diff --git a/src/flake8_bandit/mod.rs b/src/flake8_bandit/mod.rs new file mode 100644 index 0000000000..ef735e49ff --- /dev/null +++ b/src/flake8_bandit/mod.rs @@ -0,0 +1,2 @@ +mod helpers; +pub mod plugins; diff --git a/src/flake8_bandit/plugins/assert_used.rs b/src/flake8_bandit/plugins/assert_used.rs new file mode 100644 index 0000000000..a41bd585fb --- /dev/null +++ b/src/flake8_bandit/plugins/assert_used.rs @@ -0,0 +1,9 @@ +use rustpython_ast::{Located, StmtKind}; + +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; + +/// S101 +pub fn assert_used(stmt: &Located) -> Check { + Check::new(CheckKind::AssertUsed, Range::from_located(stmt)) +} diff --git a/src/flake8_bandit/plugins/exec_used.rs b/src/flake8_bandit/plugins/exec_used.rs new file mode 100644 index 0000000000..8d37af48f5 --- /dev/null +++ b/src/flake8_bandit/plugins/exec_used.rs @@ -0,0 +1,14 @@ +use rustpython_ast::{Expr, ExprKind}; + +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; + +/// S102 +pub fn exec_used(expr: &Expr, func: &Expr) -> Option { + if let ExprKind::Name { id, .. } = &func.node { + if id == "exec" { + return Some(Check::new(CheckKind::ExecUsed, Range::from_located(expr))); + } + } + None +} diff --git a/src/flake8_bandit/plugins/hardcoded_bind_all_interfaces.rs b/src/flake8_bandit/plugins/hardcoded_bind_all_interfaces.rs new file mode 100644 index 0000000000..4152cadf3a --- /dev/null +++ b/src/flake8_bandit/plugins/hardcoded_bind_all_interfaces.rs @@ -0,0 +1,11 @@ +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; + +/// S104 +pub fn hardcoded_bind_all_interfaces(value: &str, range: &Range) -> Option { + if value == "0.0.0.0" { + Some(Check::new(CheckKind::HardcodedBindAllInterfaces, *range)) + } else { + None + } +} diff --git a/src/flake8_bandit/plugins/hardcoded_password_default.rs b/src/flake8_bandit/plugins/hardcoded_password_default.rs new file mode 100644 index 0000000000..f410a482f4 --- /dev/null +++ b/src/flake8_bandit/plugins/hardcoded_password_default.rs @@ -0,0 +1,51 @@ +use rustpython_ast::{ArgData, Arguments, Expr, Located}; + +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; +use crate::flake8_bandit::helpers::{matches_password_name, string_literal}; + +fn check_password_kwarg(arg: &Located, default: &Expr) -> Option { + if let Some(string) = string_literal(default) { + let kwarg_name = &arg.node.arg; + if matches_password_name(kwarg_name) { + return Some(Check::new( + CheckKind::HardcodedPasswordDefault(string.to_string()), + Range::from_located(default), + )); + } + } + None +} + +/// S107 +pub fn hardcoded_password_default(arguments: &Arguments) -> Vec { + let mut checks: Vec = Vec::new(); + + let defaults_start = + arguments.posonlyargs.len() + arguments.args.len() - arguments.defaults.len(); + for (i, arg) in arguments + .posonlyargs + .iter() + .chain(&arguments.args) + .enumerate() + { + if let Some(i) = i.checked_sub(defaults_start) { + let default = &arguments.defaults[i]; + if let Some(check) = check_password_kwarg(arg, default) { + checks.push(check); + } + } + } + + let defaults_start = arguments.kwonlyargs.len() - arguments.kw_defaults.len(); + for (i, kwarg) in arguments.kwonlyargs.iter().enumerate() { + if let Some(i) = i.checked_sub(defaults_start) { + let default = &arguments.kw_defaults[i]; + if let Some(check) = check_password_kwarg(kwarg, default) { + checks.push(check); + } + } + } + + checks +} diff --git a/src/flake8_bandit/plugins/hardcoded_password_func_arg.rs b/src/flake8_bandit/plugins/hardcoded_password_func_arg.rs new file mode 100644 index 0000000000..8422dd6633 --- /dev/null +++ b/src/flake8_bandit/plugins/hardcoded_password_func_arg.rs @@ -0,0 +1,25 @@ +use rustpython_ast::Keyword; + +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; +use crate::flake8_bandit::helpers::{matches_password_name, string_literal}; + +/// S106 +pub fn hardcoded_password_func_arg(keywords: &[Keyword]) -> Vec { + keywords + .iter() + .filter_map(|keyword| { + if let Some(string) = string_literal(&keyword.node.value) { + if let Some(arg) = &keyword.node.arg { + if matches_password_name(arg) { + return Some(Check::new( + CheckKind::HardcodedPasswordFuncArg(string.to_string()), + Range::from_located(keyword), + )); + } + } + } + None + }) + .collect() +} diff --git a/src/flake8_bandit/plugins/hardcoded_password_string.rs b/src/flake8_bandit/plugins/hardcoded_password_string.rs new file mode 100644 index 0000000000..a8f8fb451d --- /dev/null +++ b/src/flake8_bandit/plugins/hardcoded_password_string.rs @@ -0,0 +1,58 @@ +use rustpython_ast::{Constant, Expr, ExprKind}; + +use crate::ast::types::Range; +use crate::checks::{Check, CheckKind}; +use crate::flake8_bandit::helpers::{matches_password_name, string_literal}; + +fn is_password_target(target: &Expr) -> bool { + let target_name = match &target.node { + // variable = "s3cr3t" + ExprKind::Name { id, .. } => id, + // d["password"] = "s3cr3t" + ExprKind::Subscript { slice, .. } => match &slice.node { + ExprKind::Constant { + value: Constant::Str(string), + .. + } => string, + _ => return false, + }, + // obj.password = "s3cr3t" + ExprKind::Attribute { attr, .. } => attr, + _ => return false, + }; + + matches_password_name(target_name) +} + +/// S105 +pub fn compare_to_hardcoded_password_string(left: &Expr, comparators: &[Expr]) -> Vec { + comparators + .iter() + .filter_map(|comp| { + if let Some(string) = string_literal(comp) { + if is_password_target(left) { + return Some(Check::new( + CheckKind::HardcodedPasswordString(string.to_string()), + Range::from_located(comp), + )); + } + } + None + }) + .collect() +} + +/// S105 +pub fn assign_hardcoded_password_string(value: &Expr, targets: &Vec) -> Option { + if let Some(string) = string_literal(value) { + for target in targets { + if is_password_target(target) { + return Some(Check::new( + CheckKind::HardcodedPasswordString(string.to_string()), + Range::from_located(value), + )); + } + } + } + None +} diff --git a/src/flake8_bandit/plugins/mod.rs b/src/flake8_bandit/plugins/mod.rs new file mode 100644 index 0000000000..ab917c5a22 --- /dev/null +++ b/src/flake8_bandit/plugins/mod.rs @@ -0,0 +1,15 @@ +pub use assert_used::assert_used; +pub use exec_used::exec_used; +pub use hardcoded_bind_all_interfaces::hardcoded_bind_all_interfaces; +pub use hardcoded_password_default::hardcoded_password_default; +pub use hardcoded_password_func_arg::hardcoded_password_func_arg; +pub use hardcoded_password_string::{ + assign_hardcoded_password_string, compare_to_hardcoded_password_string, +}; + +mod assert_used; +mod exec_used; +mod hardcoded_bind_all_interfaces; +mod hardcoded_password_default; +mod hardcoded_password_func_arg; +mod hardcoded_password_string; diff --git a/src/lib.rs b/src/lib.rs index 2f228edf77..d172988833 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ mod directives; mod docstrings; mod flake8_2020; pub mod flake8_annotations; +pub mod flake8_bandit; mod flake8_bugbear; mod flake8_builtins; mod flake8_comprehensions; diff --git a/src/linter.rs b/src/linter.rs index 3db8ce4269..16e43b03fb 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -469,6 +469,12 @@ mod tests { #[test_case(CheckCode::N816, Path::new("N816.py"); "N816")] #[test_case(CheckCode::N817, Path::new("N817.py"); "N817")] #[test_case(CheckCode::N818, Path::new("N818.py"); "N818")] + #[test_case(CheckCode::S101, Path::new("S101.py"); "S101")] + #[test_case(CheckCode::S102, Path::new("S102.py"); "S102")] + #[test_case(CheckCode::S104, Path::new("S104.py"); "S104")] + #[test_case(CheckCode::S105, Path::new("S105.py"); "S105")] + #[test_case(CheckCode::S106, Path::new("S106.py"); "S106")] + #[test_case(CheckCode::S107, Path::new("S107.py"); "S107")] #[test_case(CheckCode::T201, Path::new("T201.py"); "T201")] #[test_case(CheckCode::T203, Path::new("T203.py"); "T203")] #[test_case(CheckCode::U001, Path::new("U001.py"); "U001")] diff --git a/src/snapshots/ruff__linter__tests__S101_S101.py.snap b/src/snapshots/ruff__linter__tests__S101_S101.py.snap new file mode 100644 index 0000000000..6f529a8c9e --- /dev/null +++ b/src/snapshots/ruff__linter__tests__S101_S101.py.snap @@ -0,0 +1,29 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: AssertUsed + location: + row: 2 + column: 0 + end_location: + row: 2 + column: 11 + fix: ~ +- kind: AssertUsed + location: + row: 8 + column: 4 + end_location: + row: 8 + column: 17 + fix: ~ +- kind: AssertUsed + location: + row: 11 + column: 4 + end_location: + row: 11 + column: 17 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__S102_S102.py.snap b/src/snapshots/ruff__linter__tests__S102_S102.py.snap new file mode 100644 index 0000000000..82532d4410 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__S102_S102.py.snap @@ -0,0 +1,21 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: ExecUsed + location: + row: 3 + column: 4 + end_location: + row: 3 + column: 17 + fix: ~ +- kind: ExecUsed + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 13 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__S104_S104.py.snap b/src/snapshots/ruff__linter__tests__S104_S104.py.snap new file mode 100644 index 0000000000..13779f2bc7 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__S104_S104.py.snap @@ -0,0 +1,37 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: HardcodedBindAllInterfaces + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 9 + fix: ~ +- kind: HardcodedBindAllInterfaces + location: + row: 10 + column: 0 + end_location: + row: 10 + column: 9 + fix: ~ +- kind: HardcodedBindAllInterfaces + location: + row: 14 + column: 5 + end_location: + row: 14 + column: 14 + fix: ~ +- kind: HardcodedBindAllInterfaces + location: + row: 18 + column: 8 + end_location: + row: 18 + column: 17 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__S105_S105.py.snap b/src/snapshots/ruff__linter__tests__S105_S105.py.snap new file mode 100644 index 0000000000..9bdbd91f7e --- /dev/null +++ b/src/snapshots/ruff__linter__tests__S105_S105.py.snap @@ -0,0 +1,311 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 12 + column: 11 + end_location: + row: 12 + column: 19 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 13 + column: 8 + end_location: + row: 13 + column: 16 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 14 + column: 9 + end_location: + row: 14 + column: 17 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 15 + column: 6 + end_location: + row: 15 + column: 14 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 16 + column: 9 + end_location: + row: 16 + column: 17 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 17 + column: 8 + end_location: + row: 17 + column: 16 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 18 + column: 10 + end_location: + row: 18 + column: 18 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 19 + column: 18 + end_location: + row: 19 + column: 26 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 20 + column: 18 + end_location: + row: 20 + column: 26 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 22 + column: 16 + end_location: + row: 22 + column: 24 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 23 + column: 12 + end_location: + row: 23 + column: 20 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 24 + column: 14 + end_location: + row: 24 + column: 22 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 25 + column: 11 + end_location: + row: 25 + column: 19 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 26 + column: 14 + end_location: + row: 26 + column: 22 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 27 + column: 13 + end_location: + row: 27 + column: 21 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 28 + column: 15 + end_location: + row: 28 + column: 23 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 29 + column: 23 + end_location: + row: 29 + column: 31 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 30 + column: 23 + end_location: + row: 30 + column: 31 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 34 + column: 15 + end_location: + row: 34 + column: 23 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 38 + column: 19 + end_location: + row: 38 + column: 27 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 39 + column: 16 + end_location: + row: 39 + column: 24 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 40 + column: 17 + end_location: + row: 40 + column: 25 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 41 + column: 14 + end_location: + row: 41 + column: 22 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 42 + column: 17 + end_location: + row: 42 + column: 25 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 43 + column: 16 + end_location: + row: 43 + column: 24 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 44 + column: 18 + end_location: + row: 44 + column: 26 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 46 + column: 12 + end_location: + row: 46 + column: 20 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 47 + column: 9 + end_location: + row: 47 + column: 17 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 48 + column: 10 + end_location: + row: 48 + column: 18 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 49 + column: 7 + end_location: + row: 49 + column: 15 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 50 + column: 10 + end_location: + row: 50 + column: 18 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 51 + column: 9 + end_location: + row: 51 + column: 17 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 52 + column: 11 + end_location: + row: 52 + column: 19 + fix: ~ +- kind: + HardcodedPasswordString: s3cr3t + location: + row: 53 + column: 20 + end_location: + row: 53 + column: 28 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__S106_S106.py.snap b/src/snapshots/ruff__linter__tests__S106_S106.py.snap new file mode 100644 index 0000000000..7b90fa73e9 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__S106_S106.py.snap @@ -0,0 +1,14 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: + HardcodedPasswordFuncArg: s3cr3t + location: + row: 13 + column: 8 + end_location: + row: 13 + column: 25 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__S107_S107.py.snap b/src/snapshots/ruff__linter__tests__S107_S107.py.snap new file mode 100644 index 0000000000..5faef919ce --- /dev/null +++ b/src/snapshots/ruff__linter__tests__S107_S107.py.snap @@ -0,0 +1,50 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: + HardcodedPasswordDefault: default + location: + row: 5 + column: 28 + end_location: + row: 5 + column: 37 + fix: ~ +- kind: + HardcodedPasswordDefault: posonly + location: + row: 13 + column: 44 + end_location: + row: 13 + column: 53 + fix: ~ +- kind: + HardcodedPasswordDefault: kwonly + location: + row: 21 + column: 38 + end_location: + row: 21 + column: 46 + fix: ~ +- kind: + HardcodedPasswordDefault: posonly + location: + row: 29 + column: 38 + end_location: + row: 29 + column: 47 + fix: ~ +- kind: + HardcodedPasswordDefault: kwonly + location: + row: 29 + column: 61 + end_location: + row: 29 + column: 69 + fix: ~ +