@@ -95,19 +95,34 @@ def _is_builtin_open_for_writing(node):
9595 return False
9696
9797
98- def _is_subprocess_shell_true_call (node ):
99- if (
100- isinstance (node .func , astroid .Attribute )
101- and isinstance (node .func .expr , astroid .Name )
102- and node .func .expr .name in ('subprocess' , 'sp' )
103- ):
104- if node .keywords :
105- for keyword in node .keywords :
106- if keyword .arg == 'shell' and isinstance (keyword .value , astroid .Const ) and bool (keyword .value .value ):
107- return True
98+ def _is_shell_true_call (node ):
99+ if not (isinstance (node .func , astroid .Attribute ) and isinstance (node .func .expr , astroid .Name )):
100+ return False
101+
102+ # subprocess module
103+ if node .func .expr .name in ('subprocess' , 'sp' ):
104+ if node .func .attrname in ('call' , 'check_call' , 'check_output' , 'Popen' , 'run' ):
105+ if node .keywords :
106+ for keyword in node .keywords :
107+ if (
108+ keyword .arg == 'shell'
109+ and isinstance (keyword .value , astroid .Const )
110+ and bool (keyword .value .value )
111+ ):
112+ return True
108113
109- if len (node .args ) > 8 and isinstance (node .args [8 ], astroid .Const ) and bool (node .args [8 ].value ):
114+ if len (node .args ) > 8 and isinstance (node .args [8 ], astroid .Const ) and bool (node .args [8 ].value ):
115+ return True
116+
117+ if node .func .attrname in ('getoutput' , 'getstatusoutput' ):
110118 return True
119+
120+ # asyncio module
121+ if (node .func .expr .name == 'asyncio' and node .func .attrname == 'create_subprocess_shell' ) or (
122+ node .func .expr .name == 'loop' and node .func .attrname == 'subprocess_shell'
123+ ):
124+ return True
125+
111126 return False
112127
113128
@@ -229,9 +244,14 @@ class SecureCodingStandardChecker(BaseChecker):
229244 ),
230245 'E8002' : ('Avoid using `os.sytem()`' , 'avoid-os-system' , 'Use of `os.system()` should be avoided' ),
231246 'E8003' : (
232- 'Avoid using `shell=True` when calling `subprocess` functions' ,
247+ 'Avoid using `shell=True` when calling `subprocess` functions and avoid functions that internally call it ' ,
233248 'avoid-shell-true' ,
234- 'Use of `shell=True` in subprocess functions should be avoided' ,
249+ ' ' .join (
250+ [
251+ 'Use of `shell=True` in subprocess functions or use of functions that internally set it should be'
252+ 'should be avoided' ,
253+ ]
254+ ),
235255 ),
236256 'R8004' : (
237257 'Avoid using `tempfile.mktemp()`, prefer `tempfile.mkstemp()` instead' ,
@@ -292,7 +312,7 @@ def visit_call(self, node):
292312 self .add_message ('avoid-os-system' , node = node )
293313 elif _is_os_path_call (node ):
294314 self .add_message ('replace-os-relpath-abspath' , node = node )
295- elif _is_subprocess_shell_true_call (node ):
315+ elif _is_shell_true_call (node ):
296316 self .add_message ('avoid-shell-true' , node = node )
297317 elif _is_os_popen_call (node ):
298318 self .add_message ('avoid-os-popen' , node = node )
@@ -320,6 +340,11 @@ def visit_importfrom(self, node):
320340 self .add_message ('replace-mktemp' , node = node )
321341 elif node .modname in ('os.path' , 'op' ) and [name for (name , _ ) in node .names if name in ('relpath' , 'abspath' )]:
322342 self .add_message ('replace-os-relpath-abspath' , node = node )
343+ elif (
344+ node .modname == 'subprocess'
345+ and [name for (name , _ ) in node .names if name in ('getoutput' , 'getstatusoutput' )]
346+ ) or ((node .modname == 'asyncio' and [name for (name , _ ) in node .names if name == 'create_subprocess_shell' ])):
347+ self .add_message ('avoid-shell-true' , node = node )
323348 elif node .modname == 'os' and [name for (name , _ ) in node .names if name == 'system' ]:
324349 self .add_message ('avoid-os-system' , node = node )
325350 elif node .modname == 'os' and [name for (name , _ ) in node .names if name == 'popen' ]:
0 commit comments