diff --git a/pyproject.toml b/pyproject.toml index 822c38e..afee8ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "appthreat-vulnerability-db" -version = "6.0.4" +version = "6.0.5" description = "AppThreat's vulnerability database and package search library with a built-in sqlite based storage. OSV, CVE, GitHub, npm are the primary sources of vulnerabilities." authors = [ {name = "Team AppThreat", email = "cloud@appthreat.com"}, diff --git a/test/data/MAL-2024-1333.json b/test/data/MAL-2024-1333.json new file mode 100644 index 0000000..cdcebf5 --- /dev/null +++ b/test/data/MAL-2024-1333.json @@ -0,0 +1,56 @@ +{ + "id": "MAL-2024-1333", + "summary": "Malicious code in threadxpools (PyPI)", + "details": "\n---\n_-= Per source details. Do not edit below this line.=-_\n\n## Source: ossf-package-analysis (41a0be6e9aa8db3965bae9f646d47ad6cb85ac9600c8bd71358409062b8fe105)\nThe OpenSSF Package Analysis project identified 'threadxpools' @ 1.2 (pypi) as malicious.\n\nIt is considered malicious because:\n\n- The package communicates with a domain associated with malicious activity.\n", + "modified": "2024-05-06T02:38:12Z", + "published": "2024-05-05T19:10:54Z", + "database_specific": { + "malicious-packages-origins": [ + { + "modified_time": "2024-05-05T19:17:29Z", + "import_time": "2024-05-06T02:37:56.710209536Z", + "versions": [ + "1.2" + ], + "source": "ossf-package-analysis", + "sha256": "41a0be6e9aa8db3965bae9f646d47ad6cb85ac9600c8bd71358409062b8fe105" + }, + { + "modified_time": "2024-05-05T19:10:54Z", + "import_time": "2024-05-06T02:37:56.622833878Z", + "versions": [ + "1.0" + ], + "source": "ossf-package-analysis", + "sha256": "d1017e118ad5a001211a639263fb872dfa5dde20fcd41e1674155a2d7977fb47" + } + ] + }, + "affected": [ + { + "package": { + "name": "threadxpools", + "ecosystem": "PyPI", + "purl": "pkg:pypi/threadxpools" + }, + "versions": [ + "1.2", + "1.0" + ], + "database_specific": { + "source": "https://github.com/ossf/malicious-packages/blob/main/osv/malicious/pypi/threadxpools/MAL-2024-1333.json" + } + } + ], + "schema_version": "1.6.0", + "credits": [ + { + "name": "OpenSSF: Package Analysis", + "contact": [ + "https://github.com/ossf/package-analysis", + "https://openssf.slack.com/channels/package_analysis" + ], + "type": "FINDER" + } + ] +} \ No newline at end of file diff --git a/test/test_source.py b/test/test_source.py index 531d97e..bfcd364 100644 --- a/test/test_source.py +++ b/test/test_source.py @@ -198,6 +198,15 @@ def test_osv_mal_json(): return json.loads(fp.read()) +@pytest.fixture +def test_osv_mal2_json(): + test_cve_data = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "data", "MAL-2024-1333.json" + ) + with open(test_cve_data, "r") as fp: + return json.loads(fp.read()) + + @pytest.fixture def test_aqua_alsa_json(): test_cve_data = os.path.join( @@ -841,8 +850,10 @@ def test_osv_convert2(test_osv_npm_star_json): assert len(cve_data) == 3 -def test_osv_mal_convert(test_osv_mal_json): +def test_osv_mal_convert(test_osv_mal_json, test_osv_mal2_json): osvlatest = OSVSource() + cve_data = osvlatest.convert(test_osv_mal2_json) + assert len(cve_data) == 1 cve_data = osvlatest.convert(test_osv_mal_json) assert len(cve_data) == 1 diff --git a/vdb/lib/osv.py b/vdb/lib/osv.py index d54561a..f7c6b60 100644 --- a/vdb/lib/osv.py +++ b/vdb/lib/osv.py @@ -282,8 +282,8 @@ def to_vuln(cve_data): possible_fix_version = ev.get("fixed") if needs_version_backup and len(versions_list): try: - min_ver = min(versions_list, key=Version.parse) - max_ver = max(versions_list, key=Version.parse) + min_ver = min(versions_list, key=lambda x: Version.parse(x, optional_minor_and_patch=True)) + max_ver = max(versions_list, key=lambda x: Version.parse(x, optional_minor_and_patch=True)) except Exception: min_ver = versions_list[0] max_ver = versions_list[-1] diff --git a/vdb/lib/utils.py b/vdb/lib/utils.py index 749d9d7..bbc4347 100644 --- a/vdb/lib/utils.py +++ b/vdb/lib/utils.py @@ -610,7 +610,7 @@ def version_compare( return False if mae: if VersionInfo.is_valid(compare_ver) and VersionInfo.is_valid(mae): - cmp_value = VersionInfo.parse(compare_ver).compare(mae) + cmp_value = VersionInfo.parse(compare_ver, optional_minor_and_patch=True).compare(mae) return cmp_value < 0 elif "." not in compare_ver and "." not in mae: compare_ver = re.split(r"[+~]", compare_ver)[0] @@ -651,8 +651,8 @@ def version_compare( # Perform semver match once we have all the required versions if compare_ver and min_version and max_version: if semver_compatible(compare_ver, min_version, max_version): - min_value = VersionInfo.parse(compare_ver).compare(min_version) - max_value = VersionInfo.parse(compare_ver).compare(max_version) + min_value = VersionInfo.parse(compare_ver, optional_minor_and_patch=True).compare(min_version) + max_value = VersionInfo.parse(compare_ver, optional_minor_and_patch=True).compare(max_version) min_check = min_value > 0 if is_min_exclude else min_value >= 0 max_check = max_value < 0 if is_max_exclude else max_value <= 0 return min_check and max_check