Skip to content

Migrate to VulnerableCode API v3#11961

Draft
lamppu wants to merge 3 commits into
oss-review-toolkit:mainfrom
doubleopen-io:11050-vulnerablecode-api-v3
Draft

Migrate to VulnerableCode API v3#11961
lamppu wants to merge 3 commits into
oss-review-toolkit:mainfrom
doubleopen-io:11050-vulnerablecode-api-v3

Conversation

@lamppu

@lamppu lamppu commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Please see the individual commits for details.

* Retrieve all advisories affecting the given [purl]. This is done by paging through the results of the respective
* endpoint until there are no more pages left.
*/
private suspend fun HttpClient.getAllAffectedByAdvisories(purl: String): List<AffectedByAdvisoryV3> {
): List<VulnerabilityReference> =
runCatching {
val sourceUri = URI(url.fixupUrlEscaping())
private fun AffectedByAdvisoryV3.toReferences(issues: MutableList<Issue>): List<VulnerabilityReference> {
* Convert this advisory reference from the VulnerableCode data model to a [VulnerabilityReference] object.
* Populate [issues] if this fails.
*/
private fun AdvisoryReference.toModel(issues: MutableList<Issue>): VulnerabilityReference? =
* Convert this advisory severity from the VulnerableCode data model to a [VulnerabilityReference] object.
* Populate [issues] if this fails.
*/
private fun AdvisorySeverity.toModel(fallbackUrl: String, issues: MutableList<Issue>): VulnerabilityReference? {
*/
private fun VulnerableCodeService.Vulnerability.preferredCommonId(): String {
if (aliases.isEmpty()) return vulnerabilityId
private fun AffectedByAdvisoryV3.preferredCommonId(): String {
@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.44%. Comparing base (6abd9e7) to head (f1d300e).

Additional details and impacted files
@@            Coverage Diff            @@
##               main   #11961   +/-   ##
=========================================
  Coverage     58.44%   58.44%           
  Complexity     1811     1811           
=========================================
  Files           361      361           
  Lines         13504    13504           
  Branches       1385     1385           
=========================================
  Hits           7893     7893           
  Misses         5115     5115           
  Partials        496      496           
Flag Coverage Δ
funTest-external-tools 14.70% <ø> (ø)
test-ubuntu-24.04 41.77% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lamppu lamppu force-pushed the 11050-vulnerablecode-api-v3 branch 3 times, most recently from 4369687 to 820fa06 Compare June 9, 2026 06:43
lamppu added 3 commits June 9, 2026 12:46
Add the public VulnerableCode OpenAPI schema to the client module and
configure kfx to auto-generate Kotlin sources from it.

Signed-off-by: Johanna Lamppu <johanna.lamppu@doubleopen.io>
Patch the VulnerableCode OpenAPI schema on the missing parts for the
`v3/packages` and `v3/affected-by-advisories` endpoints.

Use `"type": "string"` for the `scoring_system` field to work around an
issue with the generated enums [1]. Also remove some elements from the
`required` arrays to work around an issue with generated code regarding
nullable fields [2].

[1]: hfhbd/kfx#259
[2]: hfhbd/kfx#260

Signed-off-by: Johanna Lamppu <johanna.lamppu@doubleopen.io>
Use `v3/packages` bulk search to filter out PURLs that don't have results,
and gather vulnerability details for each affected PURL from the
`v3/affected-by-advisories` results.

`v3/packages` is called with the `details=false` option, because the
additional details - returned with `details=true` - do not contain the data
needed for the references, and so the `v3/affected-by-advisories` needs to
be called separately for each PURL*. `v3/packages` also returns PURLs for
packages that fix vulnerabilities, so these are later dropped if there are
no advisories found.

The v3 model doesn't provide scores for the individual references anymore,
only the `severities` field from the advisory results contain this
information, so gather the references from both the `severities` and
`references` fields.

* There is a `v3/advisories` bulk search but it doesn't provide information
on which PURLs are affected by the listed advisories.

Resolves oss-review-toolkit#11050.

Signed-off-by: Johanna Lamppu <johanna.lamppu@doubleopen.io>
@lamppu lamppu force-pushed the 11050-vulnerablecode-api-v3 branch from 820fa06 to f1d300e Compare June 9, 2026 09:50
@lamppu lamppu marked this pull request as ready for review June 9, 2026 09:52
@lamppu lamppu requested review from a team as code owners June 9, 2026 09:52
Comment thread gradle/libs.versions.toml
kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinPoet"}
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-html = { module = "org.jetbrains.kotlinx:kotlinx-html-jvm", version.ref = "kotlinxHtml" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This line should go one up.

],
"responses": {
"201": {
"200": {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please report the schema issues upstream and add a link to the issue to the first paragraph of the commit message.


"Vulnerable Go packages" should {
"return findings for QUIC" {
// VulnerableCode v3 API doesn't currently have results for this package.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also something that should be reported upstream. It should not depend on the version of the API used whether a package is vulnerable or not.

server.stubPackagesRequest("packages_response_packages.json")
server.stubAffectedByAdvisoriesRequest(idLang, "affected_by_advisories_response_lang.json")
server.stubAffectedByAdvisoriesRequest(idStruts, "affected_by_advisories_response_struts.json")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most other tests you omit the empty line here (i guess, to more strictly follow the AAA pattern), so you should omit it here, too.

server.stubPackagesRequest("response_junit.json", request = generatePackagesRequest(idJUnit))
server.stubPackagesRequest("packages_response_junit.json", request = generatePackagesRequest(idJUnit))
server.stubAffectedByAdvisoriesRequest(idJUnit, "affected_by_advisories_response_junit.json")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

Comment on lines +94 to +97
scoringSystem should beNull()
severity should beNull()
score should beNull()
vector should beNull()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unexpected... any idea why other references have data here, but this reference not? I probably need to debug to see for myself whether the better adaption would be to change the reference URL instead of the expected values.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the pattern is approximately this, two advisories are returned for each vulnerability, one GitHub advisory and one GitLab advisory. The GitHub advisory always seems to contain two severities and the GitLab advisory one severity. The severities are the objects that contain scoring data. Both advisories also have several references, but these only contain url, reference_type and reference_id fields. So they don't have any scoring data available. I'm currently collecting VulnerabilityReference objects from both the severities and the references.

As an example, here are the advisories returned for this "CVE-2023-2976" vulnerability for the Guava package:

        {
            "advisory_id": "github_osv_importer_v2/GHSA-7g45-4rm6-3mm3",
            "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2023/06/GHSA-7g45-4rm6-3mm3/GHSA-7g45-4rm6-3mm3.json",
            "aliases": [
                "CVE-2023-2976"
            ],
            "summary": "Guava vulnerable to insecure use of temporary directory\nUse of Java's default temporary directory for file creation in `FileBackedOutputStream` in Google Guava versions 1.0 to 31.1 on Unix systems and Android Ice Cream Sandwich allows other users and apps on the machine with access to the default Java temporary directory to be able to access the files created by the class.\n\nEven though the security vulnerability is fixed in version 32.0.0, maintainers recommend using version 32.0.1 as version 32.0.0 breaks some functionality under Windows.",
            "severities": [
                {
                    "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2023/06/GHSA-7g45-4rm6-3mm3/GHSA-7g45-4rm6-3mm3.json",
                    "value": "5.5",
                    "scoring_system": "cvssv3.1",
                    "scoring_elements": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                },
                {
                    "url": null,
                    "value": "MODERATE",
                    "scoring_system": "generic_textual",
                    "scoring_elements": ""
                }
            ],
            "weaknesses": [
                {
                    "cwe_id": "552",
                    "name": "Files or Directories Accessible to External Parties",
                    "description": "The product makes files or directories accessible to unauthorized actors, even though they should not be."
                },
                {
                    "cwe_id": "379",
                    "name": "Creation of Temporary File in Directory with Insecure Permissions",
                    "description": "The product creates a temporary file in a directory whose permissions allow unintended actors to determine the file's existence or otherwise access that file."
                }
            ],
            "references": [
                {
                    "url": "https://github.com/google/guava",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://github.com/google/guava/commit/feb83a1c8fd2e7670b244d5afd23cba5aca43284",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://github.com/google/guava/issues/2575",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://github.com/google/guava/issues/6532",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://github.com/google/guava/releases/tag/v32.0.0",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://security.netapp.com/advisory/ntap-20230818-0008",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://security.netapp.com/advisory/ntap-20241108-0002",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-01006.html",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-2976",
                    "reference_type": "",
                    "reference_id": "CVE-2023-2976"
                }
            ],
            "exploitability": "0.5",
            "weighted_severity": "5.0",
            "risk_score": "2.5",
            "related_ssvc_trees": [
                {
                    "vector": "SSVCv2/E:N/A:N/T:T/P:M/B:A/M:M/D:T/2024-04-18T04:00:21Z/",
                    "decision": "Track",
                    "options": [
                        {
                            "Exploitation": "none"
                        },
                        {
                            "Automatable": "no"
                        },
                        {
                            "Technical Impact": "total"
                        },
                        {
                            "Mission Prevalence": "minimal"
                        },
                        {
                            "Public Well-being Impact": "material"
                        },
                        {
                            "Mission & Well-being": "medium"
                        }
                    ],
                    "source_url": "https://github.com/cisagov/vulnrichment/blob/develop/2023/2xxx/CVE-2023-2976.json"
                },
                {
                    "vector": "SSVCv2/E:N/A:N/T:T/P:M/B:A/M:M/D:T/2024-04-18T04:00:21Z/",
                    "decision": "Track",
                    "options": [
                        {
                            "Exploitation": "none"
                        },
                        {
                            "Automatable": "no"
                        },
                        {
                            "Technical Impact": "total"
                        },
                        {
                            "Mission Prevalence": "minimal"
                        },
                        {
                            "Public Well-being Impact": "material"
                        },
                        {
                            "Mission & Well-being": "medium"
                        }
                    ],
                    "source_url": "https://github.com/cisagov/vulnrichment/blob/develop/2023/2xxx/CVE-2023-2976.json"
                }
            ],
            "fixed_by_packages": [
                "pkg:maven/com.google.guava/guava@32.0.0-android"
            ]
        },
        {
            "advisory_id": "gitlab_importer_v2/maven/com.google.guava/guava/CVE-2023-2976",
            "url": "https://gitlab.com/gitlab-org/advisories-community/-/blob/main/maven/com.google.guava/guava/CVE-2023-2976.yml",
            "aliases": [
                "CVE-2023-2976",
                "GHSA-7g45-4rm6-3mm3"
            ],
            "summary": "Guava vulnerable to insecure use of temporary directory\nUse of Java's default temporary directory for file creation in `FileBackedOutputStream` in Google Guava versions 1.0 to 31.1 on Unix systems and Android Ice Cream Sandwich allows other users and apps on the machine with access to the default Java temporary directory to be able to access the files created by the class.\n\nEven though the security vulnerability is fixed in version 32.0.0, maintainers recommend using version 32.0.1 as version 32.0.0 breaks some functionality under Windows.",
            "severities": [
                {
                    "url": "https://gitlab.com/gitlab-org/advisories-community/-/blob/main/maven/com.google.guava/guava/CVE-2023-2976.yml",
                    "value": "None",
                    "scoring_system": "cvssv3.1",
                    "scoring_elements": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
                }
            ],
            "weaknesses": [
                {
                    "cwe_id": "552",
                    "name": "Files or Directories Accessible to External Parties",
                    "description": "The product makes files or directories accessible to unauthorized actors, even though they should not be."
                },
                {
                    "cwe_id": "379",
                    "name": "Creation of Temporary File in Directory with Insecure Permissions",
                    "description": "The product creates a temporary file in a directory whose permissions allow unintended actors to determine the file's existence or otherwise access that file."
                },
                {
                    "cwe_id": "937",
                    "name": "OWASP Top Ten 2013 Category A9 - Using Components with Known Vulnerabilities",
                    "description": "Weaknesses in this category are related to the A9 category in the OWASP Top Ten 2013."
                },
                {
                    "cwe_id": "1035",
                    "name": "OWASP Top Ten 2017 Category A9 - Using Components with Known Vulnerabilities",
                    "description": "Weaknesses in this category are related to the A9 category in the OWASP Top Ten 2017."
                }
            ],
            "references": [
                {
                    "url": "https://github.com/google/guava",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://github.com/google/guava/commit/feb83a1c8fd2e7670b244d5afd23cba5aca43284",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://github.com/google/guava/issues/2575",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://github.com/google/guava/issues/6532",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://github.com/google/guava/releases/tag/v32.0.0",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://security.netapp.com/advisory/ntap-20230818-0008",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://security.netapp.com/advisory/ntap-20241108-0002",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-01006.html",
                    "reference_type": "",
                    "reference_id": ""
                },
                {
                    "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-2976",
                    "reference_type": "",
                    "reference_id": "CVE-2023-2976"
                },
                {
                    "url": "https://github.com/advisories/GHSA-7g45-4rm6-3mm3",
                    "reference_type": "",
                    "reference_id": "GHSA-7g45-4rm6-3mm3"
                }
            ],
            "exploitability": "0.5",
            "weighted_severity": "0.0",
            "risk_score": null,
            "related_ssvc_trees": [
                {
                    "vector": "SSVCv2/E:N/A:N/T:T/P:M/B:A/M:M/D:T/2024-04-18T04:00:21Z/",
                    "decision": "Track",
                    "options": [
                        {
                            "Exploitation": "none"
                        },
                        {
                            "Automatable": "no"
                        },
                        {
                            "Technical Impact": "total"
                        },
                        {
                            "Mission Prevalence": "minimal"
                        },
                        {
                            "Public Well-being Impact": "material"
                        },
                        {
                            "Mission & Well-being": "medium"
                        }
                    ],
                    "source_url": "https://github.com/cisagov/vulnrichment/blob/develop/2023/2xxx/CVE-2023-2976.json"
                }
            ],
            "fixed_by_packages": [
                "pkg:maven/com.google.guava/guava@32.0.0-android"
            ]
        }

@MarcelBochtler

Copy link
Copy Markdown
Member

This will break compatibility with VulnerableCode API V1 and V2, correct?
If so please mark the commit as a breaking change.

@sschuberth

Copy link
Copy Markdown
Member

This will break compatibility with VulnerableCode API V1 and V2, correct? If so please mark the commit as a breaking change.

It will remove API v1 support (API v2 was never implemented), that's correct. But to what extend is that a (user-facing) breaking change according to our rules? From an ORT user perspective, everything should just work as before, i.e. no URL or API key changes needed (letting aside the fact that we now anyway need to use API keys due to general changes in VulnerabileCode rate limiting, independently of the API). @lamppu, please correct me if I'm wrong.

Are you after the code changes to the clients project? Because that's the only area where I see breaking changes to public function. But then again, I believe we probably need dedicated rules for handling breaking changes in any clients project.

@MarcelBochtler

MarcelBochtler commented Jun 10, 2026

Copy link
Copy Markdown
Member

From an ORT user perspective, everything should just work as before, i.e. no URL or API key changes needed

Users that use an on-premises version of VulnerableCode (like Bosch) are forced to upgrade to VulnerableCode v38+, which is not a trivial task with the changes in v37 and v38.

Removing the support for API V1 has therefore quite severe implications on these ORT users.

Maybe according to that rule it might be also a breaking change according to the ORT rules:

Changes to the configuration of plugins that are not backward-compatible.

But that is debatable.

I know that we have to support API V3 soon, but we have to be aware of the implications (of removing V1 support).

@sschuberth

sschuberth commented Jun 10, 2026

Copy link
Copy Markdown
Member

Users that use an on-premises version of VulnerableCode (like Bosch) are forced to upgrade to VulnerableCode v38+, which is not a trivial task with the changes in v37 and v38.

Ah, I wasn't aware that you're running a version that does not even support API v3. So, should we aim for supporting both APIs in parallel, and either create a new advisor, or make the existing advisor configurable WRT which API version to use?

@oheger-bosch

Copy link
Copy Markdown
Member

Ah, I wasn't aware that you're running a version that does not even support API v3. So, should we aim for supporting both APIs in parallel, and either create a new advisor, or make the existing advisor configurable WRT which API version to use?

It would be great to keep the support for the older API at least for a certain grace period. We are trying to upgrade VC as soon as possible, but are currently facing massive problems with an increased database load. It will probably take us some time before we solve this.

@lamppu

lamppu commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Ah, I wasn't aware that you're running a version that does not even support API v3. So, should we aim for supporting both APIs in parallel, and either create a new advisor, or make the existing advisor configurable WRT which API version to use?

It would be great to keep the support for the older API at least for a certain grace period. We are trying to upgrade VC as soon as possible, but are currently facing massive problems with an increased database load. It will probably take us some time before we solve this.

Ok, so would the preferable pattern be to introduce a config option for apiVersion and choose the flow based on that? And what would the default be?

@sschuberth

Copy link
Copy Markdown
Member

@lamppu, let's discuss in the community meeting tomorrow. If a goal is to be able to compare results from API v1 and v3, then I believe we need separate advisors, as we cannot have multiple instances of the same advisor with different options.

@lamppu lamppu marked this pull request as draft June 10, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants