diff -Nru bandit-1.1.0/bandit/blacklists/calls.py bandit-1.4.0/bandit/blacklists/calls.py --- bandit-1.1.0/bandit/blacklists/calls.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/blacklists/calls.py 2017-01-06 17:33:11.000000000 +0000 @@ -19,7 +19,7 @@ Blacklist various Python calls known to be dangerous ==================================================== -This balcklist data checks for a number of Python calls known to have possible +This blacklist data checks for a number of Python calls known to have possible security implications. The following blacklist tests are run against any function calls encoutered in the scanned code base, triggered by encoutering ast.Call nodes. @@ -124,7 +124,7 @@ +------+---------------------+------------------------------------+-----------+ | ID | Name | Calls | Severity | +======+=====================+====================================+===========+ -| B308 | mark_safe | - mark_safe | Medium | +| B308 | mark_safe | - django.utils.safestring.mark_safe| Medium | +------+---------------------+------------------------------------+-----------+ B309: httpsconnection @@ -136,8 +136,7 @@ +------+---------------------+------------------------------------+-----------+ | ID | Name | Calls | Severity | +======+=====================+====================================+===========+ -| B309 | httpsconnection | - mark_safe | Medium | -| | | - httplib.HTTPSConnection | | +| B309 | httpsconnection | - httplib.HTTPSConnection | Medium | | | | - http.client.HTTPSConnection | | | | | - six.moves.http_client | | | | | .HTTPSConnection | | @@ -260,6 +259,20 @@ | B321 | ftplib | - ftplib.\* | High | +------+---------------------+------------------------------------+-----------+ +B322: input +------------ + +The input method in Python 2 will read from standard input, evaluate and +run the resulting string as python source code. This is similar, though in +many ways worse, then using eval. On Python 2, use raw_input instead, input +is safe in Python 3. + ++------+---------------------+------------------------------------+-----------+ +| ID | Name | Calls | Severity | ++======+=====================+====================================+===========+ +| B322 | input | - input | High | ++------+---------------------+------------------------------------+-----------+ + """ from bandit.blacklists import utils @@ -337,7 +350,7 @@ )) sets.append(utils.build_conf_dict( - 'mark_safe', 'B308', ['mark_safe'], + 'mark_safe', 'B308', ['django.utils.safestring.mark_safe'], 'Use of mark_safe() may expose cross-site scripting ' 'vulnerabilities and should be reviewed.' )) @@ -396,7 +409,8 @@ xml_msg = ('Using {name} to parse untrusted XML data is known to be ' 'vulnerable to XML attacks. Replace {name} with its ' - 'defusedxml equivalent function.') + 'defusedxml equivalent function or make sure ' + 'defusedxml.defuse_stdlib() is called') sets.append(utils.build_conf_dict( 'xml_bad_cElementTree', 'B313', @@ -458,7 +472,9 @@ 'lxml.etree.GlobalParserTLS', 'lxml.etree.getDefaultParser', 'lxml.etree.check_docinfo'], - xml_msg + ('Using {name} to parse untrusted XML data is known to be ' + 'vulnerable to XML attacks. Replace {name} with its ' + 'defusedxml equivalent function.') )) # end of XML tests @@ -470,4 +486,13 @@ 'HIGH' )) + sets.append(utils.build_conf_dict( + 'input', 'B322', ['input'], + 'The input method in Python 2 will read from standard input, ' + 'evaluate and run the resulting string as python source code. This ' + 'is similar, though in many ways worse, then using eval. On Python ' + '2, use raw_input instead, input is safe in Python 3.', + 'HIGH' + )) + return {'Call': sets} diff -Nru bandit-1.1.0/bandit/blacklists/imports.py bandit-1.4.0/bandit/blacklists/imports.py --- bandit-1.1.0/bandit/blacklists/imports.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/blacklists/imports.py 2017-01-06 17:33:11.000000000 +0000 @@ -77,7 +77,8 @@ ---------------------- Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package. +XML attacks. Replace vulnerable imports with the equivalent defusedxml package, +or make sure defusedxml.defuse_stdlib() is called. +------+---------------------+------------------------------------+-----------+ | ID | Name | Imports | Severity | @@ -90,7 +91,8 @@ -------------------- Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package. +XML attacks. Replace vulnerable imports with the equivalent defusedxml package, +or make sure defusedxml.defuse_stdlib() is called. +------+---------------------+------------------------------------+-----------+ | ID | Name | Imports | Severity | @@ -102,7 +104,8 @@ ---------------------- Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package. +XML attacks. Replace vulnerable imports with the equivalent defusedxml package, +or make sure defusedxml.defuse_stdlib() is called. +------+---------------------+------------------------------------+-----------+ | ID | Name | Imports | Severity | @@ -114,7 +117,8 @@ ------------------------ Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package. +XML attacks. Replace vulnerable imports with the equivalent defusedxml package, +or make sure defusedxml.defuse_stdlib() is called. +------+---------------------+------------------------------------+-----------+ @@ -127,7 +131,8 @@ ------------------------ Using various methods to parse untrusted XML data is known to be vulnerable to -XML attacks. Replace vulnerable imports with the equivalent defusedxml package. +XML attacks. Replace vulnerable imports with the equivalent defusedxml package, +or make sure defusedxml.defuse_stdlib() is called. +------+---------------------+------------------------------------+-----------+ | ID | Name | Imports | Severity | @@ -223,7 +228,11 @@ xml_msg = ('Using {name} to parse untrusted XML data is known to be ' 'vulnerable to XML attacks. Replace {name} with the equivalent ' - 'defusedxml package.') + 'defusedxml package, or make sure defusedxml.defuse_stdlib() ' + 'is called.') + lxml_msg = ('Using {name} to parse untrusted XML data is known to be ' + 'vulnerable to XML attacks. Replace {name} with the ' + 'equivalent defusedxml package.') sets.append(utils.build_conf_dict( 'import_xml_etree', 'B405', @@ -242,7 +251,7 @@ 'import_xml_pulldom', 'B409', ['xml.dom.pulldom'], xml_msg, 'LOW')) sets.append(utils.build_conf_dict( - 'import_lxml', 'B410', ['lxml'], xml_msg, 'LOW')) + 'import_lxml', 'B410', ['lxml'], lxml_msg, 'LOW')) sets.append(utils.build_conf_dict( 'import_xmlrpclib', 'B411', ['xmlrpclib'], diff -Nru bandit-1.1.0/bandit/cli/baseline.py bandit-1.4.0/bandit/cli/baseline.py --- bandit-1.1.0/bandit/cli/baseline.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/cli/baseline.py 2017-01-06 17:33:11.000000000 +0000 @@ -38,7 +38,7 @@ baseline_tmp_file = '_bandit_baseline_run.json_' current_commit = None default_output_format = 'terminal' -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) repo = None report_basename = 'bandit_baseline_result' valid_baseline_formats = ['txt', 'html', 'json'] @@ -65,17 +65,17 @@ try: commit = repo.commit() current_commit = commit.hexsha - logger.info('Got current commit: [%s]', commit.name_rev) + LOG.info('Got current commit: [%s]', commit.name_rev) commit = commit.parents[0] parent_commit = commit.hexsha - logger.info('Got parent commit: [%s]', commit.name_rev) + LOG.info('Got parent commit: [%s]', commit.name_rev) except git.GitCommandError: - logger.error("Unable to get current or parent commit") + LOG.error("Unable to get current or parent commit") sys.exit(2) except IndexError: - logger.error("Parent commit not available") + LOG.error("Parent commit not available") sys.exit(2) # #################### Run Bandit against both commits #################### @@ -99,7 +99,7 @@ for step in steps: repo.head.reset(commit=step['commit'], working_tree=True) - logger.info(step['message']) + LOG.info(step['message']) bandit_command = ['bandit'] + step['args'] @@ -113,15 +113,15 @@ output = output.decode('utf-8') # subprocess returns bytes if return_code not in [0, 1]: - logger.error("Error running command: %s\nOutput: %s\n", - bandit_args, output) + LOG.error("Error running command: %s\nOutput: %s\n", + bandit_args, output) # #################### Output and exit #################################### # print output or display message about written report if output_format == default_output_format: print(output) else: - logger.info("Successfully wrote %s", report_fname) + LOG.info("Successfully wrote %s", report_fname) # exit with the code the last Bandit run returned sys.exit(return_code) @@ -140,14 +140,14 @@ # #################### Setup logging ########################################## def init_logger(): - logger.handlers = [] + LOG.handlers = [] log_level = logging.INFO log_format_string = "[%(levelname)7s ] %(message)s" logging.captureWarnings(True) - logger.setLevel(log_level) + LOG.setLevel(log_level) handler = logging.StreamHandler(sys.stdout) handler.setFormatter(logging.Formatter(log_format_string)) - logger.addHandler(handler) + LOG.addHandler(handler) # #################### Perform initialization and validate assumptions ######## @@ -178,8 +178,7 @@ else default_output_format) if output_format == default_output_format: - logger.info("No output format specified, using %s", - default_output_format) + LOG.info("No output format specified, using %s", default_output_format) # set the report name based on the output format report_fname = "{}.{}".format(report_basename, output_format) @@ -189,33 +188,33 @@ repo = git.Repo(os.getcwd()) except git.exc.InvalidGitRepositoryError: - logger.error("Bandit baseline must be called from a git project root") + LOG.error("Bandit baseline must be called from a git project root") valid = False except git.exc.GitCommandNotFound: - logger.error("Git command not found") + LOG.error("Git command not found") valid = False else: if repo.is_dirty(): - logger.error("Current working directory is dirty and must be " - "resolved") + LOG.error("Current working directory is dirty and must be " + "resolved") valid = False # if output format is specified, we need to be able to write the report if output_format != default_output_format and os.path.exists(report_fname): - logger.error("File %s already exists, aborting", report_fname) + LOG.error("File %s already exists, aborting", report_fname) valid = False # Bandit needs to be able to create this temp file if os.path.exists(baseline_tmp_file): - logger.error("Temporary file %s needs to be removed prior to running", - baseline_tmp_file) + LOG.error("Temporary file %s needs to be removed prior to running", + baseline_tmp_file) valid = False # we must validate -o is not provided, as it will mess up Bandit baseline if '-o' in bandit_args: - logger.error("Bandit baseline must not be called with the -o option") + LOG.error("Bandit baseline must not be called with the -o option") valid = False return (output_format, repo, report_fname) if valid else (None, None, None) diff -Nru bandit-1.1.0/bandit/cli/config_generator.py bandit-1.4.0/bandit/cli/config_generator.py --- bandit-1.1.0/bandit/cli/config_generator.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/cli/config_generator.py 2017-01-06 17:33:11.000000000 +0000 @@ -25,7 +25,7 @@ from bandit.core import extension_loader PROG_NAME = 'bandit_conf_generator' -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) template = """ @@ -60,14 +60,14 @@ def init_logger(): - logger.handlers = [] + LOG.handlers = [] log_level = logging.INFO log_format_string = "[%(levelname)5s]: %(message)s" logging.captureWarnings(True) - logger.setLevel(log_level) + LOG.setLevel(log_level) handler = logging.StreamHandler(sys.stdout) handler.setFormatter(logging.Formatter(log_format_string)) - logger.addHandler(handler) + LOG.addHandler(handler) def parse_args(): @@ -102,6 +102,11 @@ action='store', default=None, type=str, help='list of test names to skip') args = parser.parse_args() + + if not args.output_file and not args.show_defaults: + parser.print_help() + parser.exit(1) + return args @@ -133,7 +138,7 @@ if args.output_file: if os.path.exists(os.path.abspath(args.output_file)): - logger.error("File %s already exists, exiting", args.output_file) + LOG.error("File %s already exists, exiting", args.output_file) sys.exit(2) try: @@ -153,9 +158,9 @@ test_list = [tpl.format(t.plugin._test_id, t.name) for t in extension_loader.MANAGER.plugins] - test_list.extend([tpl.format(k, v['name']) - for k, v in six.iteritems( - extension_loader.MANAGER.blacklist_by_id)]) + others = [tpl.format(k, v['name']) for k, v in six.iteritems( + extension_loader.MANAGER.blacklist_by_id)] + test_list.extend(others) test_list.sort() contents = template.format( @@ -167,13 +172,13 @@ f.write(contents) except IOError: - logger.error("Unable to open %s for writing", args.output_file) + LOG.error("Unable to open %s for writing", args.output_file) except Exception as e: - logger.error("Error: %s", e) + LOG.error("Error: %s", e) else: - logger.info("Successfully wrote profile: %s", args.output_file) + LOG.info("Successfully wrote profile: %s", args.output_file) return 0 diff -Nru bandit-1.1.0/bandit/cli/main.py bandit-1.4.0/bandit/cli/main.py --- bandit-1.1.0/bandit/cli/main.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/cli/main.py 2017-01-06 17:33:11.000000000 +0000 @@ -29,7 +29,7 @@ BASE_CONFIG = 'bandit.yaml' -logger = logging.getLogger() +LOG = logging.getLogger() def _init_logger(debug=False, log_format=None): @@ -38,7 +38,7 @@ :param debug: Whether to enable debug mode :return: An instantiated logging instance ''' - logger.handlers = [] + LOG.handlers = [] log_level = logging.INFO if debug: log_level = logging.DEBUG @@ -51,11 +51,11 @@ logging.captureWarnings(True) - logger.setLevel(log_level) + LOG.setLevel(log_level) handler = logging.StreamHandler(sys.stderr) handler.setFormatter(logging.Formatter(log_format_string)) - logger.addHandler(handler) - logger.debug("logging initialized") + LOG.addHandler(handler) + LOG.debug("logging initialized") def _get_options_from_ini(ini_path, target): @@ -73,15 +73,13 @@ bandit_files.append(os.path.join(root, filename)) if len(bandit_files) > 1: - logger.error('Multiple .bandit files found - scan separately or ' - 'choose one with --ini\n\t%s', - ', '.join(bandit_files)) + LOG.error('Multiple .bandit files found - scan separately or ' + 'choose one with --ini\n\t%s', ', '.join(bandit_files)) sys.exit(2) elif len(bandit_files) == 1: ini_file = bandit_files[0] - logger.info('Found project level .bandit file: %s', - bandit_files[0]) + LOG.info('Found project level .bandit file: %s', bandit_files[0]) if ini_file: return utils.parse_ini_file(ini_file) @@ -97,10 +95,10 @@ def _log_option_source(arg_val, ini_val, option_name): """It's useful to show the source of each option.""" if arg_val: - logger.info("Using command line arg for %s", option_name) + LOG.info("Using command line arg for %s", option_name) return arg_val elif ini_val: - logger.info("Using .bandit arg for %s", option_name) + LOG.info("Using .bandit arg for %s", option_name) return ini_val else: return None @@ -120,7 +118,7 @@ profile = profiles.get(profile_name) if profile is None: raise utils.ProfileNotFound(config_path, profile_name) - logger.debug("read in legacy profile '%s': %s", profile_name, profile) + LOG.debug("read in legacy profile '%s': %s", profile_name, profile) else: profile['include'] = set(config.get_option('tests') or []) profile['exclude'] = set(config.get_option('skips') or []) @@ -130,10 +128,10 @@ def _log_info(args, profile): inc = ",".join([t for t in profile['include']]) or "None" exc = ",".join([t for t in profile['exclude']]) or "None" - logger.info("profile include tests: %s", inc) - logger.info("profile exclude tests: %s", exc) - logger.info("cli include tests: %s", args.tests) - logger.info("cli exclude tests: %s", args.skips) + LOG.info("profile include tests: %s", inc) + LOG.info("profile exclude tests: %s", exc) + LOG.info("cli include tests: %s", args.tests) + LOG.info("cli exclude tests: %s", args.skips) def main(): @@ -265,7 +263,7 @@ try: b_conf = b_config.BanditConfig(config_file=args.config_file) except utils.ConfigError as e: - logger.error(e) + LOG.error(e) sys.exit(2) # Handle .bandit files in projects to pass cmdline args from file @@ -297,7 +295,7 @@ extension_mgr.validate_profile(profile) except (utils.ProfileNotFound, ValueError) as e: - logger.error(e) + LOG.error(e) sys.exit(2) b_mgr = b_manager.BanditManager(b_conf, args.agg_type, args.debug, @@ -310,32 +308,32 @@ data = bl.read() b_mgr.populate_baseline(data) except IOError: - logger.warning("Could not open baseline report: %s", args.baseline) + LOG.warning("Could not open baseline report: %s", args.baseline) sys.exit(2) if args.output_format not in baseline_formatters: - logger.warning('Baseline must be used with one of the following ' - 'formats: ' + str(baseline_formatters)) + LOG.warning('Baseline must be used with one of the following ' + 'formats: ' + str(baseline_formatters)) sys.exit(2) if args.output_format != "json": if args.config_file: - logger.info("using config: %s", args.config_file) + LOG.info("using config: %s", args.config_file) - logger.info("running on Python %d.%d.%d", sys.version_info.major, - sys.version_info.minor, sys.version_info.micro) + LOG.info("running on Python %d.%d.%d", sys.version_info.major, + sys.version_info.minor, sys.version_info.micro) # initiate file discovery step within Bandit Manager b_mgr.discover_files(args.targets, args.recursive, args.excluded_paths) if not b_mgr.b_ts.tests: - logger.error('No tests would be run, please check the profile.') + LOG.error('No tests would be run, please check the profile.') sys.exit(2) # initiate execution of tests within Bandit Manager b_mgr.run_tests() - logger.debug(b_mgr.b_ma) - logger.debug(b_mgr.metrics) + LOG.debug(b_mgr.b_ma) + LOG.debug(b_mgr.metrics) # trigger output of results by Bandit Manager sev_level = constants.RANKING[args.severity - 1] diff -Nru bandit-1.1.0/bandit/core/config.py bandit-1.4.0/bandit/core/config.py --- bandit-1.1.0/bandit/core/config.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/config.py 2017-01-06 17:33:11.000000000 +0000 @@ -24,10 +24,10 @@ from bandit.core import utils -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) -class BanditConfig(): +class BanditConfig(object): def __init__(self, config_file=None): '''Attempt to initialize a config dictionary from a yaml file. @@ -133,7 +133,7 @@ updated_profiles = {} for name, profile in six.iteritems(self.get_option('profiles') or {}): - # NOTE(tkelsey): cant use default of get() because value is + # NOTE(tkelsey): can't use default of get() because value is # sometimes explicity 'None', for example when the list if given in # yaml but not populated with any values. include = set((extman.get_plugin_id(i) or i) @@ -167,11 +167,12 @@ bad_imports_list.append(val) if bad_imports_list or bad_calls_list: - logger.warning('Legacy blacklist data found in config, ' - 'overriding data plugins') + LOG.warning('Legacy blacklist data found in config, overriding ' + 'data plugins') return bad_calls_list, bad_imports_list - def convert_legacy_blacklist_tests(self, profiles, bad_imports, bad_calls): + @staticmethod + def convert_legacy_blacklist_tests(profiles, bad_imports, bad_calls): '''Detect old blacklist tests, convert to use new builtin.''' def _clean_set(name, data): if name in data: @@ -235,9 +236,8 @@ # show deprecation message if legacy: - logger.warn("Config file '%s' contains deprecated legacy " - "config data. Please consider upgrading to " - "the new config format. The tool " - "'bandit-config-generator' can help you with " - "this. Support for legacy configs will be removed " - "in a future bandit version.", path) + LOG.warning("Config file '%s' contains deprecated legacy config " + "data. Please consider upgrading to the new config " + "format. The tool 'bandit-config-generator' can help " + "you with this. Support for legacy configs will be " + "removed in a future bandit version.", path) diff -Nru bandit-1.1.0/bandit/core/context.py bandit-1.4.0/bandit/core/context.py --- bandit-1.1.0/bandit/core/context.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/context.py 2017-01-06 17:33:11.000000000 +0000 @@ -21,7 +21,7 @@ from bandit.core import utils -class Context(): +class Context(object): def __init__(self, context_object=None): '''Initialize the class with a context, empty dict otherwise @@ -103,10 +103,8 @@ :return: A dictionary of keyword parameters for a call as strings ''' - if ( - 'call' in self._context and - hasattr(self._context['call'], 'keywords') - ): + if ('call' in self._context and + hasattr(self._context['call'], 'keywords')): return_dict = {} for li in self._context['call'].keywords: if hasattr(li.value, 'attr'): @@ -152,7 +150,7 @@ '''Get escaped value of the object. Turn the value of a string or bytes object into byte sequence with - unknown, control, and \ characters escaped. + unknown, control, and \\ characters escaped. This function should be used when looking for a known sequence in a potentially badly encoded string in the code. @@ -202,51 +200,53 @@ :return: The value of the AST literal ''' if isinstance(literal, _ast.Num): - return literal.n + literal_value = literal.n elif isinstance(literal, _ast.Str): - return literal.s + literal_value = literal.s elif isinstance(literal, _ast.List): return_list = list() for li in literal.elts: return_list.append(self._get_literal_value(li)) - return return_list + literal_value = return_list elif isinstance(literal, _ast.Tuple): return_tuple = tuple() for ti in literal.elts: return_tuple = return_tuple + (self._get_literal_value(ti),) - return return_tuple + literal_value = return_tuple elif isinstance(literal, _ast.Set): return_set = set() for si in literal.elts: return_set.add(self._get_literal_value(si)) - return return_set + literal_value = return_set elif isinstance(literal, _ast.Dict): - return dict(zip(literal.keys, literal.values)) + literal_value = dict(zip(literal.keys, literal.values)) elif isinstance(literal, _ast.Ellipsis): # what do we want to do with this? - pass + literal_value = None elif isinstance(literal, _ast.Name): - return literal.id + literal_value = literal.id # NOTE(sigmavirus24): NameConstants are only part of the AST in Python # 3. NameConstants tend to refer to things like True and False. This # prevents them from being re-assigned in Python 3. elif six.PY3 and isinstance(literal, _ast.NameConstant): - return str(literal.value) + literal_value = str(literal.value) # NOTE(sigmavirus24): Bytes are only part of the AST in Python 3 elif six.PY3 and isinstance(literal, _ast.Bytes): - return literal.s + literal_value = literal.s else: - return None + literal_value = None + + return literal_value def get_call_arg_value(self, argument_name): '''Gets the value of a named argument in a function call. @@ -298,11 +298,9 @@ :param position_num: The index of the argument to return the value for :return: Value of the argument at the specified position if it exists ''' - if ( - 'call' in self._context and - hasattr(self._context['call'], 'args') and - position_num < len(self._context['call'].args) - ): + if ('call' in self._context and + hasattr(self._context['call'], 'args') and + position_num < len(self._context['call'].args)): return self._get_literal_value( self._context['call'].args[position_num] ) @@ -315,10 +313,7 @@ :param module: The module name to look for :return: True if the module is found, False otherwise ''' - if 'module' in self._context and self._context['module'] == module: - return True - else: - return False + return 'module' in self._context and self._context['module'] == module def is_module_imported_exact(self, module): '''Check if a specified module has been imported; only exact matches. @@ -326,10 +321,8 @@ :param module: The module name to look for :return: True if the module is found, False otherwise ''' - if 'imports' in self._context and module in self._context['imports']: - return True - else: - return False + return ('imports' in self._context and + module in self._context['imports']) def is_module_imported_like(self, module): '''Check if a specified module has been imported diff -Nru bandit-1.1.0/bandit/core/docs_utils.py bandit-1.4.0/bandit/core/docs_utils.py --- bandit-1.1.0/bandit/core/docs_utils.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/docs_utils.py 2017-01-06 17:33:11.000000000 +0000 @@ -15,7 +15,7 @@ # under the License. # where our docs are hosted -base_url = 'http://docs.openstack.org/developer/bandit/' +BASE_URL = 'http://docs.openstack.org/developer/bandit/' def get_url(bid): @@ -26,7 +26,7 @@ info = extension_loader.MANAGER.plugins_by_id.get(bid, None) if info is not None: - return base_url + ('plugins/%s.html' % info.plugin.__name__) + return BASE_URL + ('plugins/%s.html' % info.plugin.__name__) info = extension_loader.MANAGER.blacklist_by_id.get(bid, None) if info is not None: @@ -38,6 +38,6 @@ ext = template.format( kind='imports', id=info['id'], name=info['name']) - return base_url + ext.lower() + return BASE_URL + ext.lower() - return base_url # no idea, give the docs main page + return BASE_URL # no idea, give the docs main page diff -Nru bandit-1.1.0/bandit/core/issue.py bandit-1.4.0/bandit/core/issue.py --- bandit-1.1.0/bandit/core/issue.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/issue.py 2017-01-06 17:33:11.000000000 +0000 @@ -17,11 +17,11 @@ from __future__ import division from __future__ import unicode_literals -from bandit.core import constants +import linecache -from six.moves import xrange +from six import moves -import linecache +from bandit.core import constants class Issue(object): @@ -93,7 +93,7 @@ lmax = lmin + len(self.linerange) + max_lines - 1 tmplt = "%i\t%s" if tabbed else "%i %s" - for line in xrange(lmin, lmax): + for line in moves.xrange(lmin, lmax): text = linecache.getline(self.fname, line) if isinstance(text, bytes): diff -Nru bandit-1.1.0/bandit/core/manager.py bandit-1.4.0/bandit/core/manager.py --- bandit-1.1.0/bandit/core/manager.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/manager.py 2017-01-06 17:33:11.000000000 +0000 @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -from collections import OrderedDict +import collections import fnmatch import json import logging @@ -31,15 +31,15 @@ from bandit.core import test_set as b_test_set -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) -class BanditManager(): +class BanditManager(object): scope = [] def __init__(self, config, agg_type, debug=False, verbose=False, - profile={}, ignore_nosec=False): + profile=None, ignore_nosec=False): '''Get logger, config, AST handler, and result store ready :param config: config options object @@ -53,6 +53,8 @@ ''' self.debug = debug self.verbose = verbose + if not profile: + profile = {} self.ignore_nosec = ignore_nosec self.b_conf = config self.files_list = [] @@ -69,6 +71,16 @@ self.progress = b_constants.progress_increment self.scores = [] + def get_skipped(self): + ret = [] + # "skip" is a tuple of name and reason, decode just the name + for skip in self.skipped: + if isinstance(skip[0], bytes): + ret.append((skip[0].decode('utf-8'), skip[1])) + else: + ret.append(skip) + return ret + def get_issue_list(self, sev_level=b_constants.LOW, conf_level=b_constants.LOW): @@ -86,7 +98,7 @@ jdata = json.loads(data) items = [issue.issue_from_dict(j) for j in jdata["results"]] except Exception as e: - logger.warning("Failed to load baseline data: %s", e) + LOG.warning("Failed to load baseline data: %s", e) self.baseline = items def filter_results(self, sev_filter, conf_filter): @@ -181,8 +193,8 @@ files_list.update(new_files) excluded_files.update(newly_excluded) else: - logger.warning("Skipping directory (%s), use -r flag to " - "scan contents", fname) + LOG.warning("Skipping directory (%s), use -r flag to " + "scan contents", fname) else: # if the user explicitly mentions a file on command line, @@ -212,7 +224,7 @@ new_files_list = list(self.files_list) for count, fname in enumerate(self.files_list): - logger.debug("working on file : %s", fname) + LOG.debug("working on file : %s", fname) if len(self.files_list) > self.progress: # is it time to update the progress indicator? @@ -220,48 +232,12 @@ 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: - logger.error( - "Exception occurred when executing tests against " - "{0}. Run \"bandit --debug {0}\" to see the full " - "traceback.".format(fname) - ) - self.skipped.append( - (fname, 'exception while scanning file') - ) - new_files_list.remove(fname) - logger.debug(" Exception string: %s", e) - logger.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) @@ -276,6 +252,38 @@ # 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 @@ -294,8 +302,10 @@ return score -def _get_files_from_dir(files_dir, included_globs=['*.py'], +def _get_files_from_dir(files_dir, included_globs=None, excluded_path_strings=None): + if not included_globs: + included_globs = ['*.py'] if not excluded_path_strings: excluded_path_strings = [] @@ -371,7 +381,7 @@ :return: A dictionary with a list of candidates for each issue """ - issue_candidates = OrderedDict() + issue_candidates = collections.OrderedDict() for unmatched in unmatched_issues: issue_candidates[unmatched] = ([i for i in results_list if diff -Nru bandit-1.1.0/bandit/core/meta_ast.py bandit-1.4.0/bandit/core/meta_ast.py --- bandit-1.1.0/bandit/core/meta_ast.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/meta_ast.py 2017-01-06 17:33:11.000000000 +0000 @@ -15,16 +15,19 @@ # under the License. -from collections import OrderedDict +import collections import logging -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) -class BanditMetaAst(): +class BanditMetaAst(object): - nodes = OrderedDict() + nodes = collections.OrderedDict() + + def __init__(self): + pass def add_node(self, node, parent_id, depth): '''Add a node to the AST node collection @@ -35,7 +38,7 @@ :return: - ''' node_id = hex(id(node)) - logger.debug('adding node : %s [%s]', node_id, depth) + LOG.debug('adding node : %s [%s]', node_id, depth) self.nodes[node_id] = { 'raw': node, 'parent_id': parent_id, 'depth': depth } diff -Nru bandit-1.1.0/bandit/core/metrics.py bandit-1.4.0/bandit/core/metrics.py --- bandit-1.1.0/bandit/core/metrics.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/metrics.py 2017-01-06 17:33:11.000000000 +0000 @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -from collections import Counter +import collections from bandit.core import constants @@ -76,12 +76,13 @@ def aggregate(self): """Do final aggregation of metrics.""" - c = Counter() + c = collections.Counter() for fname in self.data: c.update(self.data[fname]) self.data['_totals'] = dict(c) - def _get_issue_counts(self, scores): + @staticmethod + def _get_issue_counts(scores): """Get issue counts aggregated by confidence/severity rankings. :param scores: list of scores to aggregate / count diff -Nru bandit-1.1.0/bandit/core/node_visitor.py bandit-1.4.0/bandit/core/node_visitor.py --- bandit-1.1.0/bandit/core/node_visitor.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/node_visitor.py 2017-01-06 17:33:11.000000000 +0000 @@ -21,10 +21,9 @@ from bandit.core import constants from bandit.core import tester as b_tester from bandit.core import utils as b_utils -from bandit.core.utils import InvalidModulePath -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) class BanditNodeVisitor(object): @@ -49,11 +48,11 @@ # in some cases we can't determine a qualified name try: self.namespace = b_utils.get_module_qualname_from_path(fname) - except InvalidModulePath: - logger.info('Unable to find qualified name for module: %s', - self.fname) + except b_utils.InvalidModulePath: + LOG.info('Unable to find qualified name for module: %s', + self.fname) self.namespace = "" - logger.debug('Module qualified name: %s', self.namespace) + LOG.debug('Module qualified name: %s', self.namespace) self.metrics = metrics def visit_ClassDef(self, node): @@ -185,14 +184,14 @@ self.context['import_aliases'] = self.import_aliases if self.debug: - logger.debug(ast.dump(node)) + LOG.debug(ast.dump(node)) self.metaast.add_node(node, '', self.depth) if hasattr(node, 'lineno'): self.context['lineno'] = node.lineno if node.lineno in self.nosec_lines: - logger.debug("skipped, nosec") + LOG.debug("skipped, nosec") self.metrics.note_nosec() return False @@ -201,10 +200,10 @@ self.context['filename'] = self.fname self.seen += 1 - logger.debug("entering: %s %s [%s]", hex(id(node)), type(node), - self.depth) + LOG.debug("entering: %s %s [%s]", hex(id(node)), type(node), + self.depth) self.depth += 1 - logger.debug(self.context) + LOG.debug(self.context) return True def visit(self, node): @@ -213,14 +212,14 @@ visitor = getattr(self, method, None) if visitor is not None: if self.debug: - logger.debug("%s called (%s)", method, ast.dump(node)) + LOG.debug("%s called (%s)", method, ast.dump(node)) visitor(node) else: self.update_scores(self.tester.run_tests(self.context, name)) def post_visit(self, node): self.depth -= 1 - logger.debug("%s\texiting : %s", self.depth, hex(id(node))) + LOG.debug("%s\texiting : %s", self.depth, hex(id(node))) # HACK(tkelsey): this is needed to clean up post-recursion stuff that # gets setup in the visit methods for these node types. diff -Nru bandit-1.1.0/bandit/core/tester.py bandit-1.4.0/bandit/core/tester.py --- bandit-1.1.0/bandit/core/tester.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/tester.py 2017-01-06 17:33:11.000000000 +0000 @@ -23,10 +23,10 @@ from bandit.core import utils warnings.formatwarning = utils.warnings_formatter -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) -class BanditTester(): +class BanditTester(object): def __init__(self, testset, debug, nosec_lines): self.results = [] self.testset = testset @@ -67,7 +67,12 @@ if (result is not None and result.lineno not in self.nosec_lines and temp_context['lineno'] not in self.nosec_lines): - result.fname = temp_context['filename'] + + if isinstance(temp_context['filename'], bytes): + result.fname = temp_context['filename'].decode('utf-8') + else: + result.fname = temp_context['filename'] + if result.lineno is None: result.lineno = temp_context['lineno'] result.linerange = temp_context['linerange'] @@ -77,9 +82,7 @@ self.results.append(result) - logger.debug( - "Issue identified by %s: %s", name, result - ) + LOG.debug("Issue identified by %s: %s", name, result) sev = constants.RANKING.index(result.severity) val = constants.RANKING_VALUES[result.severity] scores['SEVERITY'][sev] += val @@ -91,10 +94,11 @@ self.report_error(name, context, e) if self.debug: raise - logger.debug("Returning scores: %s", scores) + LOG.debug("Returning scores: %s", scores) return scores - def report_error(self, test, context, error): + @staticmethod + def report_error(test, context, error): what = "Bandit internal error running: " what += "%s " % test what += "on file %s at line %i: " % ( @@ -104,4 +108,4 @@ what += str(error) import traceback what += traceback.format_exc() - logger.error(what) + LOG.error(what) diff -Nru bandit-1.1.0/bandit/core/test_properties.py bandit-1.4.0/bandit/core/test_properties.py --- bandit-1.1.0/bandit/core/test_properties.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/test_properties.py 2017-01-06 17:33:11.000000000 +0000 @@ -18,7 +18,7 @@ from bandit.core import utils -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) def checks(*args): @@ -28,8 +28,8 @@ func._checks = [] func._checks.extend(utils.check_ast_node(a) for a in args) - logger.debug('checks() decorator executed') - logger.debug(' func._checks: %s', func._checks) + LOG.debug('checks() decorator executed') + LOG.debug(' func._checks: %s', func._checks) return func return wrapper @@ -79,8 +79,7 @@ if not hasattr(func, '_accepts_baseline'): func._accepts_baseline = True - logger.debug('accepts_baseline() decorator executed on %s', - func.__name__) + LOG.debug('accepts_baseline() decorator executed on %s', func.__name__) return func return wrapper(args[0]) diff -Nru bandit-1.1.0/bandit/core/test_set.py bandit-1.4.0/bandit/core/test_set.py --- bandit-1.1.0/bandit/core/test_set.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/test_set.py 2017-01-06 17:33:11.000000000 +0000 @@ -24,11 +24,13 @@ from bandit.core import extension_loader -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) -class BanditTestSet(): - def __init__(self, config, profile={}): +class BanditTestSet(object): + def __init__(self, config, profile=None): + if not profile: + profile = {} extman = extension_loader.MANAGER filtering = self._get_filter(config, profile) self.plugins = [p for p in extman.plugins @@ -36,50 +38,66 @@ self.plugins.extend(self._load_builtins(filtering, profile)) self._load_tests(config, self.plugins) - def _get_filter(self, config, profile): + @staticmethod + def _get_filter(config, profile): extman = extension_loader.MANAGER inc = set(profile.get('include', [])) exc = set(profile.get('exclude', [])) + all_blacklist_tests = set() + for _node, tests in six.iteritems(extman.blacklist): + all_blacklist_tests.update(t['id'] for t in tests) + + # this block is purely for backwards compatibility, the rules are as + # follows: + # B001,B401 means B401 + # B401 means B401 + # B001 means all blacklist tests + if 'B001' in inc: + if not inc.intersection(all_blacklist_tests): + inc.update(all_blacklist_tests) + inc.discard('B001') + if 'B001' in exc: + if not exc.intersection(all_blacklist_tests): + exc.update(all_blacklist_tests) + exc.discard('B001') + if inc: filtered = inc else: filtered = set(extman.plugins_by_id.keys()) filtered.update(extman.builtin) - for node, tests in six.iteritems(extman.blacklist): - filtered.update(t['id'] for t in tests) + filtered.update(all_blacklist_tests) return filtered - exc def _load_builtins(self, filtering, profile): '''loads up builtin functions, so they can be filtered.''' - class Wrapper: + class Wrapper(object): def __init__(self, name, plugin): self.name = name self.plugin = plugin - results = [] - - if 'B001' in filtering: - extman = extension_loader.MANAGER - blacklist = profile.get('blacklist') - if not blacklist: # not overriden by legacy data - blacklist = {} - for node, tests in six.iteritems(extman.blacklist): - values = [t for t in tests if t['id'] in filtering] - if values: - blacklist[node] = values - - # this dresses up the blacklist to look like a plugin, but - # the '_checks' data comes from the blacklist information. - # the '_config' is the filtered blacklist data set. - setattr(blacklisting.blacklist, "_test_id", 'B001') - setattr(blacklisting.blacklist, "_checks", blacklist.keys()) - setattr(blacklisting.blacklist, "_config", blacklist) - results.append(Wrapper('blacklist', blacklisting.blacklist)) - - return results + extman = extension_loader.MANAGER + blacklist = profile.get('blacklist') + if not blacklist: # not overridden by legacy data + blacklist = {} + for node, tests in six.iteritems(extman.blacklist): + values = [t for t in tests if t['id'] in filtering] + if values: + blacklist[node] = values + + if not blacklist: + return [] + + # this dresses up the blacklist to look like a plugin, but + # the '_checks' data comes from the blacklist information. + # the '_config' is the filtered blacklist data set. + setattr(blacklisting.blacklist, "_test_id", 'B001') + setattr(blacklisting.blacklist, "_checks", blacklist.keys()) + setattr(blacklisting.blacklist, "_config", blacklist) + return [Wrapper('blacklist', blacklisting.blacklist)] def _load_tests(self, config, plugins): '''Builds a dict mapping tests to node types.''' @@ -94,8 +112,8 @@ plugin.plugin._config = cfg for check in plugin.plugin._checks: self.tests.setdefault(check, []).append(plugin.plugin) - logger.debug('added function %s (%s) targetting %s', - plugin.name, plugin.plugin._test_id, check) + LOG.debug('added function %s (%s) targetting %s', + plugin.name, plugin.plugin._test_id, check) def get_tests(self, checktype): '''Returns all tests that are of type checktype diff -Nru bandit-1.1.0/bandit/core/utils.py bandit-1.4.0/bandit/core/utils.py --- bandit-1.1.0/bandit/core/utils.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/core/utils.py 2017-01-06 17:33:11.000000000 +0000 @@ -25,7 +25,7 @@ except ImportError: import ConfigParser as configparser -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) """Various helper functions.""" @@ -46,11 +46,11 @@ :param aliases: Import aliases dictionary :returns: Qualified name referred to by the attribute or name. ''' - if type(node) == _ast.Name: + if isinstance(node, _ast.Name): if node.id in aliases: return aliases[node.id] return node.id - elif type(node) == _ast.Attribute: + elif isinstance(node, _ast.Attribute): name = '%s.%s' % (_get_attr_qual_name(node.value, aliases), node.attr) if name in aliases: return aliases[name] @@ -60,11 +60,11 @@ def get_call_name(node, aliases): - if type(node.func) == _ast.Name: + if isinstance(node.func, _ast.Name): if deepgetattr(node, 'func.id') in aliases: return aliases[deepgetattr(node, 'func.id')] - return(deepgetattr(node, 'func.id')) - elif type(node.func) == _ast.Attribute: + return deepgetattr(node, 'func.id') + elif isinstance(node.func, _ast.Attribute): return _get_attr_qual_name(node.func, aliases) else: return "" @@ -76,7 +76,7 @@ def get_qual_attr(node, aliases): prefix = "" - if type(node) == _ast.Attribute: + if isinstance(node, _ast.Attribute): try: val = deepgetattr(node, 'value.id') if val in aliases: @@ -84,11 +84,11 @@ else: prefix = deepgetattr(node, 'value.id') except Exception: - # NOTE(tkelsey): degrade gracefully when we cant get the fully + # NOTE(tkelsey): degrade gracefully when we can't get the fully # qualified name for an attr, just return its base name. pass - return("%s.%s" % (prefix, node.attr)) + return "%s.%s" % (prefix, node.attr) else: return "" # TODO(tkelsey): process other node types @@ -122,10 +122,7 @@ super(ProfileNotFound, self).__init__(message) -def warnings_formatter(message, - category=UserWarning, - filename='', - lineno=-1, +def warnings_formatter(message, category=UserWarning, filename='', lineno=-1, line=''): '''Monkey patch for warnings.warn to suppress cruft output.''' return "{0}\n".format(message) @@ -286,7 +283,7 @@ ''' func = node.func try: - return (func.attr if isinstance(func, ast.Attribute) else func.id) + return func.attr if isinstance(func, ast.Attribute) else func.id except AttributeError: return "" @@ -303,14 +300,14 @@ elif hasattr(f, "im_func"): module_name = f.im_func.__module__ else: - logger.warning("Cannot resolve file where %s is defined", f) + LOG.warning("Cannot resolve file where %s is defined", f) return None module = sys.modules[module_name] if hasattr(module, "__file__"): return module.__file__ else: - logger.warning("Cannot resolve file path for module %s", module_name) + LOG.warning("Cannot resolve file path for module %s", module_name) return None @@ -321,8 +318,8 @@ return {k: v for k, v in config.items('bandit')} except (configparser.Error, KeyError, TypeError): - logger.warning("Unable to parse config file %s or missing [bandit] " - "section", f_loc) + LOG.warning("Unable to parse config file %s or missing [bandit] " + "section", f_loc) return None diff -Nru bandit-1.1.0/bandit/formatters/csv.py bandit-1.4.0/bandit/formatters/csv.py --- bandit-1.1.0/bandit/formatters/csv.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/formatters/csv.py 2017-01-06 17:33:11.000000000 +0000 @@ -40,7 +40,7 @@ import logging import sys -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) def report(manager, fileobj, sev_level, conf_level, lines=-1): @@ -73,4 +73,4 @@ writer.writerow(result.as_dict(with_code=False)) if fileobj.name != sys.stdout.name: - logger.info("CSV output written to file: %s" % fileobj.name) + LOG.info("CSV output written to file: %s", fileobj.name) diff -Nru bandit-1.1.0/bandit/formatters/html.py bandit-1.4.0/bandit/formatters/html.py --- bandit-1.1.0/bandit/formatters/html.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/formatters/html.py 2017-01-06 17:33:11.000000000 +0000 @@ -28,6 +28,8 @@ + + Bandit Report @@ -47,7 +49,6 @@ padding-top:.5em; padding-bottom:.5em; padding-left:1em; - } .metrics-box { @@ -99,45 +100,49 @@ - -
-
+
+
+
Metrics:
- Total lines of code: 5
- Total lines skipped (#nosec): 0 + Total lines of code: 9
+ Total lines skipped (#nosec): 0
- +

- +
- -
- blacklist_calls: Use of unsafe yaml load. Allows instantiation - of arbitrary objects. Consider yaml.safe_load(). -
- Test ID: B301
- Severity: MEDIUM
- Confidence: HIGH
- File: - examples/yaml_load.py
+
+
+ yaml_load: Use of unsafe yaml load. Allows + instantiation of arbitrary objects. Consider yaml.safe_load().
+ Test ID: B506
+ Severity: MEDIUM
+ Confidence: HIGH
+ File: examples/yaml_load.py
+ More info: + http://docs.openstack.org/developer/bandit/plugins/yaml_load.html +
- +
-    4       ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})
-    5       y = yaml.load(ystr)
-    6       yaml.dump(y)
+    5       ystr = yaml.dump({'a' : 1, 'b' : 2, 'c' : 3})
+    6       y = yaml.load(ystr)
+    7       yaml.dump(y)
     
- +
+ +
- - +
@@ -151,12 +156,13 @@ import sys from bandit.core import docs_utils -from bandit.core.test_properties import accepts_baseline +from bandit.core import test_properties +from bandit.formatters import utils -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) -@accepts_baseline +@test_properties.accepts_baseline def report(manager, fileobj, sev_level, conf_level, lines=-1): """Writes issues to 'fileobj' in HTML format @@ -172,6 +178,8 @@ + + Bandit Report @@ -191,7 +199,6 @@ padding-top:.5em; padding-bottom:.5em; padding-left:1em; - } .metrics-box { @@ -248,73 +255,73 @@ {skipped}
- +
{results} - +
""" issue_block = u""" - -
+
+
{test_name}: {test_text}
Test ID: {test_id}
- Severity: {severity}
- Confidence: {confidence}
- File: {path}
- More info: {url}
+ Severity: {severity}
+ Confidence: {confidence}
+ File: {path}
+ More info: {url}
{code} {candidates}
- +
""" code_block = u""" - +
 {code}
 
- +
""" candidate_block = u""" - +

Candidates: {candidate_list} - +
""" candidate_issue = u""" - -
+
+
{code}
- +
""" skipped_block = u"""
- -
+
+
Skipped files:

{files_list}
- +
""" metrics_block = u""" - -
-
+
+
+
Metrics:
- Total lines of code: {loc}
- Total lines skipped (#nosec): {nosec} + Total lines of code: {loc}
+ Total lines skipped (#nosec): {nosec}
- +
""" @@ -324,7 +331,7 @@ # build the skipped string to insert in the report skipped_str = ''.join('%s reason: %s
' % (fname, reason) - for fname, reason in manager.skipped) + for fname, reason in manager.get_skipped()) if skipped_str: skipped_text = skipped_block.format(files_list=skipped_str) else: @@ -372,8 +379,9 @@ results=results_str) with fileobj: - fileobj.write(str(header_block.encode('utf-8'))) - fileobj.write(str(report_contents.encode('utf-8'))) + wrapped_file = utils.wrap_file_object(fileobj) + wrapped_file.write(utils.convert_file_contents(header_block)) + wrapped_file.write(utils.convert_file_contents(report_contents)) if fileobj.name != sys.stdout.name: - logger.info("HTML output written to file: %s" % fileobj.name) + LOG.info("HTML output written to file: %s", fileobj.name) diff -Nru bandit-1.1.0/bandit/formatters/json.py bandit-1.4.0/bandit/formatters/json.py --- bandit-1.1.0/bandit/formatters/json.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/formatters/json.py 2017-01-06 17:33:11.000000000 +0000 @@ -68,21 +68,6 @@ "test_name": "blacklist_calls", "test_id": "B301" } - ], - "stats": [ - { - "filename": "examples/yaml_load.py", - "issue totals": { - "HIGH": 0, - "LOW": 0, - "MEDIUM": 1, - "UNDEFINED": 0 - }, - "score": { - "CONFIDENCE": 10, - "SEVERITY": 5 - } - } ] } @@ -96,18 +81,15 @@ import datetime import json import logging -from operator import itemgetter +import operator import sys -import six - -from bandit.core import constants -from bandit.core.test_properties import accepts_baseline +from bandit.core import test_properties -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) -@accepts_baseline +@test_properties.accepts_baseline def report(manager, fileobj, sev_level, conf_level, lines=-1): '''''Prints issues in JSON format @@ -118,31 +100,11 @@ :param lines: Number of lines to report, -1 for all ''' - stats = dict(zip(manager.files_list, manager.scores)) - machine_output = dict({'results': [], 'errors': [], 'stats': []}) - for (fname, reason) in manager.skipped: + machine_output = {'results': [], 'errors': []} + for (fname, reason) in manager.get_skipped(): machine_output['errors'].append({'filename': fname, 'reason': reason}) - for filer, score in six.iteritems(stats): - totals = {} - rank = constants.RANKING - sev_idx = rank.index(sev_level) - for i in range(sev_idx, len(rank)): - severity = rank[i] - severity_value = constants.RANKING_VALUES[severity] - try: - sc = score['SEVERITY'][i] / severity_value - except ZeroDivisionError: - sc = 0 - totals[severity] = sc - - machine_output['stats'].append({ - 'filename': filer, - 'score': {'SEVERITY': sum(i for i in score['SEVERITY']), - 'CONFIDENCE': sum(i for i in score['CONFIDENCE'])}, - 'issue totals': totals}) - results = manager.get_issue_list(sev_level=sev_level, conf_level=conf_level) @@ -159,6 +121,7 @@ else: collector = [r.as_dict() for r in results] + itemgetter = operator.itemgetter if manager.agg_type == 'vuln': machine_output['results'] = sorted(collector, key=itemgetter('test_name')) @@ -181,4 +144,4 @@ fileobj.write(result) if fileobj.name != sys.stdout.name: - logger.info("JSON output written to file: %s" % fileobj.name) + LOG.info("JSON output written to file: %s", fileobj.name) diff -Nru bandit-1.1.0/bandit/formatters/screen.py bandit-1.4.0/bandit/formatters/screen.py --- bandit-1.1.0/bandit/formatters/screen.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/formatters/screen.py 2017-01-06 17:33:11.000000000 +0000 @@ -44,11 +44,11 @@ import sys from bandit.core import constants -from bandit.core.test_properties import accepts_baseline +from bandit.core import test_properties -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) -color = { +COLOR = { 'DEFAULT': '\033[0m', 'HEADER': '\033[95m', 'LOW': '\033[94m', @@ -58,7 +58,7 @@ def header(text, *args): - return u'%s%s%s' % (color['HEADER'], (text % args), color['DEFAULT']) + return u'%s%s%s' % (COLOR['HEADER'], (text % args), COLOR['DEFAULT']) def get_verbose_details(manager): @@ -90,7 +90,7 @@ # returns a list of lines that should be added to the existing lines list bits = [] bits.append("%s%s>> Issue: [%s:%s] %s" % ( - indent, color[issue.severity], issue.test_id, issue.test, issue.text)) + indent, COLOR[issue.severity], issue.test_id, issue.test, issue.text)) bits.append("%s Severity: %s Confidence: %s" % ( indent, issue.severity.capitalize(), issue.confidence.capitalize())) @@ -98,7 +98,7 @@ bits.append("%s Location: %s:%s%s" % ( indent, issue.fname, issue.lineno if show_lineno else "", - color['DEFAULT'])) + COLOR['DEFAULT'])) if show_code: bits.extend([indent + l for l in @@ -143,7 +143,7 @@ print('\n'.join([bit for bit in bits])) -@accepts_baseline +@test_properties.accepts_baseline def report(manager, fileobj, sev_level, conf_level, lines=-1): """Prints discovered issues formatted for screen reading @@ -172,10 +172,11 @@ (manager.metrics.data['_totals']['nosec'])) bits.append(get_metrics(manager)) - bits.append(header("Files skipped (%i):", len(manager.skipped))) - bits.extend(["\t%s (%s)" % skip for skip in manager.skipped]) + skipped = manager.get_skipped() + bits.append(header("Files skipped (%i):", len(skipped))) + bits.extend(["\t%s (%s)" % skip for skip in skipped]) do_print(bits) if fileobj.name != sys.stdout.name: - logger.info(("Screen formatter output was not written to file: %s" - ", consider '-f txt'") % fileobj.name) + LOG.info("Screen formatter output was not written to file: %s, " + "consider '-f txt'", fileobj.name) diff -Nru bandit-1.1.0/bandit/formatters/text.py bandit-1.4.0/bandit/formatters/text.py --- bandit-1.1.0/bandit/formatters/text.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/formatters/text.py 2017-01-06 17:33:11.000000000 +0000 @@ -44,9 +44,10 @@ import sys from bandit.core import constants -from bandit.core.test_properties import accepts_baseline +from bandit.core import test_properties +from bandit.formatters import utils -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) def get_verbose_details(manager): @@ -123,7 +124,7 @@ return '\n'.join([bit for bit in bits]) -@accepts_baseline +@test_properties.accepts_baseline def report(manager, fileobj, sev_level, conf_level, lines=-1): """Prints discovered issues in the text format @@ -149,13 +150,15 @@ bits.append('\tTotal lines skipped (#nosec): %i' % (manager.metrics.data['_totals']['nosec'])) + skipped = manager.get_skipped() bits.append(get_metrics(manager)) - bits.append("Files skipped (%i):" % len(manager.skipped)) - bits.extend(["\t%s (%s)" % skip for skip in manager.skipped]) + bits.append("Files skipped (%i):" % len(skipped)) + bits.extend(["\t%s (%s)" % skip for skip in skipped]) result = '\n'.join([bit for bit in bits]) + '\n' with fileobj: - fileobj.write(str(result.encode('utf-8'))) + wrapped_file = utils.wrap_file_object(fileobj) + wrapped_file.write(utils.convert_file_contents(result)) if fileobj.name != sys.stdout.name: - logger.info("Text output written to file: %s", fileobj.name) + LOG.info("Text output written to file: %s", fileobj.name) diff -Nru bandit-1.1.0/bandit/formatters/utils.py bandit-1.4.0/bandit/formatters/utils.py --- bandit-1.1.0/bandit/formatters/utils.py 1970-01-01 00:00:00.000000000 +0000 +++ bandit-1.4.0/bandit/formatters/utils.py 2017-01-06 17:33:11.000000000 +0000 @@ -0,0 +1,43 @@ +# Copyright (c) 2016 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Utility functions for formatting plugins for Bandit.""" + +import io + +import six + + +def wrap_file_object(fileobj): + """Handle differences in Python 2 and 3 around writing bytes.""" + # If it's not an instance of IOBase, we're probably using Python 2 and + # that is less finnicky about writing text versus bytes to a file. + if not isinstance(fileobj, io.IOBase): + return fileobj + + # At this point we're using Python 3 and that will mangle text written to + # a file written in bytes mode. So, let's check if the file can handle + # text as opposed to bytes. + if isinstance(fileobj, io.TextIOBase): + return fileobj + + # Finally, we've determined that the fileobj passed in cannot handle text, + # so we use TextIOWrapper to handle the conversion for us. + return io.TextIOWrapper(fileobj) + + +def convert_file_contents(text): + """Convert text to built-in strings on Python 2.""" + if not six.PY2: + return text + return str(text.encode('utf-8')) diff -Nru bandit-1.1.0/bandit/formatters/xml.py bandit-1.4.0/bandit/formatters/xml.py --- bandit-1.1.0/bandit/formatters/xml.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/formatters/xml.py 2017-01-06 17:33:11.000000000 +0000 @@ -46,7 +46,7 @@ import six -logger = logging.getLogger(__name__) +LOG = logging.getLogger(__name__) def report(manager, fileobj, sev_level, conf_level, lines=-1): @@ -88,4 +88,4 @@ tree.write(fileobj, encoding='utf-8', xml_declaration=True) if fileobj.name != sys.stdout.name: - logger.info("XML output written to file: %s" % fileobj.name) + LOG.info("XML output written to file: %s", fileobj.name) diff -Nru bandit-1.1.0/bandit/plugins/asserts.py bandit-1.4.0/bandit/plugins/asserts.py --- bandit-1.1.0/bandit/plugins/asserts.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/asserts.py 2017-01-06 17:33:11.000000000 +0000 @@ -57,9 +57,9 @@ @test.test_id('B101') @test.checks('Assert') def assert_used(context): - return bandit.Issue( - severity=bandit.LOW, - confidence=bandit.HIGH, - text=("Use of assert detected. The enclosed code " - "will be removed when compiling to optimised byte code.") - ) + return bandit.Issue( + severity=bandit.LOW, + confidence=bandit.HIGH, + text=("Use of assert detected. The enclosed code " + "will be removed when compiling to optimised byte code.") + ) diff -Nru bandit-1.1.0/bandit/plugins/general_bad_file_permissions.py bandit-1.4.0/bandit/plugins/general_bad_file_permissions.py --- bandit-1.1.0/bandit/plugins/general_bad_file_permissions.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/general_bad_file_permissions.py 2017-01-06 17:33:11.000000000 +0000 @@ -70,10 +70,8 @@ if context.call_args_count == 2: mode = context.get_call_arg_at_position(1) - if ( - mode is not None and type(mode) == int and - (mode & stat.S_IWOTH or mode & stat.S_IXGRP) - ): + if (mode is not None and isinstance(mode, int) and + (mode & stat.S_IWOTH or mode & stat.S_IXGRP)): # world writable is an HIGH, group executable is a MEDIUM if mode & stat.S_IWOTH: sev_level = bandit.HIGH @@ -87,5 +85,5 @@ severity=sev_level, confidence=bandit.HIGH, text="Chmod setting a permissive mask %s on file (%s)." % - (oct(mode), filename) + (oct(mode), filename) ) diff -Nru bandit-1.1.0/bandit/plugins/general_hardcoded_password.py bandit-1.4.0/bandit/plugins/general_hardcoded_password.py --- bandit-1.1.0/bandit/plugins/general_hardcoded_password.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/general_hardcoded_password.py 2017-01-06 17:33:11.000000000 +0000 @@ -21,7 +21,7 @@ from bandit.core import test_properties as test -candidates = set(["password", "pass", "passwd", "pwd", "secret", "token", +CANDIDATES = set(["password", "pass", "passwd", "pwd", "secret", "token", "secrete"]) @@ -84,10 +84,10 @@ if isinstance(node.parent, ast.Assign): # looks for "candidate='some_string'" for targ in node.parent.targets: - if isinstance(targ, ast.Name) and targ.id in candidates: + if isinstance(targ, ast.Name) and targ.id in CANDIDATES: return _report(node.s) - elif isinstance(node.parent, ast.Index) and node.s in candidates: + elif isinstance(node.parent, ast.Index) and node.s in CANDIDATES: # looks for "dict[candidate]='some_string'" # assign -> subscript -> index -> string assign = node.parent.parent.parent @@ -98,7 +98,7 @@ elif isinstance(node.parent, ast.Compare): # looks for "candidate == 'some_string'" comp = node.parent - if isinstance(comp.left, ast.Name) and comp.left.id in candidates: + if isinstance(comp.left, ast.Name) and comp.left.id in CANDIDATES: if isinstance(comp.comparators[0], ast.Str): return _report(comp.comparators[0].s) @@ -150,7 +150,7 @@ """ # looks for "function(candidate='some_string')" for kw in context.node.keywords: - if isinstance(kw.value, ast.Str) and kw.arg in candidates: + if isinstance(kw.value, ast.Str) and kw.arg in CANDIDATES: return _report(kw.value.s) @@ -211,5 +211,5 @@ for key, val in zip(context.node.args.args, defs): if isinstance(key, ast.Name): check = key.arg if sys.version_info.major > 2 else key.id # Py3 - if isinstance(val, ast.Str) and check in candidates: + if isinstance(val, ast.Str) and check in CANDIDATES: return _report(val.s) diff -Nru bandit-1.1.0/bandit/plugins/general_hardcoded_tmp.py bandit-1.4.0/bandit/plugins/general_hardcoded_tmp.py --- bandit-1.1.0/bandit/plugins/general_hardcoded_tmp.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/general_hardcoded_tmp.py 2017-01-06 17:33:11.000000000 +0000 @@ -73,7 +73,7 @@ @test.checks('Str') @test.test_id('B108') def hardcoded_tmp_directory(context, config): - if (config is not None and 'tmp_dirs' in config): + if config is not None and 'tmp_dirs' in config: tmp_dirs = config['tmp_dirs'] else: tmp_dirs = ['/tmp', '/var/tmp', '/dev/shm'] diff -Nru bandit-1.1.0/bandit/plugins/injection_shell.py bandit-1.4.0/bandit/plugins/injection_shell.py --- bandit-1.1.0/bandit/plugins/injection_shell.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/injection_shell.py 2017-01-06 17:33:11.000000000 +0000 @@ -20,28 +20,16 @@ import bandit from bandit.core import test_properties as test - -def _has_special_characters(command): - # check if it contains any of the characters that may cause globing, - # multiple commands, subshell, or variable resolution - # glob: [ { * ? - # variable: $ - # subshell: ` $ - return bool(re.search(r'[{|\[;$\*\?`]', command)) +# yuck, regex: starts with a windows drive letter (eg C:) +# or one of our path delimeter characters (/, \, .) +full_path_match = re.compile(r'^(?:[A-Za-z](?=\:)|[\\\/\.])') def _evaluate_shell_call(context): no_formatting = isinstance(context.node.args[0], ast.Str) - if no_formatting: - command = context.call_args[0] - no_special_chars = not _has_special_characters(command) - else: - no_special_chars = False - if no_formatting and no_special_chars: + if no_formatting: return bandit.LOW - elif no_formatting: - return bandit.MEDIUM else: return bandit.HIGH @@ -204,15 +192,6 @@ 'rewriting without shell', lineno=context.get_lineno_for_call_arg('shell'), ) - elif sev == bandit.MEDIUM: - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.HIGH, - text='call with shell=True contains special shell ' - 'characters, consider moving extra logic into ' - 'Python code', - lineno=context.get_lineno_for_call_arg('shell'), - ) else: return bandit.Issue( severity=bandit.HIGH, @@ -464,14 +443,6 @@ 'Seems safe, but may be changed in the future, ' 'consider rewriting without shell' ) - elif sev == bandit.MEDIUM: - return bandit.Issue( - severity=bandit.MEDIUM, - confidence=bandit.HIGH, - text='Starting a process with a shell and special shell ' - 'characters, consider moving extra logic into ' - 'Python code' - ) else: return bandit.Issue( severity=bandit.HIGH, @@ -649,14 +620,13 @@ context.call_function_name_qual in config['shell'] or context.call_function_name_qual in config['no_shell']): - delims = ['/', '\\', '.'] node = context.node.args[0] # some calls take an arg list, check the first part if isinstance(node, ast.List): node = node.elts[0] # make sure the param is a string literal and not a var name - if(isinstance(node, ast.Str) and node.s[0] not in delims): + if isinstance(node, ast.Str) and not full_path_match.match(node.s): return bandit.Issue( severity=bandit.LOW, confidence=bandit.HIGH, diff -Nru bandit-1.1.0/bandit/plugins/injection_wildcard.py bandit-1.4.0/bandit/plugins/injection_wildcard.py --- bandit-1.1.0/bandit/plugins/injection_wildcard.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/injection_wildcard.py 2017-01-06 17:33:11.000000000 +0000 @@ -144,6 +144,6 @@ severity=bandit.HIGH, confidence=bandit.MEDIUM, text="Possible wildcard injection in call: %s" % - context.call_function_name_qual, + context.call_function_name_qual, lineno=context.get_lineno_for_call_arg('shell'), ) diff -Nru bandit-1.1.0/bandit/plugins/insecure_ssl_tls.py bandit-1.4.0/bandit/plugins/insecure_ssl_tls.py --- bandit-1.1.0/bandit/plugins/insecure_ssl_tls.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/insecure_ssl_tls.py 2017-01-06 17:33:11.000000000 +0000 @@ -31,8 +31,7 @@ 'PROTOCOL_SSLv3', # strict option 'PROTOCOL_TLSv1', # strict option 'SSLv3_METHOD', # strict option - 'TLSv1_METHOD'] # strict option - } + 'TLSv1_METHOD']} # strict option @test.takes_config @@ -117,7 +116,7 @@ .. versionadded:: 0.9.0 """ bad_ssl_versions = get_bad_proto_versions(config) - if (context.call_function_name_qual == 'ssl.wrap_socket'): + if context.call_function_name_qual == 'ssl.wrap_socket': if context.check_call_arg_value('ssl_version', bad_ssl_versions): return bandit.Issue( severity=bandit.HIGH, @@ -126,7 +125,7 @@ "version identified, security issue.", lineno=context.get_lineno_for_call_arg('ssl_version'), ) - elif (context.call_function_name_qual == 'pyOpenSSL.SSL.Context'): + elif context.call_function_name_qual == 'pyOpenSSL.SSL.Context': if context.check_call_arg_value('method', bad_ssl_versions): return bandit.Issue( severity=bandit.HIGH, @@ -139,7 +138,7 @@ elif (context.call_function_name_qual != 'ssl.wrap_socket' and context.call_function_name_qual != 'pyOpenSSL.SSL.Context'): if (context.check_call_arg_value('method', bad_ssl_versions) or - context.check_call_arg_value('ssl_version', bad_ssl_versions)): + context.check_call_arg_value('ssl_version', bad_ssl_versions)): lineno = (context.get_lineno_for_call_arg('method') or context.get_lineno_for_call_arg('ssl_version')) return bandit.Issue( @@ -259,7 +258,7 @@ .. versionadded:: 0.9.0 """ - if (context.call_function_name_qual == 'ssl.wrap_socket'): + if context.call_function_name_qual == 'ssl.wrap_socket': if context.check_call_arg_value('ssl_version') is None: # check_call_arg_value() returns False if the argument is found # but does not match the supplied value (or the default None). diff -Nru bandit-1.1.0/bandit/plugins/jinja2_templates.py bandit-1.4.0/bandit/plugins/jinja2_templates.py --- bandit-1.1.0/bandit/plugins/jinja2_templates.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/jinja2_templates.py 2017-01-06 17:33:11.000000000 +0000 @@ -78,7 +78,7 @@ @test.test_id('B701') def jinja2_autoescape_false(context): # check type just to be safe - if type(context.call_function_name_qual) == str: + if isinstance(context.call_function_name_qual, str): qualname_list = context.call_function_name_qual.split('.') func = qualname_list[-1] if 'jinja2' in qualname_list and func == 'Environment': @@ -87,7 +87,7 @@ # definite autoescape = False if (getattr(node, 'arg', None) == 'autoescape' and (getattr(node.value, 'id', None) == 'False' or - getattr(node.value, 'value', None) is False)): + getattr(node.value, 'value', None) is False)): return bandit.Issue( severity=bandit.HIGH, confidence=bandit.HIGH, diff -Nru bandit-1.1.0/bandit/plugins/mako_templates.py bandit-1.4.0/bandit/plugins/mako_templates.py --- bandit-1.1.0/bandit/plugins/mako_templates.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/mako_templates.py 2017-01-06 17:33:11.000000000 +0000 @@ -59,7 +59,7 @@ @test.test_id('B702') def use_of_mako_templates(context): # check type just to be safe - if type(context.call_function_name_qual) == str: + if isinstance(context.call_function_name_qual, str): qualname_list = context.call_function_name_qual.split('.') func = qualname_list[-1] if 'mako' in qualname_list and func == 'Template': diff -Nru bandit-1.1.0/bandit/plugins/try_except_continue.py bandit-1.4.0/bandit/plugins/try_except_continue.py --- bandit-1.1.0/bandit/plugins/try_except_continue.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/try_except_continue.py 2017-01-06 17:33:11.000000000 +0000 @@ -99,9 +99,9 @@ node = context.node if len(node.body) == 1: if (not config['check_typed_exception'] and - node.type is not None and - getattr(node.type, 'id', None) != 'Exception'): - return + node.type is not None and + getattr(node.type, 'id', None) != 'Exception'): + return if isinstance(node.body[0], ast.Continue): return bandit.Issue( diff -Nru bandit-1.1.0/bandit/plugins/try_except_pass.py bandit-1.4.0/bandit/plugins/try_except_pass.py --- bandit-1.1.0/bandit/plugins/try_except_pass.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/try_except_pass.py 2017-01-06 17:33:11.000000000 +0000 @@ -98,9 +98,9 @@ node = context.node if len(node.body) == 1: if (not config['check_typed_exception'] and - node.type is not None and - getattr(node.type, 'id', None) != 'Exception'): - return + node.type is not None and + getattr(node.type, 'id', None) != 'Exception'): + return if isinstance(node.body[0], ast.Pass): return bandit.Issue( diff -Nru bandit-1.1.0/bandit/plugins/weak_cryptographic_key.py bandit-1.4.0/bandit/plugins/weak_cryptographic_key.py --- bandit-1.1.0/bandit/plugins/weak_cryptographic_key.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/weak_cryptographic_key.py 2017-01-06 17:33:11.000000000 +0000 @@ -51,6 +51,10 @@ def _classify_key_size(key_type, key_size): + if isinstance(key_size, str): + # size provided via a variable - can't process it at the moment + return + key_sizes = { 'DSA': [(1024, bandit.HIGH), (2048, bandit.MEDIUM)], 'RSA': [(1024, bandit.HIGH), (2048, bandit.MEDIUM)], @@ -63,7 +67,7 @@ severity=level, confidence=bandit.HIGH, text='%s key sizes below %d bits are considered breakable. ' % - (key_type, size)) + (key_type, size)) def _weak_crypto_key_size_cryptography_io(context): @@ -92,7 +96,8 @@ 'SECT163K1': 163, 'SECT163R2': 163, } - curve = context.call_args[arg_position[key_type]] + curve = (context.get_call_arg_value('curve') or + context.call_args[arg_position[key_type]]) key_size = curve_key_sizes[curve] if curve in curve_key_sizes else 224 return _classify_key_size(key_type, key_size) diff -Nru bandit-1.1.0/bandit/plugins/yaml_load.py bandit-1.4.0/bandit/plugins/yaml_load.py --- bandit-1.1.0/bandit/plugins/yaml_load.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/bandit/plugins/yaml_load.py 2017-01-06 17:33:11.000000000 +0000 @@ -55,8 +55,10 @@ @test.test_id('B506') @test.checks('Call') def yaml_load(context): - if context.is_module_imported_like('yaml'): - if context.call_function_name_qual.endswith('.load'): + if isinstance(context.call_function_name_qual, str): + qualname_list = context.call_function_name_qual.split('.') + func = qualname_list[-1] + if 'yaml' in qualname_list and func == 'load': if not context.check_call_arg_value('Loader', 'SafeLoader'): return bandit.Issue( severity=bandit.MEDIUM, diff -Nru bandit-1.1.0/debian/changelog bandit-1.4.0/debian/changelog --- bandit-1.1.0/debian/changelog 2016-10-31 08:41:01.000000000 +0000 +++ bandit-1.4.0/debian/changelog 2017-02-06 19:42:37.000000000 +0000 @@ -1,3 +1,13 @@ +bandit (1.4.0-0ubuntu1) zesty; urgency=medium + + * d/gbp.conf: Update gbp configuration file. + * d/control: Update Vcs-* links and maintainers. + * New upstream release. + * Fixed (build-)depends for this release. + * d/patches/remove-failing-tests.patch: Dropped no longer needed. + + -- Chuck Short Mon, 06 Feb 2017 14:42:37 -0500 + bandit (1.1.0-3) unstable; urgency=medium * Add remove failing tests patch (Closes: #841631). diff -Nru bandit-1.1.0/debian/control bandit-1.4.0/debian/control --- bandit-1.1.0/debian/control 2016-10-31 08:41:01.000000000 +0000 +++ bandit-1.4.0/debian/control 2017-02-06 19:42:37.000000000 +0000 @@ -1,7 +1,8 @@ Source: bandit Section: python Priority: extra -Maintainer: PKG OpenStack +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: PKG OpenStack Uploaders: Dave Walker (Daviey) , Thomas Goirand , Build-Depends: debhelper (>= 9), @@ -14,36 +15,42 @@ python3-pbr, python3-setuptools, Build-Depends-Indep: python-bs4, - python-coverage, + python-coverage (>= 4.0), python-fixtures (>= 3.0.0), - python-git (>= 2.0.2), - python-hacking (>= 0.9.2), + python-git (>= 1.0.1), + python-hacking (>= 0.10), python-mock (>= 2.0), - python-oslosphinx (>= 2.5.0), - python-oslotest (>= 1:1.10.0), + python-oslosphinx (>= 4.7.0), + python-oslotest (>= 1.10.0), python-reno, python-six (>= 1.9.0), - python-stevedore (>= 1.16.0), - python-testscenarios, + python-sphinx (>= 1.2.1), + python-stevedore (>= 1.17.1), + python-testrepository (>= 0.0.18), + python-testscenarios (>= 0.4), python-testtools (>= 1.4.0), - python-yaml, + python-yaml (>= 3.10.0), python3-bs4, - python3-fixtures (>= 1.3.1), - python3-git (>= 2.0.2), - python3-hacking (>= 0.9.2), + python3-coverage (>= 4.0), + python3-fixtures (>= 3.0.0), + python3-git (>= 1.0.1), + python3-hacking (>= 0.10), python3-mock (>= 2.0), - python3-oslotest (>= 1:1.10.0), + python3-oslosphinx (>= 4.7.0), + python3-oslotest (>= 1.10.0), python3-six (>= 1.9.0), - python3-stevedore (>= 1.16.0), + python3-sphinx (>= 1.2.1), + python3-stevedore (>= 1.17.1), python3-subunit, - python3-testscenarios, + python3-testrepository (>= 0.0.18), + python3-testscenarios (>= 0.4), python3-testtools (>= 1.4.0), - python3-yaml, + python3-yaml (>= 3.10.0), subunit, - testrepository, + testrepository Standards-Version: 3.9.8 -Vcs-Browser: https://git.openstack.org/cgit/openstack/deb-bandit -Vcs-Git: https://git.openstack.org/openstack/deb-bandit +Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/bandit +Vcs-Git: git://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/bandit Homepage: https://wiki.openstack.org/wiki/Security/Projects/Bandit Package: python-bandit @@ -55,7 +62,7 @@ python-stevedore (>= 1.16.0), python-yaml, ${misc:Depends}, - ${python:Depends}, + ${python:Depends} Description: Security oriented static analyzer for Python code - Python 2.7 Bandit is a tool designed to find common security issues in Python code. To do this Bandit processes each file, builds an Abstract Syntaxt Tree (AST), @@ -71,7 +78,7 @@ python3-stevedore (>= 1.16.0), python3-yaml, ${misc:Depends}, - ${python3:Depends}, + ${python3:Depends} Description: Security oriented static analyzer for Python code - Python 3.x Bandit is a tool designed to find common security issues in Python code. To do this Bandit processes each file, builds an Abstract Syntaxt Tree (AST), @@ -84,7 +91,7 @@ Architecture: all Depends: python-bandit (= ${binary:Version}) | python3-bandit (= ${binary:Version}), ${misc:Depends}, - ${python:Depends}, + ${python:Depends} Description: Security oriented static analyzer for Python code - Metapackage Bandit is a tool designed to find common security issues in Python code. To do this Bandit processes each file, builds an Abstract Syntaxt Tree (AST), diff -Nru bandit-1.1.0/debian/gbp.conf bandit-1.4.0/debian/gbp.conf --- bandit-1.1.0/debian/gbp.conf 2016-10-31 08:41:01.000000000 +0000 +++ bandit-1.4.0/debian/gbp.conf 2017-02-06 19:42:37.000000000 +0000 @@ -1,8 +1,7 @@ [DEFAULT] -upstream-branch = master -debian-branch = debian/newton +debian-branch = master upstream-tag = %(version)s -compression = xz +pristine-tar = True [buildpackage] -export-dir = ../build-area/ +export-dir = ../build-area diff -Nru bandit-1.1.0/debian/patches/remove-failing-tests.patch bandit-1.4.0/debian/patches/remove-failing-tests.patch --- bandit-1.1.0/debian/patches/remove-failing-tests.patch 2016-10-31 08:41:01.000000000 +0000 +++ bandit-1.4.0/debian/patches/remove-failing-tests.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -Description: Remove failing tests -Author: Thomas Goirand -Bug-Debian: https://bugs.debian.org/841631 -Forwarded: no -Last-Update: 2016-10-31 - ---- bandit-1.1.0.orig/tests/unit/cli/test_baseline.py -+++ bandit-1.1.0/tests/unit/cli/test_baseline.py -@@ -129,25 +129,6 @@ class BanditBaselineToolTests(testtools. - # assert the system exits with code 2 - self.assertRaisesRegex(SystemExit, '2', baseline.main) - -- def test_main_git_command_failure(self): -- # Test that bandit does not run when the Git command fails -- repo_directory = self.useFixture(fixtures.TempDir()).path -- git_repo = git.Repo.init(repo_directory) -- git_repo.index.commit('Initial Commit') -- os.chdir(repo_directory) -- -- additional_content = 'additional_file.py' -- with open(additional_content, 'wt') as fd: -- fd.write(self.temp_file_contents) -- git_repo.index.add([additional_content]) -- git_repo.index.commit('Additional Content') -- -- with patch('git.Repo.commit') as mock_git_repo_commit: -- mock_git_repo_commit.side_effect = git.exc.GitCommandError('', '') -- -- # assert the system exits with code 2 -- self.assertRaisesRegex(SystemExit, '2', baseline.main) -- - def test_main_no_parent_commit(self): - # Test that bandit exits when there is no parent commit detected when - # calling main -@@ -204,27 +185,6 @@ class BanditBaselineToolTests(testtools. - # assert bandit did not run due to no git repo - self.assertEqual(return_value, (None, None, None)) - -- def test_initialize_git_command_failure(self): -- # Test that bandit does not run when the Git command fails -- repo_directory = self.useFixture(fixtures.TempDir()).path -- git_repo = git.Repo.init(repo_directory) -- git_repo.index.commit('Initial Commit') -- os.chdir(repo_directory) -- -- additional_content = 'additional_file.py' -- with open(additional_content, 'wt') as fd: -- fd.write(self.temp_file_contents) -- git_repo.index.add([additional_content]) -- git_repo.index.commit('Additional Content') -- -- with patch('git.Repo') as mock_git_repo: -- mock_git_repo.side_effect = git.exc.GitCommandNotFound -- -- return_value = baseline.initialize() -- -- # assert bandit did not run due to git command failure -- self.assertEqual(return_value, (None, None, None)) -- - def test_initialize_dirty_repo(self): - # Test that bandit does not run when the current git repository is - # 'dirty' when calling the initialize method diff -Nru bandit-1.1.0/debian/patches/series bandit-1.4.0/debian/patches/series --- bandit-1.1.0/debian/patches/series 2016-10-31 08:41:01.000000000 +0000 +++ bandit-1.4.0/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -remove-failing-tests.patch diff -Nru bandit-1.1.0/doc/source/man/bandit.rst bandit-1.4.0/doc/source/man/bandit.rst --- bandit-1.1.0/doc/source/man/bandit.rst 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/doc/source/man/bandit.rst 2017-01-06 17:33:11.000000000 +0000 @@ -88,6 +88,11 @@ 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 -Nru bandit-1.1.0/doc/source/plugins/ssl_with_bad_defaults.rst bandit-1.4.0/doc/source/plugins/ssl_with_bad_defaults.rst --- bandit-1.1.0/doc/source/plugins/ssl_with_bad_defaults.rst 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/doc/source/plugins/ssl_with_bad_defaults.rst 2017-01-06 17:33:11.000000000 +0000 @@ -1,8 +1,8 @@ --------------------------- -B502: ssl_with_bad_defaults +B503: ssl_with_bad_defaults --------------------------- .. currentmodule:: bandit.plugins.insecure_ssl_tls -.. autofunction:: ssl_with_bad_version +.. autofunction:: ssl_with_bad_defaults :noindex: diff -Nru bandit-1.1.0/doc/source/plugins/ssl_with_bad_version.rst bandit-1.4.0/doc/source/plugins/ssl_with_bad_version.rst --- bandit-1.1.0/doc/source/plugins/ssl_with_bad_version.rst 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/doc/source/plugins/ssl_with_bad_version.rst 2017-01-06 17:33:11.000000000 +0000 @@ -1,8 +1,8 @@ -------------------------- -B503: ssl_with_bad_version +B502: ssl_with_bad_version -------------------------- .. currentmodule:: bandit.plugins.insecure_ssl_tls -.. autofunction:: ssl_with_bad_defaults +.. autofunction:: ssl_with_bad_version :noindex: diff -Nru bandit-1.1.0/examples/ftplib.py bandit-1.4.0/examples/ftplib.py --- bandit-1.1.0/examples/ftplib.py 1970-01-01 00:00:00.000000000 +0000 +++ bandit-1.4.0/examples/ftplib.py 2017-01-06 17:33:11.000000000 +0000 @@ -0,0 +1,9 @@ +from ftplib import FTP + +ftp = FTP('ftp.debian.org') +ftp.login() + +ftp.cwd('debian') +ftp.retrlines('LIST') + +ftp.quit() \ No newline at end of file diff -Nru bandit-1.1.0/examples/input.py bandit-1.4.0/examples/input.py --- bandit-1.1.0/examples/input.py 1970-01-01 00:00:00.000000000 +0000 +++ bandit-1.4.0/examples/input.py 2017-01-06 17:33:11.000000000 +0000 @@ -0,0 +1 @@ +input() diff -Nru bandit-1.1.0/examples/mark_safe.py bandit-1.4.0/examples/mark_safe.py --- bandit-1.1.0/examples/mark_safe.py 1970-01-01 00:00:00.000000000 +0000 +++ bandit-1.4.0/examples/mark_safe.py 2017-01-06 17:33:11.000000000 +0000 @@ -0,0 +1,4 @@ +from django.utils import safestring + +mystr = 'Hello World' +mystr = safestring.mark_safe(mystr) diff -Nru bandit-1.1.0/examples/partial_path_process.py bandit-1.4.0/examples/partial_path_process.py --- bandit-1.1.0/examples/partial_path_process.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/examples/partial_path_process.py 2017-01-06 17:33:11.000000000 +0000 @@ -8,3 +8,6 @@ pop(['/bin/ls', '-l'], shell=False) pop('../ls -l', shell=False) + +pop('c:\hello\something', shell=False) +pop('c:/hello/something_else', shell=False) diff -Nru bandit-1.1.0/examples/subprocess_shell.py bandit-1.4.0/examples/subprocess_shell.py --- bandit-1.1.0/examples/subprocess_shell.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/examples/subprocess_shell.py 2017-01-06 17:33:11.000000000 +0000 @@ -29,3 +29,5 @@ command = "/bin/ls" + unknown_function() subprocess.Popen(command, shell=True) + +subprocess.Popen('/bin/ls && cat /etc/passwd', shell=True) diff -Nru bandit-1.1.0/examples/weak_cryptographic_key_sizes.py bandit-1.4.0/examples/weak_cryptographic_key_sizes.py --- bandit-1.1.0/examples/weak_cryptographic_key_sizes.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/examples/weak_cryptographic_key_sizes.py 2017-01-06 17:33:11.000000000 +0000 @@ -9,6 +9,8 @@ # Correct dsa.generate_private_key(key_size=2048, backend=backends.default_backend()) +ec.generate_private_key(curve=ec.SECP384R1, + backend=backends.default_backend()) rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=backends.default_backend()) @@ -29,6 +31,8 @@ # Incorrect: weak key sizes dsa.generate_private_key(key_size=1024, backend=backends.default_backend()) +ec.generate_private_key(curve=ec.SECT163R2, + backend=backends.default_backend()) rsa.generate_private_key(public_exponent=65537, key_size=1024, backend=backends.default_backend()) @@ -45,3 +49,8 @@ backends.default_backend()) DSA.generate(512) RSA.generate(512) + +# Don't crash when the size is variable +rsa.generate_private_key(public_exponent=65537, + key_size=some_key_size, + backend=backends.default_backend()) diff -Nru bandit-1.1.0/examples/yaml_load.py bandit-1.4.0/examples/yaml_load.py --- bandit-1.1.0/examples/yaml_load.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/examples/yaml_load.py 2017-01-06 17:33:11.000000000 +0000 @@ -1,3 +1,4 @@ +import json import yaml def test_yaml_load(): @@ -5,3 +6,7 @@ y = yaml.load(ystr) yaml.dump(y) y = yaml.load(ystr, Loader=yaml.SafeLoader) + +def test_json_load(): + # no issue should be found + j = json.load("{}") diff -Nru bandit-1.1.0/pylintrc bandit-1.4.0/pylintrc --- bandit-1.1.0/pylintrc 1970-01-01 00:00:00.000000000 +0000 +++ bandit-1.4.0/pylintrc 2017-01-06 17:33:11.000000000 +0000 @@ -0,0 +1,73 @@ +# The format of this file isn't really documented; just use --generate-rcfile + +[Messages Control] +# C0111: Don't require docstrings on every method +# C0301: Handled by pep8 +# C0325: Parens are required on print in py3x +# F0401: Imports are check by other linters +# W0511: TODOs in code comments are fine. +# W0142: *args and **kwargs are fine. +# W0622: Redefining id is fine. + +# TODO(browne): fix these in the future +# C0103: invalid-name +# E1101: no-member +# R0204: redefined-variable-type +# R0902: too-many-instance-attributes +# R0912: too-many-branches +# R0913: too-many-arguments +# R0914: too-many-locals +# R0915: too-many-statements +# W0110: deprecated-lambda +# W0141: bad-builtin +# W0201: attribute-defined-outside-init +# W0212: protected-access +# W0401: wildcard-import +# W0603: global-statement +# W0612: unused-variable +# W0613: unused-argument +# W0621: redefined-outer-name +# W0703: broad-except +disable=C0111,C0301,C0325,F0401,W0511,W0142,W0622,C0103,E1101,R0204,R0902,R0912,R0913,R0914,R0915,W0110,W0141,W0201,W0401,W0603,W0212,W0612,W0613,W0621,W0703 + +[Basic] +# Variable names can be 1 to 31 characters long, with lowercase and underscores +variable-rgx=[a-z_][a-z0-9_]{0,30}$ + +# Argument names can be 2 to 31 characters long, with lowercase and underscores +argument-rgx=[a-z_][a-z0-9_]{1,30}$ + +# Method names should be at least 3 characters long +# and be lowecased with underscores +method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|tearDown)$ + +# Module names matching manila-* are ok (files in bin/) +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|(manila-[a-z0-9_-]+))$ + +# Don't require docstrings on tests. +no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$ + +[Design] +max-public-methods=100 +min-public-methods=0 +max-args=6 + +[Variables] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +# _ is used by our localization +additional-builtins=_ + +[Similarities] +# Minimum lines number of a similarity. +min-similarity-lines=10 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=yes diff -Nru bandit-1.1.0/README.rst bandit-1.4.0/README.rst --- bandit-1.1.0/README.rst 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/README.rst 2017-01-06 17:33:11.000000000 +0000 @@ -1,6 +1,10 @@ Bandit ====== +.. image:: http://governance.openstack.org/badges/bandit.svg + :target: http://governance.openstack.org/reference/tags/index.html + :alt: Bandit team and repository tags + .. image:: https://img.shields.io/pypi/v/bandit.svg :target: https://pypi.python.org/pypi/bandit/ :alt: Latest Version @@ -28,12 +32,12 @@ -------- Bandit is a tool designed to find common security issues in Python code. To do this Bandit processes each file, builds an AST from it, and runs appropriate -plugins against the AST nodes. Once Bandit has finished scanning all the files +plugins against the AST nodes. Once Bandit has finished scanning all the files it generates a report. Installation ------------ -Bandit is distributed on PyPI. The best way to install it is with pip: +Bandit is distributed on PyPI. The best way to install it is with pip: Create a virtual environment (optional):: @@ -43,16 +47,16 @@ Install Bandit:: pip install bandit - # Or, if you're working with a Python 3 project - pip3.4 install bandit + # Or if you're working with a Python 3.5 project + pip3.5 install bandit Run Bandit:: bandit -r path/to/your/code -Bandit can also be installed from source. To do so, download the source -tarball from PyPI, then install it:: +Bandit can also be installed from source. To do so, download the source tarball +from PyPI, then install it:: python setup.py install @@ -68,11 +72,16 @@ bandit examples/*.py -n 3 -lll -Bandit can be run with profiles. To run Bandit against the examples directory +Bandit can be run with profiles. To run Bandit against the examples directory 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 @@ -163,6 +172,7 @@ B319 xml_bad_pulldom B320 xml_bad_etree B321 ftplib + B322 input B401 import_telnetlib B402 import_ftplib B403 import_pickle @@ -206,14 +216,14 @@ Per Project Command Line Args ----------------------------- Projects may include a `.bandit` file that specifies command line arguments -that should be supplied for that project. The currently supported arguments +that should be supplied for that project. The currently supported arguments are: - exclude: comma separated list of excluded paths - skips: comma separated list of tests to skip - tests: comma separated list of tests to run -To use this, put a .bandit file in your project's directory. For example: +To use this, put a .bandit file in your project's directory. For example: :: @@ -244,7 +254,7 @@ Vulnerability tests or "plugins" are defined in files in the plugins directory. Tests are written in Python and are autodiscovered from the plugins directory. -Each test can examine one or more type of Python statements. Tests are marked +Each test can examine one or more type of Python statements. Tests are marked with the types of Python statements they examine (for example: function call, string, import, etc). @@ -295,7 +305,7 @@ - `excluded_files`: The list of files that were excluded from the scope Plugins tend to take advantage of the `bandit.checks` decorator which allows -the author to register a check for a particular type of AST node. For example, +the author to register a check for a particular type of AST node. For example :: @@ -330,8 +340,8 @@ Contributing ------------ -Contributions to Bandit are always welcome! We can be found on #openstack-security -on Freenode IRC. +Contributions to Bandit are always welcome! We can be found on +#openstack-security on Freenode IRC. The best way to get started with Bandit is to grab the source:: @@ -342,7 +352,8 @@ pip install tox tox -e pep8 tox -e py27 - tox -e py34 + tox -e py35 + tox -e docs tox -e cover Reporting Bugs @@ -355,17 +366,17 @@ The answer to this question depends on the project(s) you will be running Bandit against. If your project is only compatible with Python 2.7, you should install Bandit to run under Python 2.7. If your project is only -compatible with Python 3.4, then use 3.4. If your project supports both, you -*could* run Bandit with both versions but you don't have to. +compatible with Python 3.5, then use 3.5 respectively. If your project supports +both, you *could* run Bandit with both versions but you don't have to. Bandit uses the `ast` module from Python's standard library in order to analyze your Python code. The `ast` module is only able to parse Python code that is valid in the version of the interpreter from which it is imported. In other words, if you try to use Python 2.7's `ast` module to parse code written -for 3.4 that uses, for example, `yield from` with asyncio, then you'll have +for 3.5 that uses, for example, `yield from` with asyncio, then you'll have syntax errors that will prevent Bandit from working properly. Alternatively, if you are relying on 2.7's octal notation of `0777` then you'll have a syntax -error if you run Bandit on 3.4. +error if you run Bandit on 3.x. References diff -Nru bandit-1.1.0/releasenotes/source/conf.py bandit-1.4.0/releasenotes/source/conf.py --- bandit-1.1.0/releasenotes/source/conf.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/releasenotes/source/conf.py 2017-01-06 17:33:11.000000000 +0000 @@ -272,3 +272,6 @@ # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False + +# -- Options for Internationalization output ------------------------------ +locale_dirs = ['locale/'] diff -Nru bandit-1.1.0/requirements.txt bandit-1.4.0/requirements.txt --- bandit-1.1.0/requirements.txt 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/requirements.txt 2017-01-06 17:33:11.000000000 +0000 @@ -2,6 +2,6 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. GitPython>=1.0.1 # BSD License (3 clause) -PyYAML>=3.1.0 # MIT +PyYAML>=3.10.0 # MIT six>=1.9.0 # MIT -stevedore>=1.16.0 # Apache-2.0 +stevedore>=1.17.1 # Apache-2.0 diff -Nru bandit-1.1.0/setup.cfg bandit-1.4.0/setup.cfg --- bandit-1.1.0/setup.cfg 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/setup.cfg 2017-01-06 17:33:11.000000000 +0000 @@ -18,7 +18,6 @@ Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Topic :: Security diff -Nru bandit-1.1.0/test-requirements.txt bandit-1.4.0/test-requirements.txt --- bandit-1.1.0/test-requirements.txt 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/test-requirements.txt 2017-01-06 17:33:11.000000000 +0000 @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -coverage>=3.6 # Apache-2.0 +coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD hacking<0.10,>=0.9.2 mock>=2.0 # BSD @@ -11,7 +11,9 @@ testtools>=1.4.0 # MIT oslotest>=1.10.0 # Apache-2.0 -sphinx!=1.3b1,<1.3,>=1.2.1 # BSD -oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 +sphinx!=1.3b1,<1.4,>=1.2.1 # BSD +oslosphinx>=4.7.0 # Apache-2.0 beautifulsoup4 # MIT -reno>=1.8.0 # Apache2 +reno>=1.8.0 # Apache-2.0 + +pylint==1.4.5 # GPLv2 diff -Nru bandit-1.1.0/tests/functional/test_functional.py bandit-1.4.0/tests/functional/test_functional.py --- bandit-1.1.0/tests/functional/test_functional.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/functional/test_functional.py 2017-01-06 17:33:11.000000000 +0000 @@ -133,6 +133,11 @@ expect = {'SEVERITY': {'MEDIUM': 3}, 'CONFIDENCE': {'HIGH': 3}} self.check_example('eval.py', expect) + def test_mark_safe(self): + '''Test the `mark_safe` example.''' + expect = {'SEVERITY': {'MEDIUM': 1}, 'CONFIDENCE': {'HIGH': 1}} + self.check_example('mark_safe.py', expect) + def test_exec(self): '''Test the `exec` example.''' filename = 'exec-{}.py' @@ -187,6 +192,11 @@ expect = {'SEVERITY': {'HIGH': 2}, 'CONFIDENCE': {'HIGH': 2}} self.check_example('telnetlib.py', expect) + def test_ftp_usage(self): + '''Test for `import ftplib` and FTP.* calls.''' + expect = {'SEVERITY': {'HIGH': 2}, 'CONFIDENCE': {'HIGH': 2}} + self.check_example('ftplib.py', expect) + def test_imports(self): '''Test for dangerous imports.''' expect = {'SEVERITY': {'LOW': 2}, 'CONFIDENCE': {'HIGH': 2}} @@ -227,7 +237,7 @@ def test_os_popen(self): '''Test for `os.popen`.''' - expect = {'SEVERITY': {'LOW': 7, 'MEDIUM': 1, 'HIGH': 1}, + expect = {'SEVERITY': {'LOW': 8, 'MEDIUM': 0, 'HIGH': 1}, 'CONFIDENCE': {'HIGH': 9}} self.check_example('os-popen.py', expect) @@ -256,7 +266,7 @@ def test_popen_wrappers(self): '''Test the `popen2` and `commands` modules.''' - expect = {'SEVERITY': {'MEDIUM': 7}, 'CONFIDENCE': {'HIGH': 7}} + expect = {'SEVERITY': {'LOW': 7}, 'CONFIDENCE': {'HIGH': 7}} self.check_example('popen_wrappers.py', expect) def test_random_module(self): @@ -297,8 +307,8 @@ def test_subprocess_shell(self): '''Test for `subprocess.Popen` with `shell=True`.''' expect = { - 'SEVERITY': {'HIGH': 3, 'MEDIUM': 2, 'LOW': 12}, - 'CONFIDENCE': {'HIGH': 16, 'LOW': 1} + 'SEVERITY': {'HIGH': 3, 'MEDIUM': 1, 'LOW': 14}, + 'CONFIDENCE': {'HIGH': 17, 'LOW': 1} } self.check_example('subprocess_shell.py', expect) @@ -318,7 +328,7 @@ def test_wildcard_injection(self): '''Test for wildcard injection in shell commands.''' expect = { - 'SEVERITY': {'HIGH': 4, 'MEDIUM': 4, 'LOW': 6}, + 'SEVERITY': {'HIGH': 4, 'MEDIUM': 0, 'LOW': 10}, 'CONFIDENCE': {'MEDIUM': 5, 'HIGH': 9} } self.check_example('wildcard-injection.py', expect) @@ -409,8 +419,8 @@ def test_partial_path(self): '''Test process spawning with partial file paths.''' - expect = {'SEVERITY': {'LOW': 9}, - 'CONFIDENCE': {'HIGH': 9}} + expect = {'SEVERITY': {'LOW': 11}, + 'CONFIDENCE': {'HIGH': 11}} self.check_example('partial_path_process.py', expect) @@ -455,8 +465,8 @@ def test_weak_cryptographic_key(self): '''Test for weak key sizes.''' expect = { - 'SEVERITY': {'MEDIUM': 5, 'HIGH': 4}, - 'CONFIDENCE': {'HIGH': 9} + 'SEVERITY': {'MEDIUM': 6, 'HIGH': 4}, + 'CONFIDENCE': {'HIGH': 10} } self.check_example('weak_cryptographic_key_sizes.py', expect) @@ -530,5 +540,12 @@ self.b_mgr.populate_baseline(json) self.run_example('flask_debug.py') - self.assertEqual(len(self.b_mgr.baseline), 1) - self.assertEqual(self.b_mgr.get_issue_list(), {}) + self.assertEqual(1, len(self.b_mgr.baseline)) + self.assertEqual({}, self.b_mgr.get_issue_list()) + + def test_blacklist_input(self): + expect = { + 'SEVERITY': {'HIGH': 1}, + 'CONFIDENCE': {'HIGH': 1} + } + self.check_example('input.py', expect) diff -Nru bandit-1.1.0/tests/functional/test_runtime.py bandit-1.4.0/tests/functional/test_runtime.py --- bandit-1.1.0/tests/functional/test_runtime.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/functional/test_runtime.py 2017-01-06 17:33:11.000000000 +0000 @@ -21,10 +21,10 @@ 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 @@ 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 @@ 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' diff -Nru bandit-1.1.0/tests/unit/cli/test_baseline.py bandit-1.4.0/tests/unit/cli/test_baseline.py --- bandit-1.1.0/tests/unit/cli/test_baseline.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/cli/test_baseline.py 2017-01-06 17:33:11.000000000 +0000 @@ -19,7 +19,7 @@ import fixtures import git -from mock import patch +import mock import testtools import bandit.cli.baseline as baseline @@ -117,8 +117,8 @@ git_repo.index.add(branch['files']) git_repo.index.commit(branch['name']) - self.assertEqual(subprocess.call(baseline_command), - branch['expected_return']) + self.assertEqual(branch['expected_return'], + subprocess.call(baseline_command)) def test_main_non_repo(self): # Test that bandit gracefully exits when there is no git repository @@ -142,8 +142,9 @@ git_repo.index.add([additional_content]) git_repo.index.commit('Additional Content') - with patch('git.Repo.commit') as mock_git_repo_commit: - mock_git_repo_commit.side_effect = git.exc.GitCommandError('', '') + with mock.patch('git.Repo.commit') as mock_git_repo_commit: + mock_git_repo_commit.side_effect = git.exc.GitCommandError( + 'commit', '') # assert the system exits with code 2 self.assertRaisesRegex(SystemExit, '2', baseline.main) @@ -175,9 +176,9 @@ git_repo.index.add([additional_content]) git_repo.index.commit('Additional Content') - with patch('subprocess.check_output') as mock_subprocess_check_output: + with mock.patch('subprocess.check_output') as mock_check_output: mock_bandit_cmd = 'bandit_mock -b temp_file.txt' - mock_subprocess_check_output.side_effect = ( + mock_check_output.side_effect = ( subprocess.CalledProcessError('3', mock_bandit_cmd) ) @@ -188,7 +189,7 @@ def test_init_logger(self): # Test whether the logger was initialized when calling init_logger baseline.init_logger() - logger = baseline.logger + logger = baseline.LOG # verify that logger was initialized self.assertIsNotNone(logger) @@ -202,7 +203,7 @@ return_value = baseline.initialize() # assert bandit did not run due to no git repo - self.assertEqual(return_value, (None, None, None)) + self.assertEqual((None, None, None), return_value) def test_initialize_git_command_failure(self): # Test that bandit does not run when the Git command fails @@ -217,13 +218,13 @@ git_repo.index.add([additional_content]) git_repo.index.commit('Additional Content') - with patch('git.Repo') as mock_git_repo: - mock_git_repo.side_effect = git.exc.GitCommandNotFound + with mock.patch('git.Repo') as mock_git_repo: + mock_git_repo.side_effect = git.exc.GitCommandNotFound('clone', '') return_value = baseline.initialize() # assert bandit did not run due to git command failure - self.assertEqual(return_value, (None, None, None)) + self.assertEqual((None, None, None), return_value) def test_initialize_dirty_repo(self): # Test that bandit does not run when the current git repository is @@ -241,9 +242,9 @@ return_value = baseline.initialize() # assert bandit did not run due to dirty repo - self.assertEqual(return_value, (None, None, None)) + self.assertEqual((None, None, None), return_value) - @patch('sys.argv', ['bandit', '-f', 'txt', 'test']) + @mock.patch('sys.argv', ['bandit', '-f', 'txt', 'test']) def test_initialize_existing_report_file(self): # Test that bandit does not run when the output file exists (and the # provided output format does not match the default format) when @@ -261,10 +262,10 @@ return_value = baseline.initialize() # assert bandit did not run due to existing report file - self.assertEqual(return_value, (None, None, None)) + self.assertEqual((None, None, None), return_value) - @patch('bandit.cli.baseline.bandit_args', ['-o', - 'bandit_baseline_result']) + @mock.patch('bandit.cli.baseline.bandit_args', ['-o', + 'bandit_baseline_result']) def test_initialize_with_output_argument(self): # Test that bandit does not run when the '-o' (output) argument is # specified @@ -276,7 +277,7 @@ return_value = baseline.initialize() # assert bandit did not run due to provided -o (--ouput) argument - self.assertEqual(return_value, (None, None, None)) + self.assertEqual((None, None, None), return_value) def test_initialize_existing_temp_file(self): # Test that bandit does not run when the temporary output file exists @@ -294,4 +295,4 @@ return_value = baseline.initialize() # assert bandit did not run due to existing temporary report file - self.assertEqual(return_value, (None, None, None)) + self.assertEqual((None, None, None), return_value) diff -Nru bandit-1.1.0/tests/unit/cli/test_config_generator.py bandit-1.4.0/tests/unit/cli/test_config_generator.py --- bandit-1.1.0/tests/unit/cli/test_config_generator.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/cli/test_config_generator.py 2017-01-06 17:33:11.000000000 +0000 @@ -61,9 +61,8 @@ class BanditConfigGeneratorTests(testtools.TestCase): @mock.patch('sys.argv', ['bandit-config-generator']) def test_parse_args_no_defaults(self): - # Test that the config generator does not show default plugin settings - return_value = config_generator.parse_args() - self.assertFalse(return_value.show_defaults) + # Without arguments, the generator should just show help and exit + self.assertRaises(SystemExit, config_generator.parse_args) @mock.patch('sys.argv', ['bandit-config-generator', '--show-defaults']) def test_parse_args_show_defaults(self): diff -Nru bandit-1.1.0/tests/unit/cli/test_main.py bandit-1.4.0/tests/unit/cli/test_main.py --- bandit-1.1.0/tests/unit/cli/test_main.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/cli/test_main.py 2017-01-06 17:33:11.000000000 +0000 @@ -16,7 +16,7 @@ import os import fixtures -from mock import patch +import mock import testtools from bandit.cli import main as bandit @@ -156,43 +156,45 @@ option_name = 'aggregate' self.assertIsNone(bandit._log_option_source(None, None, option_name)) - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test']) def test_main_config_unopenable(self): # Test that bandit exits when a config file cannot be opened - with patch('bandit.core.config.__init__') as mock_bandit_config: + with mock.patch('bandit.core.config.__init__') as mock_bandit_config: mock_bandit_config.side_effect = utils.ConfigError('', '') # assert a SystemExit with code 2 self.assertRaisesRegex(SystemExit, '2', bandit.main) - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test']) def test_main_invalid_config(self): # Test that bandit exits when a config file contains invalid YAML # content - with patch('bandit.core.config.BanditConfig.__init__' - ) as mock_bandit_config: + with mock.patch('bandit.core.config.BanditConfig.__init__' + ) as mock_bandit_config: mock_bandit_config.side_effect = utils.ConfigError('', '') # assert a SystemExit with code 2 self.assertRaisesRegex(SystemExit, '2', bandit.main) - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test']) def test_main_handle_ini_options(self): # Test that bandit handles cmdline args from a bandit.yaml file temp_directory = self.useFixture(fixtures.TempDir()).path os.chdir(temp_directory) with open('bandit.yaml', 'wt') as fd: fd.write(bandit_config_content) - with patch('bandit.cli.main._get_options_from_ini') as mock_get_opts: + with mock.patch('bandit.cli.main._get_options_from_ini' + ) as mock_get_opts: mock_get_opts.return_value = {"exclude": "/tmp", "skips": "skip_test", "tests": "some_test"} - with patch('bandit.cli.main.logger.error') as err_mock: + with mock.patch('bandit.cli.main.LOG.error') as err_mock: # SystemExit with code 2 when test not found in profile self.assertRaisesRegex(SystemExit, '2', bandit.main) self.assertEqual(str(err_mock.call_args[0][0]), 'Unknown test found in profile: some_test') - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-t', 'badID', 'test']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-t', 'badID', + 'test']) def test_main_unknown_tests(self): # Test that bandit exits when an invalid test ID is provided temp_directory = self.useFixture(fixtures.TempDir()).path @@ -202,7 +204,8 @@ # assert a SystemExit with code 2 self.assertRaisesRegex(SystemExit, '2', bandit.main) - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-s', 'badID', 'test']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-s', 'badID', + 'test']) def test_main_unknown_skip_tests(self): # Test that bandit exits when an invalid test ID is provided to skip temp_directory = self.useFixture(fixtures.TempDir()).path @@ -212,7 +215,8 @@ # assert a SystemExit with code 2 self.assertRaisesRegex(SystemExit, '2', bandit.main) - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-p', 'bad', 'test']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-p', 'bad', + 'test']) def test_main_profile_not_found(self): # Test that bandit exits when an invalid profile name is provided temp_directory = self.useFixture(fixtures.TempDir()).path @@ -220,14 +224,14 @@ with open('bandit.yaml', 'wt') as fd: fd.write(bandit_config_content) # assert a SystemExit with code 2 - with patch('bandit.cli.main.logger.error') as err_mock: + with mock.patch('bandit.cli.main.LOG.error') as err_mock: self.assertRaisesRegex(SystemExit, '2', bandit.main) self.assertEqual( str(err_mock.call_args[0][0]), 'Unable to find profile (bad) in config file: bandit.yaml') - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-b', 'base.json', - 'test']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-b', 'base.json', + 'test']) def test_main_baseline_ioerror(self): # Test that bandit exits when encountering an IOError while reading # baseline data @@ -237,14 +241,14 @@ fd.write(bandit_config_content) with open('base.json', 'wt') as fd: fd.write(bandit_baseline_content) - with patch('bandit.core.manager.BanditManager.populate_baseline' - ) as mock_mgr_pop_bl: + with mock.patch('bandit.core.manager.BanditManager.populate_baseline' + ) as mock_mgr_pop_bl: mock_mgr_pop_bl.side_effect = IOError # assert a SystemExit with code 2 self.assertRaisesRegex(SystemExit, '2', bandit.main) - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-b', 'base.json', - '-f', 'csv', 'test']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', '-b', 'base.json', + '-f', 'csv', 'test']) def test_main_invalid_output_format(self): # Test that bandit exits when an invalid output format is selected temp_directory = self.useFixture(fixtures.TempDir()).path @@ -256,28 +260,30 @@ # assert a SystemExit with code 2 self.assertRaisesRegex(SystemExit, '2', bandit.main) - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test', '-o', 'output']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test', '-o', + 'output']) def test_main_exit_with_results(self): # Test that bandit exits when there are results temp_directory = self.useFixture(fixtures.TempDir()).path os.chdir(temp_directory) with open('bandit.yaml', 'wt') as fd: fd.write(bandit_config_content) - with patch('bandit.core.manager.BanditManager.results_count' - ) as mock_mgr_results_ct: + with mock.patch('bandit.core.manager.BanditManager.results_count' + ) as mock_mgr_results_ct: mock_mgr_results_ct.return_value = 1 # assert a SystemExit with code 1 self.assertRaisesRegex(SystemExit, '1', bandit.main) - @patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test', '-o', 'output']) + @mock.patch('sys.argv', ['bandit', '-c', 'bandit.yaml', 'test', '-o', + 'output']) def test_main_exit_with_no_results(self): # Test that bandit exits when there are no results temp_directory = self.useFixture(fixtures.TempDir()).path os.chdir(temp_directory) with open('bandit.yaml', 'wt') as fd: fd.write(bandit_config_content) - with patch('bandit.core.manager.BanditManager.results_count' - ) as mock_mgr_results_ct: + with mock.patch('bandit.core.manager.BanditManager.results_count' + ) as mock_mgr_results_ct: mock_mgr_results_ct.return_value = 0 # assert a SystemExit with code 0 self.assertRaisesRegex(SystemExit, '0', bandit.main) diff -Nru bandit-1.1.0/tests/unit/core/test_config.py bandit-1.4.0/tests/unit/core/test_config.py --- bandit-1.1.0/tests/unit/core/test_config.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/core/test_config.py 2017-01-06 17:33:11.000000000 +0000 @@ -233,7 +233,7 @@ "'bandit-config-generator' can help you with this. Support for " "legacy configs will be removed in a future bandit version.") - with mock.patch('bandit.core.config.logger.warn') as m: + with mock.patch('bandit.core.config.LOG.warning') as m: self.config._config = {"profiles": {}} self.config.validate('') self.assertEqual((msg, ''), m.call_args_list[0][0]) @@ -261,4 +261,4 @@ try: self.config = config.BanditConfig(f.name) except utils.ConfigError as e: - self.assertTrue("Error parsing file." in e.message) + self.assertIn("Error parsing file.", e.message) diff -Nru bandit-1.1.0/tests/unit/core/test_issue.py bandit-1.4.0/tests/unit/core/test_issue.py --- bandit-1.1.0/tests/unit/core/test_issue.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/core/test_issue.py 2017-01-06 17:33:11.000000000 +0000 @@ -40,14 +40,14 @@ test_issue = _get_issue_instance() test_issue_dict = test_issue.as_dict(with_code=False) self.assertIsInstance(test_issue_dict, dict) - self.assertEqual(test_issue_dict['filename'], 'code.py') - self.assertEqual(test_issue_dict['test_name'], 'bandit_plugin') - self.assertEqual(test_issue_dict['test_id'], 'B999') - self.assertEqual(test_issue_dict['issue_severity'], 'MEDIUM') - self.assertEqual(test_issue_dict['issue_confidence'], 'MEDIUM') - self.assertEqual(test_issue_dict['issue_text'], 'Test issue') - self.assertEqual(test_issue_dict['line_number'], 1) - self.assertEqual(test_issue_dict['line_range'], []) + self.assertEqual('code.py', test_issue_dict['filename']) + self.assertEqual('bandit_plugin', test_issue_dict['test_name']) + self.assertEqual('B999', test_issue_dict['test_id']) + self.assertEqual('MEDIUM', test_issue_dict['issue_severity']) + self.assertEqual('MEDIUM', test_issue_dict['issue_confidence']) + self.assertEqual('Test issue', test_issue_dict['issue_text']) + self.assertEqual(1, test_issue_dict['line_number']) + self.assertEqual([], test_issue_dict['line_range']) def test_issue_filter_severity(self): levels = [bandit.LOW, bandit.MEDIUM, bandit.HIGH] diff -Nru bandit-1.1.0/tests/unit/core/test_test_set.py bandit-1.4.0/tests/unit/core/test_test_set.py --- bandit-1.1.0/tests/unit/core/test_test_set.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/core/test_test_set.py 2017-01-06 17:33:11.000000000 +0000 @@ -50,7 +50,7 @@ def setUp(self): super(BanditTestSetTests, self).setUp() - mngr = self._make_test_manager(mock.MagicMock) + mngr = self._make_test_manager(mock.Mock) self.patchExtMan = mock.patch('stevedore.extension.ExtensionManager') self.mockExtMan = self.patchExtMan.start() self.mockExtMan.return_value = mngr @@ -101,6 +101,13 @@ self.assertEqual(len(ts.get_tests('ImportFrom')), 0) self.assertEqual(len(ts.get_tests('Call')), 0) + def test_profile_exclude_builtin_blacklist_specific(self): + profile = {'exclude': ['B302', 'B401']} + ts = test_set.BanditTestSet(self.config, profile) + self.assertEqual(len(ts.get_tests('Import')), 0) + self.assertEqual(len(ts.get_tests('ImportFrom')), 0) + self.assertEqual(len(ts.get_tests('Call')), 0) + def test_profile_filter_blacklist_none(self): ts = test_set.BanditTestSet(self.config) blacklist = ts.get_tests('Import')[0] @@ -148,6 +155,6 @@ ts = test_set.BanditTestSet(self.config, profile) blacklist = ts.get_tests('Call')[0] - self.assertFalse('Import' in blacklist._config) - self.assertFalse('ImportFrom' in blacklist._config) + self.assertNotIn('Import', blacklist._config) + self.assertNotIn('ImportFrom', blacklist._config) self.assertEqual(len(blacklist._config['Call']), 1) diff -Nru bandit-1.1.0/tests/unit/formatters/test_html.py bandit-1.4.0/tests/unit/formatters/test_html.py --- bandit-1.1.0/tests/unit/formatters/test_html.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/formatters/test_html.py 2017-01-06 17:33:11.000000000 +0000 @@ -13,10 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from collections import OrderedDict +import collections import tempfile -from bs4 import BeautifulSoup +import bs4 import mock import testtools @@ -46,12 +46,12 @@ self.manager, tmp_file, bandit.LOW, bandit.LOW) with open(self.tmp_fname) as f: - soup = BeautifulSoup(f.read(), 'html.parser') - skipped_span = soup.find_all('span', id='skipped')[0] + soup = bs4.BeautifulSoup(f.read(), 'html.parser') + skipped = soup.find_all('div', id='skipped')[0] - self.assertEqual(1, len(soup.find_all('span', id='skipped'))) - self.assertIn('abc.py', skipped_span.text) - self.assertIn('File is bad', skipped_span.text) + self.assertEqual(1, len(soup.find_all('div', id='skipped'))) + self.assertIn('abc.py', skipped.text) + self.assertIn('File is bad', skipped.text) @mock.patch('bandit.core.issue.Issue.get_code') @mock.patch('bandit.core.manager.BanditManager.get_issue_list') @@ -74,24 +74,23 @@ issue_y = _get_issue_instance() - get_issue_list.return_value = OrderedDict([(issue_a, [issue_x, - issue_y]), - (issue_b, [issue_x]), - (issue_c, [issue_y])]) + get_issue_list.return_value = collections.OrderedDict( + [(issue_a, [issue_x, issue_y]), + (issue_b, [issue_x]), (issue_c, [issue_y])]) tmp_file = open(self.tmp_fname, 'w') b_html.report( self.manager, tmp_file, bandit.LOW, bandit.LOW) with open(self.tmp_fname) as f: - soup = BeautifulSoup(f.read(), 'html.parser') + soup = bs4.BeautifulSoup(f.read(), 'html.parser') self.assertEqual('1000', soup.find_all('span', id='loc')[0].text) self.assertEqual('50', soup.find_all('span', id='nosec')[0].text) - issue1 = soup.find_all('span', id='issue-0')[0] - issue2 = soup.find_all('span', id='issue-1')[0] - issue3 = soup.find_all('span', id='issue-2')[0] + issue1 = soup.find_all('div', id='issue-0')[0] + issue2 = soup.find_all('div', id='issue-1')[0] + issue3 = soup.find_all('div', id='issue-2')[0] # make sure the class has been applied properly self.assertEqual(1, len(issue1.find_all( @@ -104,23 +103,27 @@ 'div', {'class': 'issue-sev-high'}))) # issue1 has a candidates section with 2 candidates in it - self.assertEqual(1, len(issue1.find_all('span', id='candidates'))) - self.assertEqual(2, len(issue1.find_all('span', id='candidate'))) + self.assertEqual(1, len(issue1.find_all('div', + {'class': 'candidates'}))) + self.assertEqual(2, len(issue1.find_all('div', + {'class': 'candidate'}))) # issue2 doesn't have candidates - self.assertEqual(0, len(issue2.find_all('span', id='candidates'))) - self.assertEqual(0, len(issue2.find_all('span', id='candidate'))) + self.assertEqual(0, len(issue2.find_all('div', + {'class': 'candidates'}))) + self.assertEqual(0, len(issue2.find_all('div', + {'class': 'candidate'}))) # issue1 doesn't have code issue 2 and 3 do - self.assertEqual(0, len(issue1.find_all('span', id='code'))) - self.assertEqual(1, len(issue2.find_all('span', id='code'))) - self.assertEqual(1, len(issue3.find_all('span', id='code'))) + self.assertEqual(0, len(issue1.find_all('div', {'class': 'code'}))) + self.assertEqual(1, len(issue2.find_all('div', {'class': 'code'}))) + self.assertEqual(1, len(issue3.find_all('div', {'class': 'code'}))) # issue2 code and issue1 first candidate have code - self.assertIn('some code', issue1.find_all('span', - id='candidate')[0].text) - self.assertIn('some code', issue2.find_all('span', - id='code')[0].text) + element1 = issue1.find_all('div', {'class': 'candidate'}) + self.assertIn('some code', element1[0].text) + element2 = issue2.find_all('div', {'class': 'code'}) + self.assertIn('some code', element2[0].text) # make sure correct things are being output in issues self.assertIn('AAAAAAA:', issue1.text) diff -Nru bandit-1.1.0/tests/unit/formatters/test_json.py bandit-1.4.0/tests/unit/formatters/test_json.py --- bandit-1.1.0/tests/unit/formatters/test_json.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/formatters/test_json.py 2017-01-06 17:33:11.000000000 +0000 @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from collections import OrderedDict +import collections import json import tempfile @@ -72,8 +72,8 @@ self.manager.scores = [{'SEVERITY': [0] * len(constants.RANKING), 'CONFIDENCE': [0] * len(constants.RANKING)}] - get_issue_list.return_value = OrderedDict([(self.issue, - self.candidates)]) + get_issue_list.return_value = collections.OrderedDict( + [(self.issue, self.candidates)]) tmp_file = open(self.tmp_fname, 'w') b_json.report(self.manager, tmp_file, self.issue.severity, @@ -93,7 +93,4 @@ self.assertEqual(self.context['linerange'], data['results'][0]['line_range']) self.assertEqual(self.check_name, data['results'][0]['test_name']) - self.assertEqual('binding.py', data['stats'][0]['filename']) - self.assertEqual({'CONFIDENCE': 0, 'SEVERITY': 0}, - data['stats'][0]['score']) self.assertIn('candidates', data['results'][0]) diff -Nru bandit-1.1.0/tests/unit/formatters/test_screen.py bandit-1.4.0/tests/unit/formatters/test_screen.py --- bandit-1.1.0/tests/unit/formatters/test_screen.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/formatters/test_screen.py 2017-01-06 17:33:11.000000000 +0000 @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -from collections import OrderedDict +import collections import tempfile import mock @@ -46,27 +46,27 @@ _issue.confidence.capitalize()), "{} Location: {}:{}{}". format(_indent_val, _issue.fname, _issue.lineno, - screen.color['DEFAULT'])] + screen.COLOR['DEFAULT'])] if _code: return_val.append("{}{}".format(_indent_val, _code)) return '\n'.join(return_val) issue_text = screen._output_issue_str(issue, indent_val) expected_return = _template(issue, indent_val, 'DDDDDDD', - screen.color['MEDIUM']) + screen.COLOR['MEDIUM']) self.assertEqual(expected_return, issue_text) issue_text = screen._output_issue_str(issue, indent_val, show_code=False) expected_return = _template(issue, indent_val, '', - screen.color['MEDIUM']) + screen.COLOR['MEDIUM']) self.assertEqual(expected_return, issue_text) issue.lineno = '' issue_text = screen._output_issue_str(issue, indent_val, show_lineno=False) expected_return = _template(issue, indent_val, 'DDDDDDD', - screen.color['MEDIUM']) + screen.COLOR['MEDIUM']) self.assertEqual(expected_return, issue_text) @mock.patch('bandit.core.manager.BanditManager.get_issue_list') @@ -77,7 +77,7 @@ (tmp_fd, self.tmp_fname) = tempfile.mkstemp() self.manager.out_file = self.tmp_fname - get_issue_list.return_value = OrderedDict() + get_issue_list.return_value = collections.OrderedDict() with mock.patch('bandit.formatters.screen.do_print') as m: tmp_file = open(self.tmp_fname, 'w') screen.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, @@ -183,9 +183,8 @@ issue_z = _get_issue_instance() issue_z.fname = 'z' - get_issue_list.return_value = OrderedDict([(issue_a, [issue_x]), - (issue_b, [issue_y, - issue_z])]) + get_issue_list.return_value = collections.OrderedDict( + [(issue_a, [issue_x]), (issue_b, [issue_y, issue_z])]) # Validate that we're outputting the correct issues indent_val = ' ' * 10 diff -Nru bandit-1.1.0/tests/unit/formatters/test_text.py bandit-1.4.0/tests/unit/formatters/test_text.py --- bandit-1.1.0/tests/unit/formatters/test_text.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/formatters/test_text.py 2017-01-06 17:33:11.000000000 +0000 @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -from collections import OrderedDict +import collections import tempfile import mock @@ -73,7 +73,7 @@ (tmp_fd, self.tmp_fname) = tempfile.mkstemp() self.manager.out_file = self.tmp_fname - get_issue_list.return_value = OrderedDict() + get_issue_list.return_value = collections.OrderedDict() tmp_file = open(self.tmp_fname, 'w') b_text.report(self.manager, tmp_file, bandit.LOW, bandit.LOW, lines=5) @@ -170,9 +170,8 @@ issue_z = _get_issue_instance() issue_z.fname = 'z' - get_issue_list.return_value = OrderedDict([(issue_a, [issue_x]), - (issue_b, [issue_y, - issue_z])]) + get_issue_list.return_value = collections.OrderedDict( + [(issue_a, [issue_x]), (issue_b, [issue_y, issue_z])]) # Validate that we're outputting the correct issues indent_val = ' ' * 10 diff -Nru bandit-1.1.0/tests/unit/formatters/test_xml.py bandit-1.4.0/tests/unit/formatters/test_xml.py --- bandit-1.1.0/tests/unit/formatters/test_xml.py 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tests/unit/formatters/test_xml.py 2017-01-06 17:33:11.000000000 +0000 @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from collections import defaultdict +import collections import tempfile from xml.etree import cElementTree as ET @@ -52,7 +52,7 @@ d = {t.tag: {} if t.attrib else None} children = list(t) if children: - dd = defaultdict(list) + dd = collections.defaultdict(list) for dc in map(self._xml_to_dict, children): for k, v in six.iteritems(dc): dd[k].append(v) diff -Nru bandit-1.1.0/tools/tox_install.sh bandit-1.4.0/tools/tox_install.sh --- bandit-1.1.0/tools/tox_install.sh 1970-01-01 00:00:00.000000000 +0000 +++ bandit-1.4.0/tools/tox_install.sh 2017-01-06 17:33:11.000000000 +0000 @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Client constraint file contains this client version pin that is in conflict +# with installing the client from source. We should remove the version pin in +# the constraints file before applying it for from-source installation. + +CONSTRAINTS_FILE="$1" +shift 1 + +set -e + +# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get +# published to logs.openstack.org for easy debugging. +localfile="$VIRTUAL_ENV/log/upper-constraints.txt" + +if [[ "$CONSTRAINTS_FILE" != http* ]]; then + CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE" +fi +# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep +curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile" + +pip install -c"$localfile" openstack-requirements + +# This is the main purpose of the script: Allow local installation of +# the current repo. It is listed in constraints file and thus any +# install will be constrained and we need to unconstrain it. +edit-constraints "$localfile" -- "$CLIENT_NAME" + +pip install -c"$localfile" -U "$@" +exit $? diff -Nru bandit-1.1.0/tox.ini bandit-1.4.0/tox.ini --- bandit-1.1.0/tox.ini 2016-08-15 03:08:07.000000000 +0000 +++ bandit-1.4.0/tox.ini 2017-01-06 17:33:11.000000000 +0000 @@ -1,12 +1,15 @@ [tox] -minversion = 1.6 -envlist = py35,py34,py27,pep8 +minversion = 2.0 +envlist = py35,py27,pep8 skipsdist = True [testenv] usedevelop = True -install_command = pip install -U {opts} {packages} +install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} setenv = + VIRTUAL_ENV={envdir} + BRANCH_NAME=master + CLIENT_NAME=bandit VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt @@ -32,6 +35,7 @@ . commands = flake8 {posargs} bandit flake8 {posargs} tests + {[testenv:pylint]commands} bandit-baseline -r bandit -ll -ii [testenv:venv] @@ -63,13 +67,11 @@ python setup.py build_sphinx [flake8] -# E123, E125 skipped as they are invalid PEP-8. -# H303 no wild card imports -# H302 import only modules - show-source = True -ignore = E123,E125,H303,H302 -exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,releasenotes +exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,releasenotes [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html + +[testenv:pylint] +commands = pylint --rcfile=pylintrc bandit