mirror of https://github.com/astral-sh/ruff
[`flake8-bandit`] Add Rule for `S506` (unsafe use of yaml load) (#1664)
See: https://github.com/charliermarsh/ruff/issues/1646. Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
parent
5eb03d5e09
commit
2d23b1ae69
11
README.md
11
README.md
|
|
@ -767,11 +767,12 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on
|
||||||
| S102 | ExecUsed | Use of `exec` detected | |
|
| S102 | ExecUsed | Use of `exec` detected | |
|
||||||
| S103 | BadFilePermissions | `os.chmod` setting a permissive mask `0o777` on file or directory | |
|
| S103 | BadFilePermissions | `os.chmod` setting a permissive mask `0o777` on file or directory | |
|
||||||
| S104 | HardcodedBindAllInterfaces | Possible binding to all interfaces | |
|
| S104 | HardcodedBindAllInterfaces | Possible binding to all interfaces | |
|
||||||
| S105 | HardcodedPasswordString | Possible hardcoded password: `"..."` | |
|
| S105 | HardcodedPasswordString | Possible hardcoded password: "..." | |
|
||||||
| S106 | HardcodedPasswordFuncArg | Possible hardcoded password: `"..."` | |
|
| S106 | HardcodedPasswordFuncArg | Possible hardcoded password: "..." | |
|
||||||
| S107 | HardcodedPasswordDefault | Possible hardcoded password: `"..."` | |
|
| S107 | HardcodedPasswordDefault | Possible hardcoded password: "..." | |
|
||||||
| S108 | HardcodedTempFile | Probable insecure usage of temp file/directory: `"..."` | |
|
| S108 | HardcodedTempFile | Probable insecure usage of temporary file or directory: "..." | |
|
||||||
| S324 | HashlibInsecureHashFunction | Probable use of insecure hash functions in hashlib: `"..."` | |
|
| S324 | HashlibInsecureHashFunction | Probable use of insecure hash functions in `hashlib`: "..." | |
|
||||||
|
| S506 | UnsafeYAMLLoad | Probable use of unsafe `yaml.load`. Allows instantiation of arbitrary objects. Consider `yaml.safe_load`. | |
|
||||||
|
|
||||||
### flake8-blind-except (BLE)
|
### flake8-blind-except (BLE)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
from yaml import CSafeLoader
|
||||||
|
from yaml import SafeLoader
|
||||||
|
from yaml import SafeLoader as NewSafeLoader
|
||||||
|
|
||||||
|
|
||||||
|
def test_yaml_load():
|
||||||
|
ystr = yaml.dump({"a": 1, "b": 2, "c": 3})
|
||||||
|
y = yaml.load(ystr)
|
||||||
|
yaml.dump(y)
|
||||||
|
try:
|
||||||
|
y = yaml.load(ystr, Loader=yaml.CSafeLoader)
|
||||||
|
except AttributeError:
|
||||||
|
# CSafeLoader only exists if you build yaml with LibYAML
|
||||||
|
y = yaml.load(ystr, Loader=yaml.SafeLoader)
|
||||||
|
|
||||||
|
|
||||||
|
def test_json_load():
|
||||||
|
# no issue should be found
|
||||||
|
j = json.load("{}")
|
||||||
|
|
||||||
|
|
||||||
|
yaml.load("{}", Loader=yaml.Loader)
|
||||||
|
|
||||||
|
# no issue should be found
|
||||||
|
yaml.load("{}", SafeLoader)
|
||||||
|
yaml.load("{}", yaml.SafeLoader)
|
||||||
|
yaml.load("{}", CSafeLoader)
|
||||||
|
yaml.load("{}", yaml.CSafeLoader)
|
||||||
|
yaml.load("{}", NewSafeLoader)
|
||||||
|
|
@ -930,6 +930,9 @@
|
||||||
"S3",
|
"S3",
|
||||||
"S32",
|
"S32",
|
||||||
"S324",
|
"S324",
|
||||||
|
"S5",
|
||||||
|
"S50",
|
||||||
|
"S506",
|
||||||
"SIM",
|
"SIM",
|
||||||
"SIM1",
|
"SIM1",
|
||||||
"SIM10",
|
"SIM10",
|
||||||
|
|
|
||||||
|
|
@ -1896,6 +1896,17 @@ where
|
||||||
self.add_check(check);
|
self.add_check(check);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.settings.enabled.contains(&CheckCode::S506) {
|
||||||
|
if let Some(check) = flake8_bandit::checks::unsafe_yaml_load(
|
||||||
|
func,
|
||||||
|
args,
|
||||||
|
keywords,
|
||||||
|
&self.from_imports,
|
||||||
|
&self.import_aliases,
|
||||||
|
) {
|
||||||
|
self.add_check(check);
|
||||||
|
}
|
||||||
|
}
|
||||||
if self.settings.enabled.contains(&CheckCode::S106) {
|
if self.settings.enabled.contains(&CheckCode::S106) {
|
||||||
self.add_checks(
|
self.add_checks(
|
||||||
flake8_bandit::checks::hardcoded_password_func_arg(keywords).into_iter(),
|
flake8_bandit::checks::hardcoded_password_func_arg(keywords).into_iter(),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ pub use hardcoded_password_string::{
|
||||||
};
|
};
|
||||||
pub use hardcoded_tmp_directory::hardcoded_tmp_directory;
|
pub use hardcoded_tmp_directory::hardcoded_tmp_directory;
|
||||||
pub use hashlib_insecure_hash_functions::hashlib_insecure_hash_functions;
|
pub use hashlib_insecure_hash_functions::hashlib_insecure_hash_functions;
|
||||||
|
pub use unsafe_yaml_load::unsafe_yaml_load;
|
||||||
|
|
||||||
mod assert_used;
|
mod assert_used;
|
||||||
mod bad_file_permissions;
|
mod bad_file_permissions;
|
||||||
|
|
@ -19,3 +20,4 @@ mod hardcoded_password_func_arg;
|
||||||
mod hardcoded_password_string;
|
mod hardcoded_password_string;
|
||||||
mod hardcoded_tmp_directory;
|
mod hardcoded_tmp_directory;
|
||||||
mod hashlib_insecure_hash_functions;
|
mod hashlib_insecure_hash_functions;
|
||||||
|
mod unsafe_yaml_load;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
use rustpython_ast::{Expr, ExprKind, Keyword};
|
||||||
|
|
||||||
|
use crate::ast::helpers::{match_module_member, SimpleCallArgs};
|
||||||
|
use crate::ast::types::Range;
|
||||||
|
use crate::registry::{Check, CheckKind};
|
||||||
|
|
||||||
|
/// S506
|
||||||
|
pub fn unsafe_yaml_load(
|
||||||
|
func: &Expr,
|
||||||
|
args: &[Expr],
|
||||||
|
keywords: &[Keyword],
|
||||||
|
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
|
||||||
|
import_aliases: &FxHashMap<&str, &str>,
|
||||||
|
) -> Option<Check> {
|
||||||
|
if match_module_member(func, "yaml", "load", from_imports, import_aliases) {
|
||||||
|
let call_args = SimpleCallArgs::new(args, keywords);
|
||||||
|
if let Some(loader_arg) = call_args.get_argument("Loader", Some(1)) {
|
||||||
|
if !match_module_member(
|
||||||
|
loader_arg,
|
||||||
|
"yaml",
|
||||||
|
"SafeLoader",
|
||||||
|
from_imports,
|
||||||
|
import_aliases,
|
||||||
|
) && !match_module_member(
|
||||||
|
loader_arg,
|
||||||
|
"yaml",
|
||||||
|
"CSafeLoader",
|
||||||
|
from_imports,
|
||||||
|
import_aliases,
|
||||||
|
) {
|
||||||
|
let loader = match &loader_arg.node {
|
||||||
|
ExprKind::Attribute { attr, .. } => Some(attr.to_string()),
|
||||||
|
ExprKind::Name { id, .. } => Some(id.to_string()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
return Some(Check::new(
|
||||||
|
CheckKind::UnsafeYAMLLoad(loader),
|
||||||
|
Range::from_located(loader_arg),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Some(Check::new(
|
||||||
|
CheckKind::UnsafeYAMLLoad(None),
|
||||||
|
Range::from_located(func),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ mod tests {
|
||||||
#[test_case(CheckCode::S107, Path::new("S107.py"); "S107")]
|
#[test_case(CheckCode::S107, Path::new("S107.py"); "S107")]
|
||||||
#[test_case(CheckCode::S108, Path::new("S108.py"); "S108")]
|
#[test_case(CheckCode::S108, Path::new("S108.py"); "S108")]
|
||||||
#[test_case(CheckCode::S324, Path::new("S324.py"); "S324")]
|
#[test_case(CheckCode::S324, Path::new("S324.py"); "S324")]
|
||||||
|
#[test_case(CheckCode::S506, Path::new("S506.py"); "S506")]
|
||||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||||
let checks = test_path(
|
let checks = test_path(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
source: src/flake8_bandit/mod.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
UnsafeYAMLLoad: ~
|
||||||
|
location:
|
||||||
|
row: 10
|
||||||
|
column: 8
|
||||||
|
end_location:
|
||||||
|
row: 10
|
||||||
|
column: 17
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
UnsafeYAMLLoad: Loader
|
||||||
|
location:
|
||||||
|
row: 24
|
||||||
|
column: 23
|
||||||
|
end_location:
|
||||||
|
row: 24
|
||||||
|
column: 34
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
@ -335,6 +335,7 @@ pub enum CheckCode {
|
||||||
S107,
|
S107,
|
||||||
S108,
|
S108,
|
||||||
S324,
|
S324,
|
||||||
|
S506,
|
||||||
// flake8-boolean-trap
|
// flake8-boolean-trap
|
||||||
FBT001,
|
FBT001,
|
||||||
FBT002,
|
FBT002,
|
||||||
|
|
@ -1075,6 +1076,7 @@ pub enum CheckKind {
|
||||||
HardcodedPasswordDefault(String),
|
HardcodedPasswordDefault(String),
|
||||||
HardcodedTempFile(String),
|
HardcodedTempFile(String),
|
||||||
HashlibInsecureHashFunction(String),
|
HashlibInsecureHashFunction(String),
|
||||||
|
UnsafeYAMLLoad(Option<String>),
|
||||||
// mccabe
|
// mccabe
|
||||||
FunctionIsTooComplex(String, usize),
|
FunctionIsTooComplex(String, usize),
|
||||||
// flake8-boolean-trap
|
// flake8-boolean-trap
|
||||||
|
|
@ -1538,6 +1540,7 @@ impl CheckCode {
|
||||||
CheckCode::S107 => CheckKind::HardcodedPasswordDefault("...".to_string()),
|
CheckCode::S107 => CheckKind::HardcodedPasswordDefault("...".to_string()),
|
||||||
CheckCode::S108 => CheckKind::HardcodedTempFile("...".to_string()),
|
CheckCode::S108 => CheckKind::HardcodedTempFile("...".to_string()),
|
||||||
CheckCode::S324 => CheckKind::HashlibInsecureHashFunction("...".to_string()),
|
CheckCode::S324 => CheckKind::HashlibInsecureHashFunction("...".to_string()),
|
||||||
|
CheckCode::S506 => CheckKind::UnsafeYAMLLoad(None),
|
||||||
// mccabe
|
// mccabe
|
||||||
CheckCode::C901 => CheckKind::FunctionIsTooComplex("...".to_string(), 10),
|
CheckCode::C901 => CheckKind::FunctionIsTooComplex("...".to_string(), 10),
|
||||||
// flake8-boolean-trap
|
// flake8-boolean-trap
|
||||||
|
|
@ -1941,6 +1944,7 @@ impl CheckCode {
|
||||||
CheckCode::S107 => CheckCategory::Flake8Bandit,
|
CheckCode::S107 => CheckCategory::Flake8Bandit,
|
||||||
CheckCode::S108 => CheckCategory::Flake8Bandit,
|
CheckCode::S108 => CheckCategory::Flake8Bandit,
|
||||||
CheckCode::S324 => CheckCategory::Flake8Bandit,
|
CheckCode::S324 => CheckCategory::Flake8Bandit,
|
||||||
|
CheckCode::S506 => CheckCategory::Flake8Bandit,
|
||||||
// flake8-simplify
|
// flake8-simplify
|
||||||
CheckCode::SIM102 => CheckCategory::Flake8Simplify,
|
CheckCode::SIM102 => CheckCategory::Flake8Simplify,
|
||||||
CheckCode::SIM105 => CheckCategory::Flake8Simplify,
|
CheckCode::SIM105 => CheckCategory::Flake8Simplify,
|
||||||
|
|
@ -2317,6 +2321,7 @@ impl CheckKind {
|
||||||
CheckKind::HardcodedPasswordDefault(..) => &CheckCode::S107,
|
CheckKind::HardcodedPasswordDefault(..) => &CheckCode::S107,
|
||||||
CheckKind::HardcodedTempFile(..) => &CheckCode::S108,
|
CheckKind::HardcodedTempFile(..) => &CheckCode::S108,
|
||||||
CheckKind::HashlibInsecureHashFunction(..) => &CheckCode::S324,
|
CheckKind::HashlibInsecureHashFunction(..) => &CheckCode::S324,
|
||||||
|
CheckKind::UnsafeYAMLLoad(..) => &CheckCode::S506,
|
||||||
// mccabe
|
// mccabe
|
||||||
CheckKind::FunctionIsTooComplex(..) => &CheckCode::C901,
|
CheckKind::FunctionIsTooComplex(..) => &CheckCode::C901,
|
||||||
// flake8-boolean-trap
|
// flake8-boolean-trap
|
||||||
|
|
@ -3260,23 +3265,31 @@ impl CheckKind {
|
||||||
CheckKind::HardcodedPasswordString(string)
|
CheckKind::HardcodedPasswordString(string)
|
||||||
| CheckKind::HardcodedPasswordFuncArg(string)
|
| CheckKind::HardcodedPasswordFuncArg(string)
|
||||||
| CheckKind::HardcodedPasswordDefault(string) => {
|
| CheckKind::HardcodedPasswordDefault(string) => {
|
||||||
format!(
|
format!("Possible hardcoded password: \"{}\"", string.escape_debug())
|
||||||
"Possible hardcoded password: `\"{}\"`",
|
|
||||||
string.escape_debug()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
CheckKind::HardcodedTempFile(string) => {
|
CheckKind::HardcodedTempFile(string) => {
|
||||||
format!(
|
format!(
|
||||||
"Probable insecure usage of temp file/directory: `\"{}\"`",
|
"Probable insecure usage of temporary file or directory: \"{}\"",
|
||||||
string.escape_debug()
|
string.escape_debug()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CheckKind::HashlibInsecureHashFunction(string) => {
|
CheckKind::HashlibInsecureHashFunction(string) => {
|
||||||
format!(
|
format!(
|
||||||
"Probable use of insecure hash functions in hashlib: `\"{}\"`",
|
"Probable use of insecure hash functions in `hashlib`: \"{}\"",
|
||||||
string.escape_debug()
|
string.escape_debug()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
CheckKind::UnsafeYAMLLoad(loader) => match loader {
|
||||||
|
Some(name) => {
|
||||||
|
format!(
|
||||||
|
"Probable use of unsafe loader `{name}` with `yaml.load`. Allows \
|
||||||
|
instantiation of arbitrary objects. Consider `yaml.safe_load`."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => "Probable use of unsafe `yaml.load`. Allows instantiation of arbitrary \
|
||||||
|
objects. Consider `yaml.safe_load`."
|
||||||
|
.to_string(),
|
||||||
|
},
|
||||||
// flake8-blind-except
|
// flake8-blind-except
|
||||||
CheckKind::BlindExcept(name) => format!("Do not catch blind exception: `{name}`"),
|
CheckKind::BlindExcept(name) => format!("Do not catch blind exception: `{name}`"),
|
||||||
// mccabe
|
// mccabe
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue