def test_check_live_cached(self): from safety.constants import CACHE_FILE # lets clear the cache first try: with open(CACHE_FILE, 'w') as f: f.write(json.dumps({})) except Exception: pass reqs = StringIO("insecure-package==0.1") packages = util.read_requirements(reqs) vulns, _ = safety.check(packages=packages, key=None, db_mirror=False, cached=60 * 60, ignore_vulns={}, ignore_severity_rules=None, proxy={}, telemetry=False) self.assertEqual(len(vulns), 1) reqs = StringIO("insecure-package==0.1") packages = util.read_requirements(reqs) # make a second call to use the cache vulns, _ = safety.check(packages=packages, key=None, db_mirror=False, cached=60 * 60, ignore_vulns={}, ignore_severity_rules=None, proxy={}, telemetry=False) self.assertEqual(len(vulns), 1)
def check(self): vulns = check(self.requirements) if vulns: self.menu_item.icon = ICONS.RED else: self.menu_item.icon = ICONS.GREEN return vulns
def run(self, filename, file, db_path: path = '', cve_ignore: typed_list(str) = []): """ Checks for vulnerable package versions in requirements files. :param db_path: Path to a local vulnerability database. :param cve_ignore: A list of CVE number to be ignore. """ db_path = self.db_path if not db_path else db_path packages = list( Package(key=req.key, version=req.specs[0][1]) for req in self.try_parse_requirements(file) if len(req.specs) == 1 and req.specs[0][0] == '==') if not packages: return for vulnerability in safety.check(packages, key=None, db_mirror=db_path, cached=False, ignore_ids=cve_ignore, proxy=None): if 'cve' in vulnerability.vuln_id.strip().lower(): message_template = ( '{vuln.name}{vuln.spec} is vulnerable to {vuln.vuln_id} ' 'and your project is using {vuln.version}.') else: message_template = ( '{vuln.name}{vuln.spec} is vulnerable to ' 'pyup.io-{vuln.vuln_id} and your project is using ' '{vuln.version}.') # StopIteration should not ever happen so skipping its branch line_number, line = next( # pragma: no branch (index, line) for index, line in enumerate(file, start=1) if vulnerability.name in line) version_spec_match = re.search(r'[=<>]+(\S+?)(?:$|\s|#)', line) source_range = SourceRange.from_values( filename, line_number, version_spec_match.start(1) + 1, line_number, version_spec_match.end(1) + 1, ) yield Result( self, message_template.format(vuln=vulnerability), additional_info=vulnerability.advisory, affected_code=(source_range, ), )
def run(self, filename, file, db_path: path = '', cve_ignore: typed_list(str) = []): """ Checks for vulnerable package versions in requirements files. :param db_path: Path to a local vulnerability database. :param cve_ignore: A list of CVE number to be ignore. """ db_path = self.db_path if not db_path else db_path packages = list( Package(key=req.key, version=req.specs[0][1]) for req in self.try_parse_requirements(file) if len(req.specs) == 1 and req.specs[0][0] == '==' ) if not packages: return for vulnerability in safety.check(packages, key=None, db_mirror=db_path, cached=False, ignore_ids=cve_ignore): if 'cve' in vulnerability.vuln_id.strip().lower(): message_template = ( '{vuln.name}{vuln.spec} is vulnerable to {vuln.vuln_id} ' 'and your project is using {vuln.version}.' ) else: message_template = ( '{vuln.name}{vuln.spec} is vulnerable to ' 'pyup.io-{vuln.vuln_id} and your project is using ' '{vuln.version}.' ) # StopIteration should not ever happen so skipping its branch line_number, line = next( # pragma: no branch (index, line) for index, line in enumerate(file, start=1) if vulnerability.name in line ) version_spec_match = re.search(r'[=<>]+(\S+?)(?:$|\s|#)', line) source_range = SourceRange.from_values( filename, line_number, version_spec_match.start(1) + 1, line_number, version_spec_match.end(1) + 1, ) yield Result( self, message_template.format(vuln=vulnerability), additional_info=vulnerability.advisory, affected_code=(source_range, ), )
def test_check_from_file(self): reqs = StringIO("Django==1.8.1") packages = util.read_requirements(reqs) vulns, _ = safety.check( packages=packages, key=None, db_mirror=os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_db"), cached=0, ignore_vulns={}, ignore_severity_rules=None, proxy={}, telemetry=False) self.assertEqual(len(vulns), 2)
def safety_provider(dependencies): """Pypi source is scanned with `safety`.""" for dependency in dependencies: packages = [ safetyutil.Package(key=dependency['name'], version=dependency['version']) ] vulns = safety.check(packages=packages, key=Config.SAFETY_API_KEY, db_mirror='', cached=False, ignore_ids=[]) for vuln in vulns: Vulnerability.get_or_create(artifact=dependency['id'], provider="safety", reference=vuln.vuln_id, details=vuln._asdict())
def run(self, filename, file): """ Checks for vulnerable package versions in requirements files. """ packages = list( Package(key=req.key, version=req.specs[0][1]) for req in self.try_parse_requirements(file) if len(req.specs) == 1 and req.specs[0][0] == '==' ) if not packages: return for vulnerability in safety.check(packages=packages): if vulnerability.is_cve: message_template = ( '{vuln.name}{vuln.spec} is vulnerable to {vuln.cve_id} ' 'and your project is using {vuln.version}.' ) else: message_template = ( '{vuln.name}{vuln.spec} is vulnerable and your project is ' 'using {vuln.version}.' ) # StopIteration should not ever happen so skipping its branch line_number, line = next( # pragma: no branch (index, line) for index, line in enumerate(file, start=1) if vulnerability.name in line ) version_spec_match = re.search(r'[=<>]+(\S+?)(?:$|\s|#)', line) source_range = SourceRange.from_values( filename, line_number, version_spec_match.start(1) + 1, line_number, version_spec_match.end(1) + 1, ) yield Result( self, message_template.format(vuln=vulnerability), additional_info=vulnerability.description, affected_code=(source_range, ), )
def check(key, db, json, full_report, bare, stdin, files, cache, ignore): if files and stdin: click.secho("Can't read from --stdin and --file at the same time, exiting", fg="red") sys.exit(-1) if files: packages = list(itertools.chain.from_iterable(read_requirements(f, resolve=True) for f in files)) elif stdin: packages = list(read_requirements(sys.stdin)) else: packages = get_installed_distributions() try: vulns = safety.check(packages=packages, key=key, db_mirror=db, cached=cache, ignore_ids=ignore) click.secho(report( vulns=vulns, full=full_report, json_report=json, bare_report=bare, checked_packages=len(packages), db=db, key=key ) ) sys.exit(-1 if vulns else 0) except InvalidKeyError: click.secho("Your API Key '{key}' is invalid. See {link}".format( key=key, link='https://goo.gl/O7Y1rS'), fg="red") sys.exit(-1) except DatabaseFileNotFoundError: click.secho("Unable to load vulnerability database from {db}".format(db=db), fg="red") sys.exit(-1) except DatabaseFetchError: click.secho("Unable to load vulnerability database", fg="red") sys.exit(-1)
def check(ctx, key, db, full_report, stdin, files, cache, ignore, output, json, bare, proxy_protocol, proxy_host, proxy_port, exit_code, policy_file, save_json): """ Find vulnerabilities in Python dependencies at the target provided. """ LOG.info('Running check command') try: packages = get_packages(files, stdin) proxy_dictionary = get_proxy_dict(proxy_protocol, proxy_host, proxy_port) announcements = [] if not db: LOG.info('Not local DB used, Getting announcements') announcements = safety.get_announcements( key=key, proxy=proxy_dictionary, telemetry=ctx.parent.telemetry) ignore_severity_rules = None ignore, ignore_severity_rules, exit_code = get_processed_options( policy_file, ignore, ignore_severity_rules, exit_code) is_env_scan = not stdin and not files params = { 'stdin': stdin, 'files': files, 'policy_file': policy_file, 'continue_on_error': not exit_code, 'ignore_severity_rules': ignore_severity_rules } LOG.info('Calling the check function') vulns, db_full = safety.check( packages=packages, key=key, db_mirror=db, cached=cache, ignore_vulns=ignore, ignore_severity_rules=ignore_severity_rules, proxy=proxy_dictionary, include_ignored=True, is_env_scan=is_env_scan, telemetry=ctx.parent.telemetry, params=params) LOG.debug('Vulnerabilities returned: %s', vulns) LOG.debug('full database returned is None: %s', db_full is None) LOG.info('Safety is going to calculate remediations') remediations = safety.calculate_remediations(vulns, db_full) LOG.info( 'Safety is going to render the vulnerabilities report using %s output', output) output_report = SafetyFormatter(output=output).render_vulnerabilities( announcements, vulns, remediations, full_report, packages) # Announcements are send to stderr if not terminal, it doesn't depend on "exit_code" value if announcements and (not sys.stdout.isatty() and os.environ.get( "SAFETY_OS_DESCRIPTION", None) != 'run'): LOG.info( 'sys.stdout is not a tty, announcements are going to be send to stderr' ) click.secho(SafetyFormatter( output='text').render_announcements(announcements), fg="red", file=sys.stderr) found_vulns = list(filter(lambda v: not v.ignored, vulns)) LOG.info('Vulnerabilities found (Not ignored): %s', len(found_vulns)) LOG.info('All vulnerabilities found (ignored and Not ignored): %s', len(vulns)) if save_json: default_name = 'safety-report.json' json_report = output_report if output != 'json': json_report = SafetyFormatter( output='json').render_vulnerabilities( announcements, vulns, remediations, full_report, packages) if os.path.isdir(save_json): save_json = os.path.join(save_json, default_name) with open(save_json, 'w+') as output_json_file: output_json_file.write(json_report) click.secho(output_report, nl=should_add_nl(output, found_vulns), file=sys.stdout) if exit_code and found_vulns: LOG.info('Exiting with default code for vulnerabilities found') sys.exit(EXIT_CODE_VULNERABILITIES_FOUND) sys.exit(EXIT_CODE_OK) except SafetyError as e: LOG.exception('Expected SafetyError happened: %s', e) output_exception(e, exit_code_output=exit_code) except Exception as e: LOG.exception('Unexpected Exception happened: %s', e) exception = e if isinstance(e, SafetyException) else SafetyException( info=e) output_exception(exception, exit_code_output=exit_code)