From fdb241cad29e1e2335062410b47f06a168c90043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Fri, 19 May 2023 06:40:50 +0300 Subject: [PATCH] [`flake8-bandit`] Implement `paramiko-call` (`S601`) (#4500) --- .../test/fixtures/flake8_bandit/S601.py | 3 ++ crates/ruff/src/checkers/ast/mod.rs | 3 ++ crates/ruff/src/codes.rs | 1 + crates/ruff/src/registry.rs | 1 + crates/ruff/src/rules/flake8_bandit/mod.rs | 1 + .../ruff/src/rules/flake8_bandit/rules/mod.rs | 2 ++ .../flake8_bandit/rules/paramiko_calls.rs | 31 +++++++++++++++++++ ...s__flake8_bandit__tests__S601_S601.py.snap | 12 +++++++ ruff.schema.json | 1 + 9 files changed, 55 insertions(+) create mode 100644 crates/ruff/resources/test/fixtures/flake8_bandit/S601.py create mode 100644 crates/ruff/src/rules/flake8_bandit/rules/paramiko_calls.rs create mode 100644 crates/ruff/src/rules/flake8_bandit/snapshots/ruff__rules__flake8_bandit__tests__S601_S601.py.snap diff --git a/crates/ruff/resources/test/fixtures/flake8_bandit/S601.py b/crates/ruff/resources/test/fixtures/flake8_bandit/S601.py new file mode 100644 index 0000000000..1a76018616 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_bandit/S601.py @@ -0,0 +1,3 @@ +import paramiko + +paramiko.exec_command('something; really; unsafe') diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index c6fa2d148b..d9c4de45b1 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2846,6 +2846,9 @@ where if self.settings.rules.enabled(Rule::RequestWithoutTimeout) { flake8_bandit::rules::request_without_timeout(self, func, args, keywords); } + if self.settings.rules.enabled(Rule::ParamikoCall) { + flake8_bandit::rules::paramiko_call(self, func); + } if self .settings .rules diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 064fd722f4..6af2638445 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -507,6 +507,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Bandit, "506") => (RuleGroup::Unspecified, Rule::UnsafeYAMLLoad), (Flake8Bandit, "508") => (RuleGroup::Unspecified, Rule::SnmpInsecureVersion), (Flake8Bandit, "509") => (RuleGroup::Unspecified, Rule::SnmpWeakCryptography), + (Flake8Bandit, "601") => (RuleGroup::Unspecified, Rule::ParamikoCall), (Flake8Bandit, "602") => (RuleGroup::Unspecified, Rule::SubprocessPopenWithShellEqualsTrue), (Flake8Bandit, "603") => (RuleGroup::Unspecified, Rule::SubprocessWithoutShellEqualsTrue), (Flake8Bandit, "604") => (RuleGroup::Unspecified, Rule::CallWithShellEqualsTrue), diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 2de02bde30..bf0a3b6410 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -422,6 +422,7 @@ ruff_macros::register_rules!( rules::flake8_bandit::rules::HardcodedTempFile, rules::flake8_bandit::rules::HashlibInsecureHashFunction, rules::flake8_bandit::rules::Jinja2AutoescapeFalse, + rules::flake8_bandit::rules::ParamikoCall, rules::flake8_bandit::rules::LoggingConfigInsecureListen, rules::flake8_bandit::rules::RequestWithNoCertValidation, rules::flake8_bandit::rules::RequestWithoutTimeout, diff --git a/crates/ruff/src/rules/flake8_bandit/mod.rs b/crates/ruff/src/rules/flake8_bandit/mod.rs index 91cd61a150..6cfa043884 100644 --- a/crates/ruff/src/rules/flake8_bandit/mod.rs +++ b/crates/ruff/src/rules/flake8_bandit/mod.rs @@ -43,6 +43,7 @@ mod tests { #[test_case(Rule::TryExceptContinue, Path::new("S112.py"); "S112")] #[test_case(Rule::TryExceptPass, Path::new("S110.py"); "S110")] #[test_case(Rule::UnsafeYAMLLoad, Path::new("S506.py"); "S506")] + #[test_case(Rule::ParamikoCall, Path::new("S601.py"); "S601")] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff/src/rules/flake8_bandit/rules/mod.rs b/crates/ruff/src/rules/flake8_bandit/rules/mod.rs index a7a3f4f92e..5653a946e6 100644 --- a/crates/ruff/src/rules/flake8_bandit/rules/mod.rs +++ b/crates/ruff/src/rules/flake8_bandit/rules/mod.rs @@ -20,6 +20,7 @@ pub(crate) use jinja2_autoescape_false::{jinja2_autoescape_false, Jinja2Autoesca pub(crate) use logging_config_insecure_listen::{ logging_config_insecure_listen, LoggingConfigInsecureListen, }; +pub(crate) use paramiko_calls::{paramiko_call, ParamikoCall}; pub(crate) use request_with_no_cert_validation::{ request_with_no_cert_validation, RequestWithNoCertValidation, }; @@ -57,6 +58,7 @@ mod hardcoded_tmp_directory; mod hashlib_insecure_hash_functions; mod jinja2_autoescape_false; mod logging_config_insecure_listen; +mod paramiko_calls; mod request_with_no_cert_validation; mod request_without_timeout; mod shell_injection; diff --git a/crates/ruff/src/rules/flake8_bandit/rules/paramiko_calls.rs b/crates/ruff/src/rules/flake8_bandit/rules/paramiko_calls.rs new file mode 100644 index 0000000000..09995a400a --- /dev/null +++ b/crates/ruff/src/rules/flake8_bandit/rules/paramiko_calls.rs @@ -0,0 +1,31 @@ +use rustpython_parser::ast::{Expr, Ranged}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; + +use crate::checkers::ast::Checker; + +#[violation] +pub struct ParamikoCall; + +impl Violation for ParamikoCall { + #[derive_message_formats] + fn message(&self) -> String { + format!("Possible shell injection via Paramiko call; check inputs are properly sanitized") + } +} + +/// S601 +pub(crate) fn paramiko_call(checker: &mut Checker, func: &Expr) { + if checker + .ctx + .resolve_call_path(func) + .map_or(false, |call_path| { + call_path.as_slice() == ["paramiko", "exec_command"] + }) + { + checker + .diagnostics + .push(Diagnostic::new(ParamikoCall, func.range())); + } +} diff --git a/crates/ruff/src/rules/flake8_bandit/snapshots/ruff__rules__flake8_bandit__tests__S601_S601.py.snap b/crates/ruff/src/rules/flake8_bandit/snapshots/ruff__rules__flake8_bandit__tests__S601_S601.py.snap new file mode 100644 index 0000000000..8e11bc8541 --- /dev/null +++ b/crates/ruff/src/rules/flake8_bandit/snapshots/ruff__rules__flake8_bandit__tests__S601_S601.py.snap @@ -0,0 +1,12 @@ +--- +source: crates/ruff/src/rules/flake8_bandit/mod.rs +--- +S601.py:3:1: S601 Possible shell injection via Paramiko call; check inputs are properly sanitized + | +3 | import paramiko +4 | +5 | paramiko.exec_command('something; really; unsafe') + | ^^^^^^^^^^^^^^^^^^^^^ S601 + | + + diff --git a/ruff.schema.json b/ruff.schema.json index 576d09a765..18ed2c26f8 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2257,6 +2257,7 @@ "S509", "S6", "S60", + "S601", "S602", "S603", "S604",