Skip to content

Commit a6a7584

Browse files
authored
Implement flake8-bandit shell injection rules (astral-sh#3924)
1 parent ffac4f6 commit a6a7584

20 files changed

Lines changed: 1320 additions & 0 deletions

_typos.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ extend-exclude = ["snapshots", "black"]
55
trivias = "trivias"
66
hel = "hel"
77
whos = "whos"
8+
spawnve = "spawnve"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from subprocess import Popen, call, check_call, check_output, run
2+
3+
# Check different Popen wrappers are checked.
4+
Popen("true", shell=True)
5+
call("true", shell=True)
6+
check_call("true", shell=True)
7+
check_output("true", shell=True)
8+
run("true", shell=True)
9+
10+
# Check values that truthy values are treated as true.
11+
Popen("true", shell=1)
12+
Popen("true", shell=[1])
13+
Popen("true", shell={1: 1})
14+
Popen("true", shell=(1,))
15+
16+
# Check command argument looks unsafe.
17+
var_string = "true"
18+
Popen(var_string, shell=True)
19+
Popen([var_string], shell=True)
20+
Popen([var_string, ""], shell=True)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from subprocess import Popen, call, check_call, check_output, run
2+
3+
# Different Popen wrappers are checked.
4+
Popen("true", shell=False)
5+
call("true", shell=False)
6+
check_call("true", shell=False)
7+
check_output("true", shell=False)
8+
run("true", shell=False)
9+
10+
# Values that falsey values are treated as false.
11+
Popen("true", shell=0)
12+
Popen("true", shell=[])
13+
Popen("true", shell={})
14+
Popen("true", shell=None)
15+
16+
# Unknown values are treated as falsey.
17+
Popen("true", shell=True if True else False)
18+
19+
# No value is also caught.
20+
Popen("true")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def foo(shell):
2+
pass
3+
4+
5+
foo(shell=True)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import os
2+
3+
import commands
4+
import popen2
5+
6+
# Check all shell functions.
7+
os.system("true")
8+
os.popen("true")
9+
os.popen2("true")
10+
os.popen3("true")
11+
os.popen4("true")
12+
popen2.popen2("true")
13+
popen2.popen3("true")
14+
popen2.popen4("true")
15+
popen2.Popen3("true")
16+
popen2.Popen4("true")
17+
commands.getoutput("true")
18+
commands.getstatusoutput("true")
19+
20+
21+
# Check command argument looks unsafe.
22+
var_string = "true"
23+
os.system(var_string)
24+
os.system([var_string])
25+
os.system([var_string, ""])
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
3+
# Check all shell functions.
4+
os.execl("true")
5+
os.execle("true")
6+
os.execlp("true")
7+
os.execlpe("true")
8+
os.execv("true")
9+
os.execve("true")
10+
os.execvp("true")
11+
os.execvpe("true")
12+
os.spawnl("true")
13+
os.spawnle("true")
14+
os.spawnlp("true")
15+
os.spawnlpe("true")
16+
os.spawnv("true")
17+
os.spawnve("true")
18+
os.spawnvp("true")
19+
os.spawnvpe("true")
20+
os.startfile("true")
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
3+
# Check all functions.
4+
subprocess.Popen("true")
5+
subprocess.call("true")
6+
subprocess.check_call("true")
7+
subprocess.check_output("true")
8+
subprocess.run("true")
9+
os.system("true")
10+
os.popen("true")
11+
os.popen2("true")
12+
os.popen3("true")
13+
os.popen4("true")
14+
popen2.popen2("true")
15+
popen2.popen3("true")
16+
popen2.popen4("true")
17+
popen2.Popen3("true")
18+
popen2.Popen4("true")
19+
commands.getoutput("true")
20+
commands.getstatusoutput("true")
21+
os.execl("true")
22+
os.execle("true")
23+
os.execlp("true")
24+
os.execlpe("true")
25+
os.execv("true")
26+
os.execve("true")
27+
os.execvp("true")
28+
os.execvpe("true")
29+
os.spawnl("true")
30+
os.spawnle("true")
31+
os.spawnlp("true")
32+
os.spawnlpe("true")
33+
os.spawnv("true")
34+
os.spawnve("true")
35+
os.spawnvp("true")
36+
os.spawnvpe("true")
37+
os.startfile("true")
38+
39+
# Check it does not fail for full paths.
40+
os.system("/bin/ls")
41+
os.system("./bin/ls")
42+
os.system(["/bin/ls"])
43+
os.system(["/bin/ls", "/tmp"])
44+
os.system(r"C:\\bin\ls")

crates/ruff/src/checkers/ast/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2651,6 +2651,16 @@ where
26512651
self, func, args, keywords,
26522652
);
26532653
}
2654+
if self.settings.rules.any_enabled(&[
2655+
Rule::SubprocessWithoutShellEqualsTrue,
2656+
Rule::SubprocessPopenWithShellEqualsTrue,
2657+
Rule::CallWithShellEqualsTrue,
2658+
Rule::StartProcessWithAShell,
2659+
Rule::StartProcessWithNoShell,
2660+
Rule::StartProcessWithPartialPath,
2661+
]) {
2662+
flake8_bandit::rules::shell_injection(self, func, args, keywords);
2663+
}
26542664

26552665
// flake8-comprehensions
26562666
if self.settings.rules.enabled(Rule::UnnecessaryGeneratorList) {

crates/ruff/src/codes.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,12 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
507507
(Flake8Bandit, "506") => Rule::UnsafeYAMLLoad,
508508
(Flake8Bandit, "508") => Rule::SnmpInsecureVersion,
509509
(Flake8Bandit, "509") => Rule::SnmpWeakCryptography,
510+
(Flake8Bandit, "602") => Rule::SubprocessPopenWithShellEqualsTrue,
511+
(Flake8Bandit, "603") => Rule::SubprocessWithoutShellEqualsTrue,
512+
(Flake8Bandit, "604") => Rule::CallWithShellEqualsTrue,
513+
(Flake8Bandit, "605") => Rule::StartProcessWithAShell,
514+
(Flake8Bandit, "606") => Rule::StartProcessWithNoShell,
515+
(Flake8Bandit, "607") => Rule::StartProcessWithPartialPath,
510516
(Flake8Bandit, "608") => Rule::HardcodedSQLExpression,
511517
(Flake8Bandit, "612") => Rule::LoggingConfigInsecureListen,
512518
(Flake8Bandit, "701") => Rule::Jinja2AutoescapeFalse,

crates/ruff/src/registry.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,12 @@ ruff_macros::register_rules!(
446446
rules::flake8_bandit::rules::RequestWithoutTimeout,
447447
rules::flake8_bandit::rules::SnmpInsecureVersion,
448448
rules::flake8_bandit::rules::SnmpWeakCryptography,
449+
rules::flake8_bandit::rules::SubprocessPopenWithShellEqualsTrue,
450+
rules::flake8_bandit::rules::SubprocessWithoutShellEqualsTrue,
451+
rules::flake8_bandit::rules::CallWithShellEqualsTrue,
452+
rules::flake8_bandit::rules::StartProcessWithAShell,
453+
rules::flake8_bandit::rules::StartProcessWithNoShell,
454+
rules::flake8_bandit::rules::StartProcessWithPartialPath,
449455
rules::flake8_bandit::rules::SuspiciousEvalUsage,
450456
rules::flake8_bandit::rules::SuspiciousFTPLibUsage,
451457
rules::flake8_bandit::rules::SuspiciousInsecureCipherUsage,

0 commit comments

Comments
 (0)