Skip to content

Commit 1468940

Browse files
Merge branch 'main' into add-advisory-v2-api-endpoint-2224
2 parents 7fe2c65 + 74172c4 commit 1468940

40 files changed

+501
-113
lines changed

.github/workflows/pypi-release.yml

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ on:
2121
jobs:
2222
build-pypi-distribs:
2323
name: Build and publish library to PyPI
24-
runs-on: ubuntu-22.04
24+
runs-on: ubuntu-24.04
2525

2626
steps:
27-
- uses: actions/checkout@master
27+
- uses: actions/checkout@v4
2828
- name: Set up Python
29-
uses: actions/setup-python@v1
29+
uses: actions/setup-python@v5
3030
with:
3131
python-version: 3.12
3232

@@ -37,7 +37,7 @@ jobs:
3737
run: python -m build --sdist --wheel --outdir dist/
3838

3939
- name: Upload built archives
40-
uses: actions/upload-artifact@v4
40+
uses: actions/upload-artifact@v7
4141
with:
4242
name: pypi_archives
4343
path: dist/*
@@ -47,37 +47,39 @@ jobs:
4747
name: Create GH release
4848
needs:
4949
- build-pypi-distribs
50-
runs-on: ubuntu-22.04
50+
runs-on: ubuntu-24.04
5151

5252
steps:
5353
- name: Download built archives
54-
uses: actions/download-artifact@v4
54+
uses: actions/download-artifact@v8
5555
with:
5656
name: pypi_archives
5757
path: dist
5858

5959
- name: Create GH release
60-
uses: softprops/action-gh-release@v1
60+
uses: softprops/action-gh-release@v2
6161
with:
62-
draft: true
62+
draft: false
63+
generate_release_notes: true
6364
files: dist/*
6465

6566

6667
create-pypi-release:
6768
name: Create PyPI release
6869
needs:
6970
- create-gh-release
70-
runs-on: ubuntu-22.04
71+
runs-on: ubuntu-24.04
72+
environment: pypi-publish
73+
permissions:
74+
id-token: write
7175

7276
steps:
7377
- name: Download built archives
74-
uses: actions/download-artifact@v4
78+
uses: actions/download-artifact@v8
7579
with:
7680
name: pypi_archives
7781
path: dist
7882

7983
- name: Publish to PyPI
80-
if: startsWith(github.ref, 'refs/tags')
81-
uses: pypa/gh-action-pypi-publish@master
82-
with:
83-
password: ${{ secrets.PYPI_API_TOKEN }}
84+
if: startsWith(github.ref, 'refs/tags/')
85+
uses: pypa/gh-action-pypi-publish@release/v1

CHANGELOG.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ Version v37.0.0
1313
- We have added new models AdvisoryV2, AdvisoryAlias, AdvisoryReference, AdvisorySeverity, AdvisoryWeakness, PackageV2 and CodeFixV2.
1414
- We are using ``avid`` as an internal advisory ID for uniquely identifying advisories.
1515
- We have a new route ``/v2`` which only support package search which has information on packages that are reported to be affected or fixing by advisories.
16-
- This version introduces ``/api/v2/advisories-packages`` which has information on packages that are reported to be affected or fixing by advisories.
16+
- This version introduces ``/api/v3/packages`` which has information on packages that are reported to be affected or fixing by advisories.
1717
- Pipeline Dashboard improvements #1920.
1818
- Throttle API requests based on user permissions #1909.
1919
- Add pipeline to compute Advisory ToDos #1764
20+
- Use related advisory severity to calculate exploitibility, weighted severity and risk scores
21+
- Migrate all importers to use the new advisory models. All new advisories have a unique AVID and all importers will use this AVID as the unique identifier for advisories instead of CVE ID or other identifiers used by the data sources #1881.
22+
- Handle advisories with same and related data https://github.com/aboutcode-org/vulnerablecode/issues/2099.
23+
- Add a pipeline for exporting VulnerableCode data to FederatedCode #2110.
24+
- Plan storing of exploits and EPSS based advisories #2069.
25+
2026

2127
Version v36.1.3
2228
---------------------

PIPELINES-AVID.rst

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
.. list-table:: Pipeline AVID Mapping
2+
:header-rows: 1
3+
:widths: 35 65
4+
5+
* - pipeline name
6+
- AVID
7+
* - alpine_linux_importer_v2
8+
- {package_name}/{distroversion}/{version}/{vulnerability_id}
9+
* - aosp_dataset_fix_commits
10+
- CVE ID of the record
11+
* - apache_httpd_importer_v2
12+
- CVE ID of the record
13+
* - apache_kafka_importer_v2
14+
- CVE ID of the record
15+
* - apache_tomcat_importer_v2
16+
- {page_id}/{cve_id}
17+
* - archlinux_importer_v2
18+
- AVG ID of the record
19+
* - curl_importer_v2
20+
- CURL-CVE ID of the record
21+
* - debian_importer_v2
22+
- {package_name}/{debian_record_id}
23+
* - elixir_security_importer_v2
24+
- {package_name}/{file_id}
25+
* - epss_importer_v2
26+
- CVE ID of the record
27+
* - fireeye_importer_v2
28+
- {file_id}
29+
* - gentoo_importer_v2
30+
- GLSA ID of the record
31+
* - github_osv_importer_v2
32+
- ID of the OSV record
33+
* - gitlab_importer_v2
34+
- Identifier of the GitLab community advisory record
35+
* - istio_importer_v2
36+
- ISTIO-SECURITY-<ID>
37+
* - mattermost_importer_v2
38+
- MMSA-<ID>
39+
* - mozilla_importer_v2
40+
- MFSA-<ID>
41+
* - nginx_importer_v2
42+
- First alias of the record
43+
* - nodejs_security_wg
44+
- NPM-<ID>
45+
* - nvd_importer_v2
46+
- CVE ID of the record
47+
* - openssl_importer_v2
48+
- CVE ID of the record
49+
* - oss_fuzz_importer_v2
50+
- ID of the OSV record
51+
* - postgresql_importer_v2
52+
- CVE ID of the record
53+
* - project-kb-msr-2019_v2
54+
- Vulnerability ID of the record
55+
* - project-kb-statements_v2
56+
- Vulnerability ID of the record
57+
* - pypa_importer_v2
58+
- ID of the OSV record
59+
* - pysec_importer_v2
60+
- ID of the OSV record
61+
* - redhat_importer_v2
62+
- RHSA ID of the record
63+
* - retiredotnet_importer_v2
64+
- retiredotnet-{file_id}
65+
* - ruby_importer_v2
66+
- {file_id}
67+
* - suse_importer_v2
68+
- CVE ID of the record
69+
* - ubuntu_osv_importer_v2
70+
- ID of the OSV record
71+
* - vulnrichment_importer_v2
72+
- CVE ID of the record
73+
* - xen_importer_v2
74+
- XSA-<ID>

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ docutils==0.17.1
4040
drf-spectacular==0.24.2
4141
drf-spectacular-sidecar==2022.10.1
4242
executing==0.8.3
43-
fetchcode==0.6.0
43+
fetchcode==0.8.2
4444
freezegun==1.2.1
4545
frozenlist==1.3.0
4646
gitdb==4.0.9

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ install_requires =
9191
# networking
9292
GitPython>=3.1.17
9393
requests>=2.25.1
94-
fetchcode>=0.6.0
94+
fetchcode>=0.8.2
9595

9696
#pipeline
9797
aboutcode.pipeline>=0.1.0

vulnerabilities/improvers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from vulnerabilities.pipelines import populate_vulnerability_summary_pipeline
2121
from vulnerabilities.pipelines import remove_duplicate_advisories
2222
from vulnerabilities.pipelines.v2_improvers import collect_ssvc_trees
23+
from vulnerabilities.pipelines.v2_improvers import compute_advisory_content_hash
2324
from vulnerabilities.pipelines.v2_improvers import compute_advisory_todo as compute_advisory_todo_v2
2425
from vulnerabilities.pipelines.v2_improvers import compute_package_risk as compute_package_risk_v2
2526
from vulnerabilities.pipelines.v2_improvers import (
@@ -74,5 +75,6 @@
7475
compute_advisory_todo.ComputeToDo,
7576
collect_ssvc_trees.CollectSSVCPipeline,
7677
relate_severities.RelateSeveritiesPipeline,
78+
compute_advisory_content_hash.ComputeAdvisoryContentHash,
7779
]
7880
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 5.2.11 on 2026-03-11 08:46
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("vulnerabilities", "0115_impactedpackageaffecting_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="advisoryv2",
15+
name="advisory_content_hash",
16+
field=models.CharField(
17+
blank=True,
18+
help_text="A unique hash computed from the content of the advisory used to identify advisories with the same content.",
19+
max_length=64,
20+
null=True,
21+
),
22+
),
23+
]

vulnerabilities/models.py

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3010,6 +3010,13 @@ class AdvisoryV2(models.Model):
30103010
help_text="Related advisories that are used to calculate the severity of this advisory.",
30113011
)
30123012

3013+
advisory_content_hash = models.CharField(
3014+
max_length=64,
3015+
blank=True,
3016+
null=True,
3017+
help_text="A unique hash computed from the content of the advisory used to identify advisories with the same content.",
3018+
)
3019+
30133020
@property
30143021
def risk_score(self):
30153022
"""
@@ -3078,35 +3085,6 @@ def get_aliases(self):
30783085
"""
30793086
return self.aliases.all()
30803087

3081-
def compute_advisory_content(self):
3082-
"""
3083-
Compute a unique content hash for an advisory by normalizing its data and hashing it.
3084-
3085-
:param advisory: An Advisory object
3086-
:return: SHA-256 hash digest as content hash
3087-
"""
3088-
normalized_data = {
3089-
"summary": normalize_text(self.summary),
3090-
"impacted_packages": sorted(
3091-
[impact.to_dict() for impact in self.impacted_packages.all()],
3092-
key=lambda x: json.dumps(x, sort_keys=True),
3093-
),
3094-
"patches": sorted(
3095-
[patch.to_patch_data().to_dict() for patch in self.patches.all()],
3096-
key=lambda x: json.dumps(x, sort_keys=True),
3097-
),
3098-
"severities": sorted(
3099-
[sev.to_vulnerability_severity_data().to_dict() for sev in self.severities.all()],
3100-
key=lambda x: (x.get("system"), x.get("value")),
3101-
),
3102-
"weaknesses": normalize_list([weakness.cwe_id for weakness in self.weaknesses.all()]),
3103-
}
3104-
3105-
normalized_json = json.dumps(normalized_data, separators=(",", ":"), sort_keys=True)
3106-
content_hash = hashlib.sha256(normalized_json.encode("utf-8")).hexdigest()
3107-
3108-
return content_hash
3109-
31103088
alias = get_aliases
31113089

31123090

vulnerabilities/pipelines/enhance_with_exploitdb.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,16 @@ def add_exploit(self):
7878

7979

8080
def add_vulnerability_exploit(row, logger):
81-
vulnerabilities = set()
82-
8381
aliases = row["codes"].split(";") if row["codes"] else []
8482

8583
if not aliases:
8684
return 0
8785

88-
for raw_alias in aliases:
89-
try:
90-
if alias := Alias.objects.get(alias=raw_alias):
91-
vulnerabilities.add(alias.vulnerability)
92-
except Alias.DoesNotExist:
93-
continue
86+
vulnerabilities = (
87+
Alias.objects.filter(alias__in=aliases, vulnerability__isnull=False)
88+
.values_list("vulnerability_id", flat=True)
89+
.distinct()
90+
)
9491

9592
if not vulnerabilities:
9693
logger(f"No vulnerability found for aliases {aliases}")
@@ -104,7 +101,7 @@ def add_vulnerability_exploit(row, logger):
104101
add_exploit_references(row["codes"], row["source_url"], row["file"], vulnerability, logger)
105102
try:
106103
Exploit.objects.update_or_create(
107-
vulnerability=vulnerability,
104+
vulnerability_id=vulnerability,
108105
data_source="Exploit-DB",
109106
defaults={
110107
"date_added": date_added,
@@ -125,7 +122,7 @@ def add_vulnerability_exploit(row, logger):
125122
return 1
126123

127124

128-
def add_exploit_references(ref_id, direct_url, path, vul, logger):
125+
def add_exploit_references(ref_id, direct_url, path, vul_id, logger):
129126
url_map = {
130127
"file_url": f"https://gitlab.com/exploit-database/exploitdb/-/blob/main/{path}",
131128
"direct_url": direct_url,
@@ -144,7 +141,7 @@ def add_exploit_references(ref_id, direct_url, path, vul, logger):
144141

145142
if created:
146143
VulnerabilityRelatedReference.objects.get_or_create(
147-
vulnerability=vul,
144+
vulnerability_id=vul_id,
148145
reference=ref,
149146
)
150147

vulnerabilities/pipelines/enhance_with_kev.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,26 +71,29 @@ def add_vulnerability_exploit(kev_vul, logger):
7171
if not cve_id:
7272
return 0
7373

74-
vulnerability = None
75-
try:
76-
if alias := Alias.objects.get(alias=cve_id):
77-
vulnerability = alias.vulnerability
78-
except Alias.DoesNotExist:
74+
vulnerabilities = (
75+
Alias.objects.filter(alias=cve_id, vulnerability__isnull=False)
76+
.values_list("vulnerability", flat=True)
77+
.distinct()
78+
)
79+
80+
if not vulnerabilities:
7981
logger(f"No vulnerability found for aliases {cve_id}")
8082
return 0
8183

82-
Exploit.objects.update_or_create(
83-
vulnerability=vulnerability,
84-
data_source="KEV",
85-
defaults={
86-
"description": kev_vul["shortDescription"],
87-
"date_added": kev_vul["dateAdded"],
88-
"required_action": kev_vul["requiredAction"],
89-
"due_date": kev_vul["dueDate"],
90-
"notes": kev_vul["notes"],
91-
"known_ransomware_campaign_use": True
92-
if kev_vul["knownRansomwareCampaignUse"] == "Known"
93-
else False,
94-
},
95-
)
84+
for vulnerability in vulnerabilities:
85+
Exploit.objects.update_or_create(
86+
vulnerability_id=vulnerability,
87+
data_source="KEV",
88+
defaults={
89+
"description": kev_vul["shortDescription"],
90+
"date_added": kev_vul["dateAdded"],
91+
"required_action": kev_vul["requiredAction"],
92+
"due_date": kev_vul["dueDate"],
93+
"notes": kev_vul["notes"],
94+
"known_ransomware_campaign_use": True
95+
if kev_vul["knownRansomwareCampaignUse"] == "Known"
96+
else False,
97+
},
98+
)
9699
return 1

0 commit comments

Comments
 (0)