diff --git a/README.rst b/README.rst index f10fe78b..482ef233 100644 --- a/README.rst +++ b/README.rst @@ -73,6 +73,11 @@ using only the plugins listed in the ``ShellInjection`` profile:: bandit examples/*.py -p ShellInjection +Bandit also supports passing lines of code to scan using standard input. To +run Bandit with standard input:: + + cat examples/imports.py | bandit - + Usage:: $ bandit -h diff --git a/bandit/core/manager.py b/bandit/core/manager.py index de94651f..b3dbd541 100644 --- a/bandit/core/manager.py +++ b/bandit/core/manager.py @@ -222,48 +222,12 @@ class BanditManager(): sys.stderr.write("%s.. " % count) sys.stderr.flush() try: - with open(fname, 'rb') as fdata: - try: - # parse the current file - data = fdata.read() - lines = data.splitlines() - self.metrics.begin(fname) - self.metrics.count_locs(lines) - if self.ignore_nosec: - nosec_lines = set() - else: - nosec_lines = set( - lineno + 1 for - (lineno, line) in enumerate(lines) - if b'#nosec' in line or b'# nosec' in line) - score = self._execute_ast_visitor(fname, data, - nosec_lines) - self.scores.append(score) - self.metrics.count_issues([score, ]) - except KeyboardInterrupt as e: - sys.exit(2) - except SyntaxError as e: - self.skipped.append(( - fname, - "syntax error while parsing AST from file" - )) - new_files_list.remove(fname) - except Exception as e: - LOG.error( - "Exception occurred when executing tests against " - "%s. Run \"bandit --debug %s\" to see the full " - "traceback.", fname, fname - ) - self.skipped.append( - (fname, 'exception while scanning file') - ) - new_files_list.remove(fname) - LOG.debug(" Exception string: %s", e) - LOG.debug( - " Exception traceback: %s", - traceback.format_exc() - ) - continue + if fname == '-': + sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) + self._parse_file('', sys.stdin, new_files_list) + else: + with open(fname, 'rb') as fdata: + self._parse_file(fname, fdata, new_files_list) except IOError as e: self.skipped.append((fname, e.strerror)) new_files_list.remove(fname) @@ -278,6 +242,38 @@ class BanditManager(): # do final aggregation of metrics self.metrics.aggregate() + def _parse_file(self, fname, fdata, new_files_list): + try: + # parse the current file + data = fdata.read() + lines = data.splitlines() + self.metrics.begin(fname) + self.metrics.count_locs(lines) + if self.ignore_nosec: + nosec_lines = set() + else: + nosec_lines = set( + lineno + 1 for + (lineno, line) in enumerate(lines) + if b'#nosec' in line or b'# nosec' in line) + score = self._execute_ast_visitor(fname, data, nosec_lines) + self.scores.append(score) + self.metrics.count_issues([score, ]) + except KeyboardInterrupt as e: + sys.exit(2) + except SyntaxError as e: + self.skipped.append((fname, + "syntax error while parsing AST from file")) + new_files_list.remove(fname) + except Exception as e: + LOG.error("Exception occurred when executing tests against " + "%s. Run \"bandit --debug %s\" to see the full " + "traceback.", fname, fname) + self.skipped.append((fname, 'exception while scanning file')) + new_files_list.remove(fname) + LOG.debug(" Exception string: %s", e) + LOG.debug(" Exception traceback: %s", traceback.format_exc()) + def _execute_ast_visitor(self, fname, data, nosec_lines): '''Execute AST parse on each file diff --git a/doc/source/man/bandit.rst b/doc/source/man/bandit.rst index 60340083..be0b993d 100644 --- a/doc/source/man/bandit.rst +++ b/doc/source/man/bandit.rst @@ -88,6 +88,11 @@ using only the plugins listed in the ShellInjection profile:: bandit examples/*.py -p ShellInjection +Bandit also supports passing lines of code to scan using standard input. To +run Bandit with standard input:: + + cat examples/imports.py | bandit - + SEE ALSO ======== diff --git a/tests/functional/test_runtime.py b/tests/functional/test_runtime.py index edf4f7e3..2fe8ff29 100644 --- a/tests/functional/test_runtime.py +++ b/tests/functional/test_runtime.py @@ -21,10 +21,10 @@ import testtools class RuntimeTests(testtools.TestCase): - def _test_runtime(self, cmdlist): + def _test_runtime(self, cmdlist, infile=None): process = subprocess.Popen( cmdlist, - stdin=subprocess.PIPE, + stdin=infile if infile else subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True @@ -38,7 +38,6 @@ class RuntimeTests(testtools.TestCase): cmdlist.append(os.path.join(os.getcwd(), 'examples', t)) return self._test_runtime(cmdlist) - # test direct execution of bandit def test_no_arguments(self): (retcode, output) = self._test_runtime(['bandit', ]) self.assertEqual(2, retcode) @@ -47,6 +46,18 @@ class RuntimeTests(testtools.TestCase): else: self.assertIn("arguments are required: targets", output) + def test_piped_input(self): + with open('examples/imports.py', 'r') as infile: + (retcode, output) = self._test_runtime(['bandit', '-'], infile) + self.assertEqual(1, retcode) + self.assertIn("Total lines of code: 4", output) + self.assertIn("Low: 2", output) + self.assertIn("High: 2", output) + self.assertIn("Files skipped (0):", output) + self.assertIn("Issue: [B403:blacklist] Consider possible", output) + self.assertIn(":2", output) + self.assertIn(":4", output) + def test_nonexistent_config(self): (retcode, output) = self._test_runtime([ 'bandit', '-c', 'nonexistent.yml', 'xx.py'