mirror of https://github.com/astral-sh/ruff
[`flake8-bandit`] Support new PySNMP API paths (`S508`, `S509`) (#21374)
## Summary Updated `S508` (snmp-insecure-version) and `S509` (snmp-weak-cryptography) rules to support both old and new PySNMP API module paths. Previously, these rules only detected the old API path `pysnmp.hlapi.*`, but now they correctly detect all PySNMP API variants including `pysnmp.hlapi.asyncio.*`, `pysnmp.hlapi.v1arch.*`, `pysnmp.hlapi.v3arch.*`, and `pysnmp.hlapi.auth.*`. Fixes #21364 ## Problem Analysis The `S508` and `S509` rules used exact pattern matching on qualified names: - `S509` only matched `["pysnmp", "hlapi", "UsmUserData"]` - `S508` only matched `["pysnmp", "hlapi", "CommunityData"]` This meant that newer PySNMP API paths were not detected, such as: - `pysnmp.hlapi.asyncio.UsmUserData` - `pysnmp.hlapi.v3arch.asyncio.UsmUserData` - `pysnmp.hlapi.v3arch.asyncio.auth.UsmUserData` - `pysnmp.hlapi.auth.UsmUserData` - Similar variants for `CommunityData` in `S508` Additionally, the old API path `pysnmp.hlapi.auth.*` was also missing from both rules. ## Approach Instead of exact pattern matching, both rules now check if: 1. The qualified name starts with `["pysnmp", "hlapi"]` 2. The qualified name ends with the target class name (`"UsmUserData"` for `S509`, `"CommunityData"` for `S508`) This flexible approach matches all PySNMP API paths without hardcoding each variant, making the rules more maintainable and future-proof. ## Test Plan Added comprehensive test cases to both `S508.py` and `S509.py` test files covering: - New API paths: `pysnmp.hlapi.asyncio.*`, `pysnmp.hlapi.v1arch.*`, `pysnmp.hlapi.v3arch.*` - Old API path: `pysnmp.hlapi.auth.*` - Both insecure and secure usage patterns All existing tests pass, and new snapshot tests were added and accepted. Manual verification confirms both rules correctly detect all PySNMP API variants. --------- Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
This commit is contained in:
parent
a8f7ccf2ca
commit
0d47334f3b
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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__{}_{}",
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
||||
|
|
@ -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
|
||||
|
|
||||
Loading…
Reference in New Issue