Skip to content

Commit 4bf342a

Browse files
authored
Tune down CPE. Filter duplicate versions in vers (#227)
* Tune down the CPE alias. Duplicate versions in vers was resulting in comparison failures. Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com> * Bug fix Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com> --------- Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>
1 parent 5d837c6 commit 4bf342a

File tree

8 files changed

+77
-58
lines changed

8 files changed

+77
-58
lines changed

contrib/cpe_research.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def propose_pseudo_purls() -> list:
7272
"citrix",
7373
"juniper",
7474
"qnap",
75+
"tenable",
7576
]
7677
)
7778
raw_hits = index_conn.execute(

packages/mcp-server-vdb/src/mcp_server_vdb/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ async def handle_call_tool(
385385
write_stream,
386386
InitializationOptions(
387387
server_name="appthreat-vulnerability-db",
388-
server_version="6.4.2",
388+
server_version="6.4.3",
389389
capabilities=server.get_capabilities(
390390
notification_options=NotificationOptions(),
391391
experimental_capabilities={},

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "appthreat-vulnerability-db"
3-
version = "6.4.2"
3+
version = "6.4.3"
44
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."
55
authors = [
66
{name = "Team AppThreat", email = "cloud@appthreat.com"},

test/test_source.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ def test_convert(test_cve_json):
573573
results_count = len(
574574
list(search.search_by_any("cpe:2.3:o:google:android:8.1:*:*:*:*:*:*:*"))
575575
)
576-
assert results_count == 25
576+
assert results_count == 6
577577

578578
cvesource = CVESource()
579579
cve = cvesource.convert5(vulnerabilities)
@@ -709,22 +709,22 @@ def test_nvd_api_convert(
709709
)
710710
)
711711
)
712-
assert results_count == 2
712+
assert results_count == 1
713713

714714
# git_json
715715
vulnerabilities = nvdlatest.convert(test_nvd_api_git_json)
716716
assert len(vulnerabilities) == 1
717-
assert len(vulnerabilities[0].details) == 2
717+
assert len(vulnerabilities[0].details) == 1
718718

719719
db6.clear_all()
720720
nvdlatest.store(vulnerabilities)
721721
cve_data_count, cve_index_count = db6.stats()
722-
assert cve_data_count == 2
723-
assert cve_index_count == 2
722+
assert cve_data_count == 1
723+
assert cve_index_count == 1
724724
results_count = len(list(search.search_by_any("CVE-2020-8022")))
725725
assert results_count == 0
726726
results_count = len(list(search.search_by_any("CVE-2023-52426")))
727-
assert results_count == 2
727+
assert results_count == 1
728728
results_count = len(
729729
list(
730730
search.search_by_any("cpe:2.3:a:libexpat_project:libexpat:*:*:*:*:*:*:*:*")
@@ -760,13 +760,13 @@ def test_nvd_api_convert3(test_nvd_api_signal_json):
760760
# signal ios
761761
vulnerabilities = nvdlatest.convert(test_nvd_api_signal_json)
762762
assert len(vulnerabilities) == 1
763-
assert len(vulnerabilities[0].details) == 2
763+
assert len(vulnerabilities[0].details) == 1
764764
nvdlatest.store(vulnerabilities)
765765
cve_data_count, cve_index_count = db6.stats()
766-
assert cve_data_count == 2
767-
assert cve_index_count == 2
766+
assert cve_data_count == 1
767+
assert cve_index_count == 1
768768
results_count = len(list(search.search_by_any("CVE-2018-9840")))
769-
assert results_count == 2
769+
assert results_count == 1
770770
results_count = len(
771771
list(
772772
search.search_by_any("pkg:generic/ios/signal")

test/test_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,15 @@ def test_vers_compare():
10971097
assert utils.vers_compare("1.12", "")
10981098

10991099

1100+
def test_vers_compare2():
1101+
assert not utils.vers_compare("2.4.59", "vers:apache/2.4.1|2.4.1")
1102+
assert not utils.vers_compare("2.4.59", "vers:apache/2.4.1")
1103+
assert not utils.vers_compare("2.4.59", "vers:apache/2.4.1|2.4.2|2.4.3|2.4.4")
1104+
assert utils.vers_compare("2.4.59", "vers:apache/2.4.1|2.4.2|2.4.3|2.4.4|2.4.59")
1105+
assert utils.vers_compare("2.4.59", "vers:apache/2.4.1|2.4.22|2.4.32|2.4.43|2.4.59|2.4.60")
1106+
assert utils.vers_compare("2.4.59", "vers:apache/2.4.59|2.4.22|2.4.32|2.4.43|2.4.59|2.4.60")
1107+
assert utils.vers_compare("2.4.59", "vers:apache/2.4.58|2.4.59|2.4.32|2.4.43|2.4.59|2.4.60")
1108+
11001109

11011110
def test_clean_cpe_uri():
11021111
test_tuples = (

uv.lock

Lines changed: 31 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vdb/lib/nvd.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
# Size of the stream to read and write to the file
3232
DOWNLOAD_CHUNK_SIZE = 128
3333

34-
purl_proposal_cache = defaultdict(list)
34+
cpe_proposal_cache = defaultdict(list)
3535

3636

3737
def filterable_cve(cve_id: str) -> bool:
@@ -133,15 +133,23 @@ def get_alt_cpes(cpe_uri, git_urls):
133133
git_repo_product = f"{purl_obj.get('namespace')}/{purl_obj.get('name')}"
134134
if not parsed_git_repo_names.get(git_repo_product):
135135
parsed_git_repo_names[git_repo_product] = True
136-
# We only need 2 new aliases
137-
if len(purl_proposal_cache.get(cpe_uri, [])) > 2:
138-
purl_proposal_cache[cpe_uri].pop(0)
139-
purl_proposal_cache[cpe_uri].append(
140-
f"cpe:2.3:a:{purl_obj['type']}:{git_repo_product}:*:*:*:*:*:*:*:*"
141-
)
136+
purl_type = purl_obj["type"]
137+
# Tune down the CPE proposal to reduce false positives
138+
# See issue #224
139+
if cpe_uri.startswith(f"cpe:2.3:a:{purl_type}:") or (
140+
purl_type in cpe_uri
141+
and "git" not in purl_type
142+
and "git" not in git_repo_product
143+
):
144+
# We only need 2 new aliases
145+
if len(cpe_proposal_cache.get(cpe_uri, [])) > 2:
146+
cpe_proposal_cache[cpe_uri].pop(0)
147+
cpe_proposal_cache[cpe_uri].append(
148+
f"cpe:2.3:a:{purl_type}:{git_repo_product}:*:*:*:*:*:*:*:*"
149+
)
142150
# See if there is something useful in the cache
143151
if not alt_cpes:
144-
alt_cpes = purl_proposal_cache.get(cpe_uri, [])
152+
alt_cpes = cpe_proposal_cache.get(cpe_uri, [])
145153
return alt_cpes
146154

147155

vdb/lib/utils.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ def vers_compare(compare_ver: str | int | float | None, vers: str) -> bool:
458458
if vers == "*" or compare_ver is None or compare_ver == "*":
459459
return True
460460
if vers.startswith("vers:"):
461-
vers_parts = vers.split("/")[-1].split("|")
461+
vers_parts = list(dict.fromkeys(vers.rsplit("/", 1)[-1].split("|")))
462462
if len(vers_parts) == 1:
463463
single_version = vers_parts[0].strip().replace(" ", "")
464464
# Handle wildcard and the special placeholder fix version
@@ -472,6 +472,9 @@ def vers_compare(compare_ver: str | int | float | None, vers: str) -> bool:
472472
return False
473473
for apart in vers_parts:
474474
apart = apart.strip().replace(" ", "")
475+
# Do we have a direct match
476+
if apart == compare_ver:
477+
return True
475478
if apart.startswith(">="):
476479
min_version = apart.removeprefix(">=")
477480
elif apart.startswith(">"):
@@ -480,16 +483,15 @@ def vers_compare(compare_ver: str | int | float | None, vers: str) -> bool:
480483
max_version = apart.removeprefix("<=")
481484
elif apart.startswith("<"):
482485
max_excluding = apart.removeprefix("<")
483-
# There is exactly only one version
486+
# Could the vers be simply a list of values
484487
if (
485-
len(vers_parts) == 1
486-
and not min_version
488+
not min_version
487489
and not max_version
488490
and not min_excluding
489491
and not max_excluding
490492
):
491493
min_version = vers_parts[0].strip().replace(" ", "")
492-
max_version = min_version
494+
max_version = vers_parts[-1].strip().replace(" ", "")
493495
return version_compare(
494496
compare_ver, min_version, max_version, min_excluding, max_excluding
495497
)
@@ -1143,8 +1145,7 @@ def to_purl_vers(vendor: str, versions: List) -> str:
11431145
vers_list.append("*")
11441146
else:
11451147
vers_list.append(f"<={less_than_or_equal}")
1146-
1147-
return f"vers:{scheme}/{'|'.join(vers_list)}" if vers_list else ""
1148+
return f"vers:{scheme}/{'|'.join(dict.fromkeys(vers_list))}" if vers_list else ""
11481149

11491150

11501151
def calculate_hash(content: str, digest_size: int = 16) -> str:

0 commit comments

Comments
 (0)