diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S508.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S508.py index cf87d7ad8e..6f9c672ea5 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S508.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S508.py @@ -4,3 +4,31 @@ CommunityData("public", mpModel=0) # S508 CommunityData("public", mpModel=1) # S508 CommunityData("public", mpModel=2) # OK + +# New API paths +import pysnmp.hlapi.asyncio +import pysnmp.hlapi.v1arch +import pysnmp.hlapi.v1arch.asyncio +import pysnmp.hlapi.v1arch.asyncio.auth +import pysnmp.hlapi.v3arch +import pysnmp.hlapi.v3arch.asyncio +import pysnmp.hlapi.v3arch.asyncio.auth +import pysnmp.hlapi.auth + +pysnmp.hlapi.asyncio.CommunityData("public", mpModel=0) # S508 +pysnmp.hlapi.v1arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 +pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508 +pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508 +pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 +pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508 +pysnmp.hlapi.v3arch.CommunityData("public", mpModel=0) # S508 +pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508 + +pysnmp.hlapi.asyncio.CommunityData("public", mpModel=2) # OK +pysnmp.hlapi.v1arch.asyncio.auth.CommunityData("public", mpModel=2) # OK +pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=2) # OK +pysnmp.hlapi.v1arch.CommunityData("public", mpModel=2) # OK +pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=2) # OK +pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=2) # OK +pysnmp.hlapi.v3arch.CommunityData("public", mpModel=2) # OK +pysnmp.hlapi.auth.CommunityData("public", mpModel=2) # OK diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S509.py b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S509.py index 618f7a1b78..9db0d98dda 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S509.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bandit/S509.py @@ -5,3 +5,19 @@ insecure = UsmUserData("securityName") # S509 auth_no_priv = UsmUserData("securityName", "authName") # S509 less_insecure = UsmUserData("securityName", "authName", "privName") # OK + +# New API paths +import pysnmp.hlapi.asyncio +import pysnmp.hlapi.v3arch.asyncio +import pysnmp.hlapi.v3arch.asyncio.auth +import pysnmp.hlapi.auth + +pysnmp.hlapi.asyncio.UsmUserData("user") # S509 +pysnmp.hlapi.v3arch.asyncio.UsmUserData("user") # S509 +pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user") # S509 +pysnmp.hlapi.auth.UsmUserData("user") # S509 + +pysnmp.hlapi.asyncio.UsmUserData("user", "authkey", "privkey") # OK +pysnmp.hlapi.v3arch.asyncio.UsmUserData("user", "authkey", "privkey") # OK +pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user", "authkey", "privkey") # OK +pysnmp.hlapi.auth.UsmUserData("user", "authkey", "privkey") # OK diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 239466f599..c49af75fab 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -270,6 +270,11 @@ pub(crate) const fn is_extended_i18n_function_matching_enabled(settings: &Linter settings.preview.is_enabled() } +// https://github.com/astral-sh/ruff/pull/21374 +pub(crate) const fn is_extended_snmp_api_path_detection_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + // https://github.com/astral-sh/ruff/pull/21395 pub(crate) const fn is_enumerate_for_loop_int_index_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() diff --git a/crates/ruff_linter/src/rules/flake8_bandit/mod.rs b/crates/ruff_linter/src/rules/flake8_bandit/mod.rs index 46e9a53e45..7c3bad60d1 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/mod.rs @@ -104,6 +104,8 @@ mod tests { #[test_case(Rule::SuspiciousURLOpenUsage, Path::new("S310.py"))] #[test_case(Rule::SuspiciousNonCryptographicRandomUsage, Path::new("S311.py"))] #[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"))] + #[test_case(Rule::SnmpInsecureVersion, Path::new("S508.py"))] + #[test_case(Rule::SnmpWeakCryptography, Path::new("S509.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs index b490efdfc5..8bf5830e31 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_insecure_version.rs @@ -4,6 +4,7 @@ use ruff_text_size::Ranged; use crate::Violation; use crate::checkers::ast::Checker; +use crate::preview::is_extended_snmp_api_path_detection_enabled; /// ## What it does /// Checks for uses of SNMPv1 or SNMPv2. @@ -47,10 +48,17 @@ pub(crate) fn snmp_insecure_version(checker: &Checker, call: &ast::ExprCall) { .semantic() .resolve_qualified_name(&call.func) .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - ["pysnmp", "hlapi", "CommunityData"] - ) + if is_extended_snmp_api_path_detection_enabled(checker.settings()) { + matches!( + qualified_name.segments(), + ["pysnmp", "hlapi", .., "CommunityData"] + ) + } else { + matches!( + qualified_name.segments(), + ["pysnmp", "hlapi", "CommunityData"] + ) + } }) { if let Some(keyword) = call.arguments.find_keyword("mpModel") { diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs index 9f067e2c4e..390f1bf13a 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/snmp_weak_cryptography.rs @@ -4,6 +4,7 @@ use ruff_text_size::Ranged; use crate::Violation; use crate::checkers::ast::Checker; +use crate::preview::is_extended_snmp_api_path_detection_enabled; /// ## What it does /// Checks for uses of the SNMPv3 protocol without encryption. @@ -47,10 +48,17 @@ pub(crate) fn snmp_weak_cryptography(checker: &Checker, call: &ast::ExprCall) { .semantic() .resolve_qualified_name(&call.func) .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - ["pysnmp", "hlapi", "UsmUserData"] - ) + if is_extended_snmp_api_path_detection_enabled(checker.settings()) { + matches!( + qualified_name.segments(), + ["pysnmp", "hlapi", .., "UsmUserData"] + ) + } else { + matches!( + qualified_name.segments(), + ["pysnmp", "hlapi", "UsmUserData"] + ) + } }) { checker.report_diagnostic(SnmpWeakCryptography, call.func.range()); diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S508_S508.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S508_S508.py.snap new file mode 100644 index 0000000000..b47cdd7f77 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S508_S508.py.snap @@ -0,0 +1,108 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs +--- +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:3:25 + | +1 | from pysnmp.hlapi import CommunityData +2 | +3 | CommunityData("public", mpModel=0) # S508 + | ^^^^^^^^^ +4 | CommunityData("public", mpModel=1) # S508 + | + +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:4:25 + | +3 | CommunityData("public", mpModel=0) # S508 +4 | CommunityData("public", mpModel=1) # S508 + | ^^^^^^^^^ +5 | +6 | CommunityData("public", mpModel=2) # OK + | + +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:18:46 + | +16 | import pysnmp.hlapi.auth +17 | +18 | pysnmp.hlapi.asyncio.CommunityData("public", mpModel=0) # S508 + | ^^^^^^^^^ +19 | pysnmp.hlapi.v1arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 +20 | pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508 + | + +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:19:58 + | +18 | pysnmp.hlapi.asyncio.CommunityData("public", mpModel=0) # S508 +19 | pysnmp.hlapi.v1arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 + | ^^^^^^^^^ +20 | pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508 +21 | pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508 + | + +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:20:53 + | +18 | pysnmp.hlapi.asyncio.CommunityData("public", mpModel=0) # S508 +19 | pysnmp.hlapi.v1arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 +20 | pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508 + | ^^^^^^^^^ +21 | pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508 +22 | pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 + | + +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:21:45 + | +19 | pysnmp.hlapi.v1arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 +20 | pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508 +21 | pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508 + | ^^^^^^^^^ +22 | pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 +23 | pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508 + | + +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:22:58 + | +20 | pysnmp.hlapi.v1arch.asyncio.CommunityData("public", mpModel=0) # S508 +21 | pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508 +22 | pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 + | ^^^^^^^^^ +23 | pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508 +24 | pysnmp.hlapi.v3arch.CommunityData("public", mpModel=0) # S508 + | + +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:23:53 + | +21 | pysnmp.hlapi.v1arch.CommunityData("public", mpModel=0) # S508 +22 | pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 +23 | pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508 + | ^^^^^^^^^ +24 | pysnmp.hlapi.v3arch.CommunityData("public", mpModel=0) # S508 +25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508 + | + +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:24:45 + | +22 | pysnmp.hlapi.v3arch.asyncio.auth.CommunityData("public", mpModel=0) # S508 +23 | pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508 +24 | pysnmp.hlapi.v3arch.CommunityData("public", mpModel=0) # S508 + | ^^^^^^^^^ +25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508 + | + +S508 The use of SNMPv1 and SNMPv2 is insecure. Use SNMPv3 if able. + --> S508.py:25:43 + | +23 | pysnmp.hlapi.v3arch.asyncio.CommunityData("public", mpModel=0) # S508 +24 | pysnmp.hlapi.v3arch.CommunityData("public", mpModel=0) # S508 +25 | pysnmp.hlapi.auth.CommunityData("public", mpModel=0) # S508 + | ^^^^^^^^^ +26 | +27 | pysnmp.hlapi.asyncio.CommunityData("public", mpModel=2) # OK + | diff --git a/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S509_S509.py.snap b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S509_S509.py.snap new file mode 100644 index 0000000000..da81c2a630 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bandit/snapshots/ruff_linter__rules__flake8_bandit__tests__preview__S509_S509.py.snap @@ -0,0 +1,62 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs +--- +S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. + --> S509.py:4:12 + | +4 | insecure = UsmUserData("securityName") # S509 + | ^^^^^^^^^^^ +5 | auth_no_priv = UsmUserData("securityName", "authName") # S509 + | + +S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. + --> S509.py:5:16 + | +4 | insecure = UsmUserData("securityName") # S509 +5 | auth_no_priv = UsmUserData("securityName", "authName") # S509 + | ^^^^^^^^^^^ +6 | +7 | less_insecure = UsmUserData("securityName", "authName", "privName") # OK + | + +S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. + --> S509.py:15:1 + | +13 | import pysnmp.hlapi.auth +14 | +15 | pysnmp.hlapi.asyncio.UsmUserData("user") # S509 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +16 | pysnmp.hlapi.v3arch.asyncio.UsmUserData("user") # S509 +17 | pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user") # S509 + | + +S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. + --> S509.py:16:1 + | +15 | pysnmp.hlapi.asyncio.UsmUserData("user") # S509 +16 | pysnmp.hlapi.v3arch.asyncio.UsmUserData("user") # S509 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +17 | pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user") # S509 +18 | pysnmp.hlapi.auth.UsmUserData("user") # S509 + | + +S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. + --> S509.py:17:1 + | +15 | pysnmp.hlapi.asyncio.UsmUserData("user") # S509 +16 | pysnmp.hlapi.v3arch.asyncio.UsmUserData("user") # S509 +17 | pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user") # S509 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +18 | pysnmp.hlapi.auth.UsmUserData("user") # S509 + | + +S509 You should not use SNMPv3 without encryption. `noAuthNoPriv` & `authNoPriv` is insecure. + --> S509.py:18:1 + | +16 | pysnmp.hlapi.v3arch.asyncio.UsmUserData("user") # S509 +17 | pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData("user") # S509 +18 | pysnmp.hlapi.auth.UsmUserData("user") # S509 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +19 | +20 | pysnmp.hlapi.asyncio.UsmUserData("user", "authkey", "privkey") # OK + |