From a96ed71abc54477cccb45fdf52da34c4bc5431aa Mon Sep 17 00:00:00 2001 From: Pavel Vdovenko Date: Tue, 25 Aug 2020 18:43:11 +0700 Subject: [PATCH 01/11] Load premium database file if available --- safety/cli.py | 2 ++ safety/safety.py | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/safety/cli.py b/safety/cli.py index 09737831..57d90b48 100644 --- a/safety/cli.py +++ b/safety/cli.py @@ -31,6 +31,8 @@ def cli(): @click.option("--full-report/--short-report", default=False, help='Full reports include a security advisory (if available). Default: ' '--short-report') +# @click.option("--cvss", default="", +# help="Extend reports with premium CVSS information (if available). Default: empty") @click.option("--bare/--not-bare", default=False, help='Output vulnerable packages only. ' 'Useful in combination with other tools. ' diff --git a/safety/safety.py b/safety/safety.py index 2dd9c958..4d48ad79 100644 --- a/safety/safety.py +++ b/safety/safety.py @@ -107,7 +107,7 @@ def fetch_database_file(path, db_name): return json.loads(f.read()) -def fetch_database(full=False, key=False, db=False, cached=False, proxy={}): +def fetch_database(full=False, premium=False, key=False, db=False, cached=False, proxy={}): if db: mirrors = [db] @@ -115,6 +115,7 @@ def fetch_database(full=False, key=False, db=False, cached=False, proxy={}): mirrors = API_MIRRORS if key else OPEN_MIRRORS db_name = "insecure_full.json" if full else "insecure.json" + db_name = "insecure_premium.json" if premium else db_name for mirror in mirrors: # mirror can either be a local path or a URL if mirror.startswith("http://") or mirror.startswith("https://"): @@ -137,6 +138,7 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): key = key if key else os.environ.get("SAFETY_API_KEY", False) db = fetch_database(key=key, db=db_mirror, cached=cached, proxy=proxy) db_full = None + premium = False vulnerable_packages = frozenset(db.keys()) vulnerable = [] for pkg in packages: @@ -154,9 +156,14 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: - db_full = fetch_database(full=True, key=key, db=db_mirror, cached=cached, proxy=proxy) + premium = True + db_full = fetch_database(full=True, premium=True, key=key, db=db_mirror, cached=cached, proxy=proxy) + if not db_full: + premium = False + db_full = fetch_database(full=True, key=key, db=db_mirror, cached=cached, proxy=proxy) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): vuln_id = data.get("id").replace("pyup.io-", "") + cve_ids = map(str.strip, data.get("cve").split(',')) if vuln_id and vuln_id not in ignore_ids: vulnerable.append( Vulnerability( From 6381287605e19c36be6c61a3e737922273a28f42 Mon Sep 17 00:00:00 2001 From: Pavel Vdovenko Date: Thu, 27 Aug 2020 17:08:38 +0700 Subject: [PATCH 02/11] Extend Vulnerability class with CVSS information --- safety/cli.py | 2 -- safety/safety.py | 20 ++++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/safety/cli.py b/safety/cli.py index 57d90b48..09737831 100644 --- a/safety/cli.py +++ b/safety/cli.py @@ -31,8 +31,6 @@ def cli(): @click.option("--full-report/--short-report", default=False, help='Full reports include a security advisory (if available). Default: ' '--short-report') -# @click.option("--cvss", default="", -# help="Extend reports with premium CVSS information (if available). Default: empty") @click.option("--bare/--not-bare", default=False, help='Output vulnerable packages only. ' 'Useful in combination with other tools. ' diff --git a/safety/safety.py b/safety/safety.py index 4d48ad79..07a6f597 100644 --- a/safety/safety.py +++ b/safety/safety.py @@ -16,7 +16,7 @@ class Vulnerability(namedtuple("Vulnerability", - ["name", "spec", "version", "advisory", "vuln_id"])): + ["name", "spec", "version", "advisory", "vuln_id", "cvssv2", "cvssv3"])): pass @@ -138,7 +138,6 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): key = key if key else os.environ.get("SAFETY_API_KEY", False) db = fetch_database(key=key, db=db_mirror, cached=cached, proxy=proxy) db_full = None - premium = False vulnerable_packages = frozenset(db.keys()) vulnerable = [] for pkg in packages: @@ -156,14 +155,15 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: - premium = True - db_full = fetch_database(full=True, premium=True, key=key, db=db_mirror, cached=cached, proxy=proxy) - if not db_full: - premium = False + try: + db_full = fetch_database(full=True, premium=True, key=key, db=db_mirror, cached=cached, proxy=proxy) + except: db_full = fetch_database(full=True, key=key, db=db_mirror, cached=cached, proxy=proxy) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): vuln_id = data.get("id").replace("pyup.io-", "") - cve_ids = map(str.strip, data.get("cve").split(',')) + cve_id = data.get("cve") + if cve_id: + cve_id = cve_id.split(",")[0].strip() if vuln_id and vuln_id not in ignore_ids: vulnerable.append( Vulnerability( @@ -171,7 +171,9 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): spec=specifier, version=pkg.version, advisory=data.get("advisory"), - vuln_id=vuln_id + vuln_id=vuln_id, + cvssv2=db_full.get("$meta", {}).get("cve", {}).get(cve_id, {}).get("cvssv2", None), + cvssv3=db_full.get("$meta", {}).get("cve", {}).get(cve_id, {}).get("cvssv3", None) ) ) return vulnerable @@ -186,6 +188,8 @@ def review(vulnerabilities): "version": vuln[2], "advisory": vuln[3], "vuln_id": vuln[4], + "cvssv2": None, + "cvssv3": None } vulnerable.append( Vulnerability(**current_vuln) From 5267f4808f1893523a55ad2e2c578626f2fb13d0 Mon Sep 17 00:00:00 2001 From: Pavel Vdovenko Date: Thu, 27 Aug 2020 18:11:52 +0700 Subject: [PATCH 03/11] Add CVSS information to full report --- safety/formatter.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/safety/formatter.py b/safety/formatter.py index 8e756c97..63aac45d 100644 --- a/safety/formatter.py +++ b/safety/formatter.py @@ -109,6 +109,25 @@ def render(vulns, full, checked_packages, used_db): if full: table.append(SheetReport.REPORT_SECTION) + if vuln.cvssv2 is not None: + base_score = vuln.cvssv2.get("base_score", "None") + impact_score = vuln.cvssv2.get("impact_score", "None") + + table.append("| {:76} |".format( + f"CVSS v2 | BASE SCORE: {base_score} | IMPACT SCORE: {impact_score}" + )) + table.append(SheetReport.REPORT_SECTION) + + if vuln.cvssv3 is not None: + base_score = vuln.cvssv3.get("base_score", "None") + impact_score = vuln.cvssv3.get("impact_score", "None") + base_severity = vuln.cvssv3.get("base_severity", "None") + + table.append("| {:76} |".format( + f"CVSS v3 | BASE SCORE: {base_score} | IMPACT SCORE: {impact_score} | BASE SEVERITY: {base_severity}" + )) + table.append(SheetReport.REPORT_SECTION) + descr = get_advisory(vuln) for pn, paragraph in enumerate(descr.replace('\r', '').split('\n\n')): @@ -203,6 +222,23 @@ def render(vulns, full, checked_packages, used_db): vuln.vuln_id )) if full: + if vuln.cvssv2 is not None: + base_score = vuln.cvssv2.get("base_score", "None") + impact_score = vuln.cvssv2.get("impact_score", "None") + + table.append( + f"CVSS v2 -- BASE SCORE: {base_score}, IMPACT SCORE: {impact_score}" + ) + + if vuln.cvssv3 is not None: + base_score = vuln.cvssv3.get("base_score", "None") + impact_score = vuln.cvssv3.get("impact_score", "None") + base_severity = vuln.cvssv3.get("base_severity", "None") + + table.append( + f"CVSS v3 -- BASE SCORE: {base_score}, IMPACT SCORE: {impact_score}, BASE SEVERITY: {base_severity}" + ) + table.append(get_advisory(vuln)) table.append("--") else: From 73586e5ecd140b969207ffd33015323e9ea96c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Kegler?= Date: Sun, 22 Nov 2020 18:10:25 -0300 Subject: [PATCH 04/11] Removing extra info. --- safety/safety.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/safety/safety.py b/safety/safety.py index 07a6f597..d4301e5c 100644 --- a/safety/safety.py +++ b/safety/safety.py @@ -107,7 +107,7 @@ def fetch_database_file(path, db_name): return json.loads(f.read()) -def fetch_database(full=False, premium=False, key=False, db=False, cached=False, proxy={}): +def fetch_database(full=False, key=False, db=False, cached=False, proxy={}): if db: mirrors = [db] @@ -115,7 +115,6 @@ def fetch_database(full=False, premium=False, key=False, db=False, cached=False, mirrors = API_MIRRORS if key else OPEN_MIRRORS db_name = "insecure_full.json" if full else "insecure.json" - db_name = "insecure_premium.json" if premium else db_name for mirror in mirrors: # mirror can either be a local path or a URL if mirror.startswith("http://") or mirror.startswith("https://"): @@ -155,10 +154,7 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: - try: - db_full = fetch_database(full=True, premium=True, key=key, db=db_mirror, cached=cached, proxy=proxy) - except: - db_full = fetch_database(full=True, key=key, db=db_mirror, cached=cached, proxy=proxy) + db_full = fetch_database(full=True, key=key, db=db_mirror, cached=cached, proxy=proxy) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): vuln_id = data.get("id").replace("pyup.io-", "") cve_id = data.get("cve") From cc20b4199aa1489f23aaeaab5e41ff674e5dec33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Kegler?= Date: Sun, 22 Nov 2020 19:40:01 -0300 Subject: [PATCH 05/11] Holding value on memory. --- safety/safety.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/safety/safety.py b/safety/safety.py index d4301e5c..0817e5c8 100644 --- a/safety/safety.py +++ b/safety/safety.py @@ -161,6 +161,7 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): if cve_id: cve_id = cve_id.split(",")[0].strip() if vuln_id and vuln_id not in ignore_ids: + cve_meta = db_full.get("$meta", {}).get("cve", {}).get(cve_id, {}) vulnerable.append( Vulnerability( name=name, @@ -168,8 +169,8 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): version=pkg.version, advisory=data.get("advisory"), vuln_id=vuln_id, - cvssv2=db_full.get("$meta", {}).get("cve", {}).get(cve_id, {}).get("cvssv2", None), - cvssv3=db_full.get("$meta", {}).get("cve", {}).get(cve_id, {}).get("cvssv3", None) + cvssv2=cve_meta.get("cvssv2", None), + cvssv3=cve_meta.get("cvssv3", None) ) ) return vulnerable From 19403399d08b5c07e39e64d88b6c4abf36ba2f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Kegler?= Date: Sun, 22 Nov 2020 19:53:08 -0300 Subject: [PATCH 06/11] Fixing tests. --- safety/safety.py | 2 +- tests/test_safety.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/safety/safety.py b/safety/safety.py index 0817e5c8..9f3a5bbd 100644 --- a/safety/safety.py +++ b/safety/safety.py @@ -170,7 +170,7 @@ def check(packages, key, db_mirror, cached, ignore_ids, proxy): advisory=data.get("advisory"), vuln_id=vuln_id, cvssv2=cve_meta.get("cvssv2", None), - cvssv3=cve_meta.get("cvssv3", None) + cvssv3=cve_meta.get("cvssv3", None), ) ) return vulnerable diff --git a/tests/test_safety.py b/tests/test_safety.py index 5f2c3e58..a810beb9 100644 --- a/tests/test_safety.py +++ b/tests/test_safety.py @@ -121,6 +121,8 @@ def test_full_report(self): + ' blah' * 15 + '.\r\n\r\n' + 'All users are urged to upgrade please.\r\n', vuln_id=1234, + cvssv2=None, + cvssv3=None, ), ] full_report = formatter.SheetReport.render( From 629cfac3f053a977c79b1863d343d95d6aef8012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Kegler?= Date: Mon, 23 Nov 2020 08:48:04 -0300 Subject: [PATCH 07/11] Using format to backwards compatibility. --- safety/formatter.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/safety/formatter.py b/safety/formatter.py index 63aac45d..27f6882e 100644 --- a/safety/formatter.py +++ b/safety/formatter.py @@ -114,7 +114,10 @@ def render(vulns, full, checked_packages, used_db): impact_score = vuln.cvssv2.get("impact_score", "None") table.append("| {:76} |".format( - f"CVSS v2 | BASE SCORE: {base_score} | IMPACT SCORE: {impact_score}" + "CVSS v2 | BASE SCORE: {} | IMPACT SCORE: {}".format( + base_score, + impact_score, + ) )) table.append(SheetReport.REPORT_SECTION) @@ -124,7 +127,11 @@ def render(vulns, full, checked_packages, used_db): base_severity = vuln.cvssv3.get("base_severity", "None") table.append("| {:76} |".format( - f"CVSS v3 | BASE SCORE: {base_score} | IMPACT SCORE: {impact_score} | BASE SEVERITY: {base_severity}" + "CVSS v3 | BASE SCORE: {} | IMPACT SCORE: {} | BASE SEVERITY: {}".format( + base_score, + impact_score, + base_severity, + ) )) table.append(SheetReport.REPORT_SECTION) @@ -226,18 +233,21 @@ def render(vulns, full, checked_packages, used_db): base_score = vuln.cvssv2.get("base_score", "None") impact_score = vuln.cvssv2.get("impact_score", "None") - table.append( - f"CVSS v2 -- BASE SCORE: {base_score}, IMPACT SCORE: {impact_score}" - ) + table.append("CVSS v2 -- BASE SCORE: {}, IMPACT SCORE: {}".format( + base_score, + impact_score, + )) if vuln.cvssv3 is not None: base_score = vuln.cvssv3.get("base_score", "None") impact_score = vuln.cvssv3.get("impact_score", "None") base_severity = vuln.cvssv3.get("base_severity", "None") - table.append( - f"CVSS v3 -- BASE SCORE: {base_score}, IMPACT SCORE: {impact_score}, BASE SEVERITY: {base_severity}" - ) + table.append("CVSS v3 -- BASE SCORE: {}, IMPACT SCORE: {}, BASE SEVERITY: {}".format( + base_score, + impact_score, + base_severity, + )) table.append(get_advisory(vuln)) table.append("--") From 9cf2893585ffdc5e46637ef8c5a49493177faaa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Kegler?= Date: Sat, 9 Jan 2021 21:53:16 -0300 Subject: [PATCH 08/11] Providing history and updating readme for CVSS report --- HISTORY.rst | 1 + README.md | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index b0af7d8c..5599d9c4 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,6 +7,7 @@ History * Reduced Docker image and Binary size * Added bare and json outputs to license command +* Provide CVSS scores on full report, when available 1.10.0 (2020-12-20) ------------------- diff --git a/README.md b/README.md index 099c0680..e0a768f6 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ ___ ### `--full-report` -*Full reports include a security advisory (if available).* +*Full reports include a security advisory and CVSS scores (if available).* **Example** ```bash @@ -277,6 +277,8 @@ safety check --full-report +============================+===========+==========================+==========+ | package | installed | affected | ID | +============================+===========+==========================+==========+ +| CVSS v2 | BASE SCORE: 6.5 | IMPACT SCORE: 6.4 | ++============================+===========+==========================+==========+ | django | 1.2 | <1.2.2 | 25701 | +==============================================================================+ | Cross-site scripting (XSS) vulnerability in Django 1.2.x before 1.2.2 allows | From 28c9e3acc94c415d7369700053314abeb4b6cdee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Kegler?= Date: Sun, 10 Jan 2021 20:08:53 -0300 Subject: [PATCH 09/11] Update HISTORY.rst --- HISTORY.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 5599d9c4..6e7cf94f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,7 +2,12 @@ History ======= -1.10.1 (2020-12-03) +1.10.2 (master) +------------------- + +* Provide CVSS scores on full report, when available + +1.10.1 (2021-01-03) ------------------- * Reduced Docker image and Binary size From 23b234fc548f3e5260fa686fd6c00dc333905234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Kegler?= Date: Sun, 10 Jan 2021 20:09:17 -0300 Subject: [PATCH 10/11] Update HISTORY.rst --- HISTORY.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 6e7cf94f..0d8eae9b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -12,7 +12,6 @@ History * Reduced Docker image and Binary size * Added bare and json outputs to license command -* Provide CVSS scores on full report, when available 1.10.0 (2020-12-20) ------------------- From 63e6b21359367746f7868593b8c49be81ee37dad Mon Sep 17 00:00:00 2001 From: Rafael Pivato Date: Mon, 11 Jan 2021 00:21:39 -0300 Subject: [PATCH 11/11] Starting version 1.10.2 --- appveyor.yml | 2 +- safety/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 4a32eca4..642add31 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.10.1+{build} +version: 1.10.2-dev+{build} image: - Visual Studio 2019 - Ubuntu diff --git a/safety/__init__.py b/safety/__init__.py index e8d56fc8..e135c23c 100644 --- a/safety/__init__.py +++ b/safety/__init__.py @@ -2,4 +2,4 @@ __author__ = """pyup.io""" __email__ = 'support@pyup.io' -__version__ = '1.10.1' +__version__ = '1.10.2-dev' diff --git a/setup.py b/setup.py index 686949b7..c748336e 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name='safety', - version='1.10.1', + version='1.10.2-dev', description="Checks installed dependencies for known vulnerabilities.", long_description=readme + '\n\n' + history, long_description_content_type="text/markdown",