From f99797d8e73a6fc080b10fae72a70d8c1a05322f Mon Sep 17 00:00:00 2001 From: Johanna Lamppu Date: Thu, 28 May 2026 17:39:45 +0300 Subject: [PATCH 1/3] feat(clients): Add kfx codegen setup for VulnerableCode 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 --- REUSE.toml | 5 + clients/vulnerable-code/build.gradle.kts | 22 + clients/vulnerable-code/openapi/README.md | 1 + .../openapi/vulnerablecode.openapi.json | 3419 +++++++++++++++++ .../src/main/kotlin/Constants.kt | 25 + gradle/libs.versions.toml | 2 + 6 files changed, 3474 insertions(+) create mode 100644 clients/vulnerable-code/openapi/README.md create mode 100644 clients/vulnerable-code/openapi/vulnerablecode.openapi.json create mode 100644 clients/vulnerable-code/src/main/kotlin/Constants.kt diff --git a/REUSE.toml b/REUSE.toml index aeb416bec52a2..7c5ec68872e66 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -150,6 +150,11 @@ path = "clients/scanoss/swagger/**" SPDX-FileCopyrightText = "Copyright (c) 2022 SCANOSS" SPDX-License-Identifier = "MIT" +[[annotations]] +path = "clients/vulnerable-code/openapi/**" +SPDX-FileCopyrightText = "Copyright (c) nexB Inc. and others" +SPDX-License-Identifier = "Apache-2.0" + [[annotations]] path = "evaluator/src/main/resources/rules/matrixseqexpl.json" SPDX-FileCopyrightText = "2021 Open Source Automation Development Lab (OSADL) eG" diff --git a/clients/vulnerable-code/build.gradle.kts b/clients/vulnerable-code/build.gradle.kts index 07c28294e74a1..b3ab27cd911e9 100644 --- a/clients/vulnerable-code/build.gradle.kts +++ b/clients/vulnerable-code/build.gradle.kts @@ -17,20 +17,42 @@ * License-Filename: LICENSE */ +import io.github.hfhbd.kfx.openapi.OpenApi + plugins { // Apply precompiled plugins. id("ort-library-conventions") // Apply third-party plugins. + alias(libs.plugins.kfx) alias(libs.plugins.kotlinSerialization) } +kfx { + register("VulnerableCodeApi") { + files.from("openapi/vulnerablecode.openapi.json") + + packageName = "org.ossreviewtoolkit.clients.vulnerablecode" + + dependencies { + compiler(kotlinClasses()) + compiler(kotlinxJson()) + compiler(ktorClient()) + } + + usingKotlinSourceSet(kotlin.sourceSets.main) + } +} + dependencies { + api(ktorLibs.client.core) + api(libs.kotlinx.datetime) api(libs.kotlinx.serialization.core) api(libs.kotlinx.serialization.json) api(libs.okhttp) api(libs.retrofit) + implementation(ktorLibs.http) implementation(libs.retrofit.converter.kotlinxSerialization) } diff --git a/clients/vulnerable-code/openapi/README.md b/clients/vulnerable-code/openapi/README.md new file mode 100644 index 0000000000000..dd8ef279d83cc --- /dev/null +++ b/clients/vulnerable-code/openapi/README.md @@ -0,0 +1 @@ +The OpenAPI schema in this directory was copied from the public [VulnerableCode API schema](https://public.vulnerablecode.io/api/schema/?format=json). diff --git a/clients/vulnerable-code/openapi/vulnerablecode.openapi.json b/clients/vulnerable-code/openapi/vulnerablecode.openapi.json new file mode 100644 index 0000000000000..c017229c80735 --- /dev/null +++ b/clients/vulnerable-code/openapi/vulnerablecode.openapi.json @@ -0,0 +1,3419 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "VulnerableCode API", + "version": "38.6.0", + "description": "\n
\n

VulnerableCode is open data and free software by\n nexB Inc. and others.\n

\n

The VulnerableCode API exposes these endpoints:

\n
    \n
  • \n packages/: main endpoint to lookup for vulnerable packages.\n
  • \n
  • \n vulnerabilities/: secondary endpoint to lookup by vulnerabilities.\n
  • \n
  • \n alias/: secondary endpoint to lookup vulnerabilities by aliases (e.g., CVE)\n
  • \n
  • \n cpes/: secondary endpoint to lookup vulnerabilities by CPE.\n
  • \n
\n
\n", + "termsOfService": "/tos/", + "contact": { + "name": "nexB Inc.", + "url": "https://public.vulnerablecode.io", + "email": "mailto:info@nexb.com" + }, + "license": { + "name": "Source code: Apache-2.0 | Data: CC-BY-SA-4.0", + "url": "https://github.com/nexb/vulnerablecode#license" + } + }, + "paths": { + "/api/v2/packages/": { + "get": { + "operationId": "v2_packages_list", + "parameters": [ + { + "in": "query", + "name": "affected_by_vulnerability", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "fixing_vulnerability", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "purl", + "schema": { + "type": "string" + } + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedPackageV2List" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/packages/all/": { + "get": { + "operationId": "v2_packages_all_retrieve", + "description": "Return a list of Package URLs of vulnerable packages.", + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PackageV2" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/packages/bulk_lookup/": { + "post": { + "operationId": "v2_packages_bulk_lookup_create", + "description": "Return the response for exact PackageURLs requested for.", + "tags": [ + "v2" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PackageurlList" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/PackageurlList" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/PackageurlList" + } + } + }, + "required": true + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageV2" + } + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/packages/bulk_search/": { + "post": { + "operationId": "v2_packages_bulk_search_create", + "description": "Lookup for vulnerable packages using many Package URLs at once.", + "tags": [ + "v2" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PackageBulkSearchRequest" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/PackageBulkSearchRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/PackageBulkSearchRequest" + } + } + }, + "required": true + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageV2" + } + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/packages/lookup/": { + "post": { + "operationId": "v2_packages_lookup_create", + "description": "Return the response for exact PackageURL requested for.", + "tags": [ + "v2" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LookupRequest" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/LookupRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/LookupRequest" + } + } + }, + "required": true + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageV2" + } + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/packages/{id}/": { + "get": { + "operationId": "v2_packages_retrieve", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "description": "A unique integer value identifying this package.", + "required": true + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PackageV2" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/vulnerabilities/": { + "get": { + "operationId": "v2_vulnerabilities_list", + "parameters": [ + { + "in": "query", + "name": "alias", + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "Filter by alias (CVE or other unique identifier)" + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "search", + "required": false, + "in": "query", + "description": "A search term.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "vulnerability_id", + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "Filter by one or more vulnerability IDs" + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedVulnerabilityListList" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/vulnerabilities/{vulnerability_id}/": { + "get": { + "operationId": "v2_vulnerabilities_retrieve", + "parameters": [ + { + "in": "path", + "name": "vulnerability_id", + "schema": { + "type": "string", + "description": "Unique identifier for a vulnerability in the external representation. It is prefixed with VCID-" + }, + "required": true + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VulnerabilityV2" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/codefixes/": { + "get": { + "operationId": "v2_codefixes_list", + "description": "API endpoint that allows viewing CodeFix entries.", + "parameters": [ + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "search", + "required": false, + "in": "query", + "description": "A search term.", + "schema": { + "type": "string" + } + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedCodeFixList" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/codefixes/{id}/": { + "get": { + "operationId": "v2_codefixes_retrieve", + "description": "API endpoint that allows viewing CodeFix entries.", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "description": "A unique integer value identifying this code fix.", + "required": true + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CodeFix" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/pipelines/": { + "post": { + "operationId": "v2_pipelines_create", + "description": "A viewset that provides `create`, `list, `retrieve`, and `update` actions.\nTo use it, override the class and set the `.queryset` and\n`.serializer_class` attributes.", + "tags": [ + "v2" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleCreate" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleCreate" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleCreate" + } + } + }, + "required": true + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleCreate" + } + } + }, + "description": "" + } + } + }, + "get": { + "operationId": "v2_pipelines_list", + "description": "A viewset that provides `create`, `list, `retrieve`, and `update` actions.\nTo use it, override the class and set the `.queryset` and\n`.serializer_class` attributes.", + "parameters": [ + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "search", + "required": false, + "in": "query", + "description": "A search term.", + "schema": { + "type": "string" + } + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedPipelineScheduleAPIList" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/pipelines/{pipeline_id}/": { + "put": { + "operationId": "v2_pipelines_update", + "description": "A viewset that provides `create`, `list, `retrieve`, and `update` actions.\nTo use it, override the class and set the `.queryset` and\n`.serializer_class` attributes.", + "parameters": [ + { + "in": "path", + "name": "pipeline_id", + "schema": { + "type": "string", + "pattern": "^[\\w.]+$" + }, + "required": true + } + ], + "tags": [ + "v2" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleUpdate" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleUpdate" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleUpdate" + } + } + } + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleUpdate" + } + } + }, + "description": "" + } + } + }, + "get": { + "operationId": "v2_pipelines_retrieve", + "description": "A viewset that provides `create`, `list, `retrieve`, and `update` actions.\nTo use it, override the class and set the `.queryset` and\n`.serializer_class` attributes.", + "parameters": [ + { + "in": "path", + "name": "pipeline_id", + "schema": { + "type": "string", + "pattern": "^[\\w.]+$" + }, + "required": true + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleAPI" + } + } + }, + "description": "" + } + } + }, + "patch": { + "operationId": "v2_pipelines_partial_update", + "description": "A viewset that provides `create`, `list, `retrieve`, and `update` actions.\nTo use it, override the class and set the `.queryset` and\n`.serializer_class` attributes.", + "parameters": [ + { + "in": "path", + "name": "pipeline_id", + "schema": { + "type": "string", + "pattern": "^[\\w.]+$" + }, + "required": true + } + ], + "tags": [ + "v2" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PatchedPipelineScheduleAPI" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/PatchedPipelineScheduleAPI" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/PatchedPipelineScheduleAPI" + } + } + } + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PipelineScheduleAPI" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/advisory-codefixes/": { + "get": { + "operationId": "v2_advisory_codefixes_list", + "description": "API endpoint that allows viewing CodeFix entries.", + "parameters": [ + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "search", + "required": false, + "in": "query", + "description": "A search term.", + "schema": { + "type": "string" + } + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedCodeFixV2List" + } + } + }, + "description": "" + } + } + } + }, + "/api/v2/advisory-codefixes/{id}/": { + "get": { + "operationId": "v2_advisory_codefixes_retrieve", + "description": "API endpoint that allows viewing CodeFix entries.", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "description": "A unique integer value identifying this code fix v2.", + "required": true + } + ], + "tags": [ + "v2" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CodeFixV2" + } + } + }, + "description": "" + } + } + } + }, + "/api/v3/packages/": { + "post": { + "operationId": "v3_packages_create", + "tags": [ + "v3" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PackageQuery" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/PackageQuery" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/PackageQuery" + } + } + } + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PackageV3" + } + } + }, + "description": "" + } + } + } + }, + "/api/v3/advisories/": { + "post": { + "operationId": "v3_advisories_create", + "tags": [ + "v3" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdvisoryQuery" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/AdvisoryQuery" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/AdvisoryQuery" + } + } + } + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdvisoryV3" + } + } + }, + "description": "" + } + } + } + }, + "/api/v3/affected-by-advisories/": { + "get": { + "operationId": "v3_affected_by_advisories_list", + "parameters": [ + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "search", + "required": false, + "in": "query", + "description": "A search term.", + "schema": { + "type": "string" + } + } + ], + "tags": [ + "v3" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedAffectedByAdvisoryV3List" + } + } + }, + "description": "" + } + } + } + }, + "/api/v3/affected-by-advisories/{id}/": { + "get": { + "operationId": "v3_affected_by_advisories_retrieve", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "description": "A unique integer value identifying this advisory v2.", + "required": true + } + ], + "tags": [ + "v3" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AffectedByAdvisoryV3" + } + } + }, + "description": "" + } + } + } + }, + "/api/v3/fixing-advisories/": { + "get": { + "operationId": "v3_fixing_advisories_list", + "parameters": [ + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "search", + "required": false, + "in": "query", + "description": "A search term.", + "schema": { + "type": "string" + } + } + ], + "tags": [ + "v3" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedAdvisoryV3List" + } + } + }, + "description": "" + } + } + } + }, + "/api/v3/fixing-advisories/{id}/": { + "get": { + "operationId": "v3_fixing_advisories_retrieve", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "description": "A unique integer value identifying this advisory v2.", + "required": true + } + ], + "tags": [ + "v3" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdvisoryV3" + } + } + }, + "description": "" + } + } + } + }, + "/api/packages/": { + "get": { + "operationId": "packages_list", + "description": "Lookup for vulnerable packages by Package URL.", + "parameters": [ + { + "in": "query", + "name": "name", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "namespace", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "purl", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "qualifiers", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "subpath", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "type", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "version", + "schema": { + "type": "string" + } + } + ], + "tags": [ + "packages" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedPackageList" + } + } + }, + "description": "" + } + } + } + }, + "/api/packages/all/": { + "get": { + "operationId": "packages_all_retrieve", + "description": "Return a list of Package URLs of vulnerable packages.", + "tags": [ + "packages" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Package" + } + } + }, + "description": "" + } + } + } + }, + "/api/packages/bulk_lookup/": { + "post": { + "operationId": "packages_bulk_lookup_create", + "description": "Return the response for exact PackageURLs requested for.", + "tags": [ + "packages" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PackageurlList" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/PackageurlList" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/PackageurlList" + } + } + }, + "required": true + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Package" + } + } + } + }, + "description": "" + } + } + } + }, + "/api/packages/bulk_search/": { + "post": { + "operationId": "packages_bulk_search_create", + "description": "Lookup for vulnerable packages using many Package URLs at once.", + "tags": [ + "packages" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PackageBulkSearchRequest" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/PackageBulkSearchRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/PackageBulkSearchRequest" + } + } + }, + "required": true + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Package" + } + } + } + }, + "description": "" + } + } + } + }, + "/api/packages/lookup/": { + "post": { + "operationId": "packages_lookup_create", + "description": "Return the response for exact PackageURL requested for.", + "tags": [ + "packages" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LookupRequest" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/LookupRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/LookupRequest" + } + } + }, + "required": true + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Package" + } + } + } + }, + "description": "" + } + } + } + }, + "/api/packages/{id}/": { + "get": { + "operationId": "packages_retrieve", + "description": "Lookup for vulnerable packages by Package URL.", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "description": "A unique integer value identifying this package.", + "required": true + } + ], + "tags": [ + "packages" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Package" + } + } + }, + "description": "" + } + } + } + }, + "/api/vulnerabilities/": { + "get": { + "operationId": "vulnerabilities_list", + "description": "Lookup for vulnerabilities affecting packages.", + "parameters": [ + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "vulnerability_id", + "schema": { + "type": "string" + } + } + ], + "tags": [ + "vulnerabilities" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedVulnerabilityList" + } + } + }, + "description": "" + } + } + } + }, + "/api/vulnerabilities/{id}/": { + "get": { + "operationId": "vulnerabilities_retrieve", + "description": "Lookup for vulnerabilities affecting packages.", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "description": "A unique integer value identifying this vulnerability.", + "required": true + } + ], + "tags": [ + "vulnerabilities" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vulnerability" + } + } + }, + "description": "" + } + } + } + }, + "/api/cpes/": { + "get": { + "operationId": "cpes_list", + "description": "Lookup for vulnerabilities by CPE (https://nvd.nist.gov/products/cpe)", + "parameters": [ + { + "in": "query", + "name": "cpe", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "cpes" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedVulnerabilityList" + } + } + }, + "description": "" + } + } + } + }, + "/api/cpes/bulk_search/": { + "post": { + "operationId": "cpes_bulk_search_create", + "description": "Lookup for vulnerabilities using many CPEs at once.", + "tags": [ + "cpes" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vulnerability" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Vulnerability" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Vulnerability" + } + } + }, + "required": true + }, + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vulnerability" + } + } + }, + "description": "" + } + } + } + }, + "/api/cpes/{id}/": { + "get": { + "operationId": "cpes_retrieve", + "description": "Lookup for vulnerabilities by CPE (https://nvd.nist.gov/products/cpe)", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "description": "A unique integer value identifying this vulnerability.", + "required": true + } + ], + "tags": [ + "cpes" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vulnerability" + } + } + }, + "description": "" + } + } + } + }, + "/api/aliases/": { + "get": { + "operationId": "aliases_list", + "description": "Lookup for vulnerabilities by vulnerability aliases such as a CVE\n(https://nvd.nist.gov/general/cve-process).", + "parameters": [ + { + "in": "query", + "name": "alias", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "A page number within the paginated result set.", + "schema": { + "type": "integer" + } + }, + { + "name": "page_size", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "aliases" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedVulnerabilityList" + } + } + }, + "description": "" + } + } + } + }, + "/api/aliases/{id}/": { + "get": { + "operationId": "aliases_retrieve", + "description": "Lookup for vulnerabilities by vulnerability aliases such as a CVE\n(https://nvd.nist.gov/general/cve-process).", + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "description": "A unique integer value identifying this vulnerability.", + "required": true + } + ], + "tags": [ + "aliases" + ], + "security": [ + { + "cookieAuth": [] + }, + { + "tokenAuth": [] + }, + {} + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vulnerability" + } + } + }, + "description": "" + } + } + } + } + }, + "components": { + "schemas": { + "AdvisoryQuery": { + "type": "object", + "properties": { + "purls": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "AdvisoryReference": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "reference_type": { + "type": "string" + }, + "reference_id": { + "type": "string" + } + }, + "required": [ + "reference_id", + "reference_type", + "url" + ] + }, + "AdvisorySeverity": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL to the vulnerability severity", + "maxLength": 1024 + }, + "value": { + "type": "string", + "nullable": true, + "description": "Example: 9.0, Important, High", + "maxLength": 50 + }, + "scoring_system": { + "allOf": [ + { + "$ref": "#/components/schemas/ScoringSystemEnum" + } + ], + "description": "Identifier for the scoring system used. Available choices are: cvssv2: CVSSv2 Base Score,\ncvssv3: CVSSv3 Base Score,\ncvssv3.1: CVSSv3.1 Base Score,\ncvssv4: CVSSv4 Base Score,\nrhbs: RedHat Bugzilla severity,\nrhas: RedHat Aggregate severity,\narchlinux: Archlinux Vulnerability Group Severity,\ncvssv3.1_qr: CVSSv3.1 Qualitative Severity Rating,\ngeneric_textual: Generic textual severity rating,\napache_httpd: Apache Httpd Severity,\napache_tomcat: Apache Tomcat Severity,\nepss: Exploit Prediction Scoring System,\nssvc: Stakeholder-Specific Vulnerability Categorization,\nopenssl: OpenSSL Severity,\nubuntu-priority: Ubuntu Priority " + }, + "scoring_elements": { + "type": "string", + "nullable": true, + "description": "Supporting scoring elements used to compute the score values. For example a CVSS vector string as used to compute a CVSS score.", + "maxLength": 250 + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "UTC Date of publication of the vulnerability severity" + } + }, + "required": [ + "published_at", + "scoring_elements", + "scoring_system", + "url", + "value" + ] + }, + "AdvisoryV3": { + "type": "object", + "properties": { + "advisory_id": { + "type": "string", + "readOnly": true + }, + "url": { + "type": "string", + "format": "uri", + "description": "Link to the advisory on the upstream website", + "maxLength": 200 + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true + }, + "summary": { + "type": "string" + }, + "severities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdvisorySeverity" + } + }, + "weaknesses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdvisoryWeakness" + } + }, + "references": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdvisoryReference" + } + }, + "exploitability": { + "type": "string", + "format": "decimal", + "pattern": "^-?\\d{0,1}(?:\\.\\d{0,1})?$", + "nullable": true, + "description": "Exploitability indicates the likelihood that a vulnerability in a software package could be used by malicious actors to compromise systems, applications, or networks. This metric is determined automatically based on the discovery of known exploits." + }, + "weighted_severity": { + "type": "string", + "format": "decimal", + "pattern": "^-?\\d{0,2}(?:\\.\\d{0,1})?$", + "nullable": true, + "description": "Weighted severity is the highest value calculated by multiplying each severity by its corresponding weight, divided by 10." + }, + "risk_score": { + "type": "string", + "format": "decimal", + "pattern": "^-?\\d{0,2}(?:\\.\\d{0,1})?$", + "nullable": true, + "description": "Risk expressed as a number ranging from 0 to 10. Risk is calculated from weighted severity and exploitability values. It is the maximum value of (the weighted severity multiplied by its exploitability) or 10. Risk = min(weighted severity * exploitability, 10)" + }, + "related_ssvc_trees": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "advisory_id", + "aliases", + "references", + "related_ssvc_trees", + "severities", + "url", + "weaknesses" + ] + }, + "AdvisoryWeakness": { + "type": "object", + "properties": { + "cwe_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "cwe_id", + "description", + "name" + ] + }, + "AffectedByAdvisoryV3": { + "type": "object", + "properties": { + "advisory_id": { + "type": "string", + "readOnly": true + }, + "url": { + "type": "string", + "format": "uri", + "description": "Link to the advisory on the upstream website", + "maxLength": 200 + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true + }, + "summary": { + "type": "string" + }, + "severities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdvisorySeverity" + } + }, + "weaknesses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdvisoryWeakness" + } + }, + "references": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdvisoryReference" + } + }, + "exploitability": { + "type": "string", + "format": "decimal", + "pattern": "^-?\\d{0,1}(?:\\.\\d{0,1})?$", + "nullable": true, + "description": "Exploitability indicates the likelihood that a vulnerability in a software package could be used by malicious actors to compromise systems, applications, or networks. This metric is determined automatically based on the discovery of known exploits." + }, + "weighted_severity": { + "type": "string", + "format": "decimal", + "pattern": "^-?\\d{0,2}(?:\\.\\d{0,1})?$", + "nullable": true, + "description": "Weighted severity is the highest value calculated by multiplying each severity by its corresponding weight, divided by 10." + }, + "risk_score": { + "type": "string", + "format": "decimal", + "pattern": "^-?\\d{0,2}(?:\\.\\d{0,1})?$", + "nullable": true, + "description": "Risk expressed as a number ranging from 0 to 10. Risk is calculated from weighted severity and exploitability values. It is the maximum value of (the weighted severity multiplied by its exploitability) or 10. Risk = min(weighted severity * exploitability, 10)" + }, + "related_ssvc_trees": { + "type": "string", + "readOnly": true + }, + "fixed_by_packages": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "advisory_id", + "aliases", + "fixed_by_packages", + "references", + "related_ssvc_trees", + "severities", + "url", + "weaknesses" + ] + }, + "Alias": { + "type": "object", + "description": "Used for nesting inside package focused APIs.", + "properties": { + "alias": { + "type": "string", + "description": "An alias is a unique vulnerability identifier in some database, such as CVE-2020-2233", + "maxLength": 50 + } + }, + "required": [ + "alias" + ] + }, + "CodeFix": { + "type": "object", + "description": "Serializer for the CodeFix model.\nProvides detailed information about a code fix.", + "properties": { + "id": { + "type": "integer", + "readOnly": true + }, + "commits": { + "type": "object", + "additionalProperties": {}, + "description": "List of commit identifiers using VCS URLs associated with the code change." + }, + "pulls": { + "type": "object", + "additionalProperties": {}, + "description": "List of pull request URLs associated with the code change." + }, + "downloads": { + "type": "object", + "additionalProperties": {}, + "description": "List of download URLs for the patched code." + }, + "patch": { + "type": "string", + "nullable": true, + "description": "The code change as a patch in unified diff format." + }, + "affected_vulnerability_id": { + "type": "string", + "readOnly": true, + "description": "ID of the affected vulnerability." + }, + "affected_package_purl": { + "type": "string", + "readOnly": true, + "description": "PURL of the affected package." + }, + "fixed_package_purl": { + "type": "string", + "readOnly": true, + "description": "PURL of the fixing package (if available)." + }, + "notes": { + "type": "string", + "nullable": true, + "description": "Notes or instructions about this code change." + }, + "references": { + "type": "object", + "additionalProperties": {}, + "description": "URL references related to this code change." + }, + "is_reviewed": { + "type": "boolean", + "description": "Indicates if this code change has been reviewed." + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "description": "Timestamp when the code fix was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "description": "Timestamp when the code fix was last updated." + } + }, + "required": [ + "affected_package_purl", + "affected_vulnerability_id", + "created_at", + "fixed_package_purl", + "id", + "updated_at" + ] + }, + "CodeFixV2": { + "type": "object", + "description": "Serializer for the CodeFix model.\nProvides detailed information about a code fix.", + "properties": { + "id": { + "type": "integer", + "readOnly": true + }, + "commits": { + "type": "object", + "additionalProperties": {}, + "description": "List of commit identifiers using VCS URLs associated with the code change." + }, + "pulls": { + "type": "object", + "additionalProperties": {}, + "description": "List of pull request URLs associated with the code change." + }, + "downloads": { + "type": "object", + "additionalProperties": {}, + "description": "List of download URLs for the patched code." + }, + "patch": { + "type": "string", + "nullable": true, + "description": "The code change as a patch in unified diff format." + }, + "affected_advisory_id": { + "type": "string", + "readOnly": true, + "description": "ID of the advisory affecting the package." + }, + "affected_package_purl": { + "type": "string", + "readOnly": true, + "description": "PURL of the affected package." + }, + "fixed_package_purl": { + "type": "string", + "readOnly": true, + "description": "PURL of the fixing package (if available)." + }, + "notes": { + "type": "string", + "nullable": true, + "description": "Notes or instructions about this code change." + }, + "references": { + "type": "object", + "additionalProperties": {}, + "description": "URL references related to this code change." + }, + "is_reviewed": { + "type": "boolean", + "description": "Indicates if this code change has been reviewed." + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "description": "Timestamp when the code fix was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "description": "Timestamp when the code fix was last updated." + } + }, + "required": [ + "affected_advisory_id", + "affected_package_purl", + "created_at", + "fixed_package_purl", + "id", + "updated_at" + ] + }, + "Exploit": { + "type": "object", + "properties": { + "date_added": { + "type": "string", + "format": "date", + "nullable": true, + "description": "The date the vulnerability was added to an exploit catalog." + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description of the vulnerability in an exploit catalog, often a refinement of the original CVE description" + }, + "required_action": { + "type": "string", + "nullable": true, + "description": "The required action to address the vulnerability, typically to apply vendor updates or apply vendor mitigations or to discontinue use." + }, + "due_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "The date the required action is due, which applies to all USA federal civilian executive branch (FCEB) agencies, but all organizations are strongly encouraged to execute the required action" + }, + "notes": { + "type": "string", + "nullable": true, + "description": "Additional notes and resources about the vulnerability, often a URL to vendor instructions." + }, + "known_ransomware_campaign_use": { + "type": "boolean", + "description": "Known' if this vulnerability is known to have been leveraged as part of a ransomware campaign; \n or 'Unknown' if there is no confirmation that the vulnerability has been utilized for ransomware." + }, + "source_date_published": { + "type": "string", + "format": "date", + "nullable": true, + "description": "The date that the exploit was published or disclosed." + }, + "exploit_type": { + "type": "string", + "nullable": true, + "description": "The type of the exploit as provided by the original upstream data source." + }, + "platform": { + "type": "string", + "nullable": true, + "description": "The platform associated with the exploit as provided by the original upstream data source." + }, + "source_date_updated": { + "type": "string", + "format": "date", + "nullable": true, + "description": "The date the exploit was updated in the original upstream data source." + }, + "data_source": { + "type": "string", + "nullable": true, + "description": "The source of the exploit information, such as CISA KEV, exploitdb, metaspoit, or others." + }, + "source_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The URL to the exploit as provided in the original upstream data source.", + "maxLength": 200 + } + } + }, + "LookupRequest": { + "type": "object", + "properties": { + "purl": { + "type": "string", + "description": "PackageURL strings in canonical form." + } + }, + "required": [ + "purl" + ] + }, + "MinimalPackage": { + "type": "object", + "description": "Used for nesting inside vulnerability focused APIs.", + "properties": { + "url": { + "type": "string", + "format": "uri", + "readOnly": true + }, + "purl": { + "type": "string" + }, + "is_vulnerable": { + "type": "boolean" + }, + "affected_by_vulnerabilities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VulnVulnID" + } + }, + "resource_url": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "affected_by_vulnerabilities", + "is_vulnerable", + "purl", + "resource_url", + "url" + ] + }, + "Package": { + "type": "object", + "description": "Lookup software package using Package URLs", + "properties": { + "url": { + "type": "string", + "format": "uri", + "readOnly": true + }, + "purl": { + "type": "string" + }, + "type": { + "type": "string", + "description": "A short code to identify the type of this package. For example: gem for a Rubygem, docker for a container, pypi for a Python Wheel or Egg, maven for a Maven Jar, deb for a Debian package, etc.", + "maxLength": 16 + }, + "namespace": { + "type": "string", + "description": "Package name prefix, such as Maven groupid, Docker image owner, GitHub user or organization, etc.", + "maxLength": 255 + }, + "name": { + "type": "string", + "description": "Name of the package.", + "maxLength": 100 + }, + "version": { + "type": "string", + "description": "Version of the package.", + "maxLength": 100 + }, + "qualifiers": { + "type": "string", + "readOnly": true + }, + "subpath": { + "type": "string", + "description": "Extra subpath within a package, relative to the package root.", + "maxLength": 200 + }, + "is_vulnerable": { + "type": "boolean" + }, + "next_non_vulnerable_version": { + "type": "string", + "readOnly": true + }, + "latest_non_vulnerable_version": { + "type": "string", + "readOnly": true + }, + "affected_by_vulnerabilities": { + "type": "object", + "additionalProperties": {}, + "readOnly": true + }, + "fixing_vulnerabilities": { + "type": "object", + "additionalProperties": {}, + "readOnly": true + }, + "risk_score": { + "type": "string", + "format": "decimal", + "pattern": "^-?\\d{0,2}(?:\\.\\d{0,1})?$", + "nullable": true, + "description": "Risk score between 0.00 and 10.00, where higher values indicate greater vulnerability risk for the package." + }, + "resource_url": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "affected_by_vulnerabilities", + "fixing_vulnerabilities", + "is_vulnerable", + "latest_non_vulnerable_version", + "name", + "namespace", + "next_non_vulnerable_version", + "purl", + "qualifiers", + "resource_url", + "subpath", + "type", + "url", + "version" + ] + }, + "PackageBulkSearchRequest": { + "type": "object", + "properties": { + "purls": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of PackageURL strings in canonical form." + }, + "purl_only": { + "type": "boolean", + "default": false + }, + "plain_purl": { + "type": "boolean", + "default": false + } + }, + "required": [ + "purls" + ] + }, + "PackageQuery": { + "type": "object", + "properties": { + "purls": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "boolean", + "default": false + }, + "ignore_qualifiers_subpath": { + "type": "boolean", + "default": false + } + } + }, + "PackageV2": { + "type": "object", + "properties": { + "purl": { + "type": "string" + }, + "affected_by_vulnerabilities": { + "type": "string", + "readOnly": true + }, + "fixing_vulnerabilities": { + "type": "string", + "readOnly": true + }, + "next_non_vulnerable_version": { + "type": "string", + "readOnly": true + }, + "latest_non_vulnerable_version": { + "type": "string", + "readOnly": true + }, + "risk_score": { + "type": "number", + "format": "double", + "readOnly": true + } + }, + "required": [ + "affected_by_vulnerabilities", + "fixing_vulnerabilities", + "latest_non_vulnerable_version", + "next_non_vulnerable_version", + "purl", + "risk_score" + ] + }, + "PackageV3": { + "type": "object", + "properties": { + "purl": { + "type": "string" + }, + "affected_by_vulnerabilities": { + "type": "string", + "readOnly": true + }, + "affected_by_vulnerabilities_url": { + "type": "string", + "readOnly": true + }, + "fixing_vulnerabilities": { + "type": "string", + "readOnly": true + }, + "fixing_vulnerabilities_url": { + "type": "string", + "readOnly": true + }, + "next_non_vulnerable_version": { + "type": "string", + "readOnly": true + }, + "latest_non_vulnerable_version": { + "type": "string", + "readOnly": true + }, + "risk_score": { + "type": "number", + "format": "double", + "readOnly": true + } + }, + "required": [ + "affected_by_vulnerabilities", + "affected_by_vulnerabilities_url", + "fixing_vulnerabilities", + "fixing_vulnerabilities_url", + "latest_non_vulnerable_version", + "next_non_vulnerable_version", + "purl", + "risk_score" + ] + }, + "PackageurlList": { + "type": "object", + "properties": { + "purls": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of PackageURL strings in canonical form." + } + }, + "required": [ + "purls" + ] + }, + "PaginatedAdvisoryV3List": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdvisoryV3" + } + } + } + }, + "PaginatedAffectedByAdvisoryV3List": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AffectedByAdvisoryV3" + } + } + } + }, + "PaginatedCodeFixList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CodeFix" + } + } + } + }, + "PaginatedCodeFixV2List": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CodeFixV2" + } + } + } + }, + "PaginatedPackageList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Package" + } + } + } + }, + "PaginatedPackageV2List": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageV2" + } + } + } + }, + "PaginatedPipelineScheduleAPIList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PipelineScheduleAPI" + } + } + } + }, + "PaginatedVulnerabilityList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Vulnerability" + } + } + } + }, + "PaginatedVulnerabilityListList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=4" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?page=2" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VulnerabilityList" + } + } + } + }, + "PatchedPipelineScheduleAPI": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "readOnly": true + }, + "pipeline_id": { + "type": "string", + "description": "Identify a registered Pipeline class.", + "maxLength": 600 + }, + "is_active": { + "type": "boolean", + "nullable": true, + "description": "When set to True, this Pipeline is active. When set to False, this Pipeline is inactive and not run." + }, + "live_logging": { + "type": "boolean", + "description": "When enabled logs will be streamed live during pipeline execution. For legacy importers and improvers, logs are always made available only after execution completes." + }, + "run_interval": { + "type": "integer", + "maximum": 8760, + "minimum": 1, + "description": "Number of hours to wait between run of this pipeline." + }, + "execution_timeout": { + "type": "integer", + "maximum": 72, + "minimum": 1, + "description": "Number hours before pipeline execution is forcefully terminated." + }, + "created_date": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "schedule_work_id": { + "type": "string", + "nullable": true, + "description": "Identifier used to manage the periodic run job.", + "maxLength": 255 + }, + "next_run_date": { + "type": "string", + "readOnly": true + }, + "latest_run": { + "type": "string", + "readOnly": true + } + } + }, + "PipelineScheduleAPI": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "readOnly": true + }, + "pipeline_id": { + "type": "string", + "description": "Identify a registered Pipeline class.", + "maxLength": 600 + }, + "is_active": { + "type": "boolean", + "nullable": true, + "description": "When set to True, this Pipeline is active. When set to False, this Pipeline is inactive and not run." + }, + "live_logging": { + "type": "boolean", + "description": "When enabled logs will be streamed live during pipeline execution. For legacy importers and improvers, logs are always made available only after execution completes." + }, + "run_interval": { + "type": "integer", + "maximum": 8760, + "minimum": 1, + "description": "Number of hours to wait between run of this pipeline." + }, + "execution_timeout": { + "type": "integer", + "maximum": 72, + "minimum": 1, + "description": "Number hours before pipeline execution is forcefully terminated." + }, + "created_date": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "schedule_work_id": { + "type": "string", + "nullable": true, + "description": "Identifier used to manage the periodic run job.", + "maxLength": 255 + }, + "next_run_date": { + "type": "string", + "readOnly": true + }, + "latest_run": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "created_date", + "latest_run", + "next_run_date", + "pipeline_id", + "url" + ] + }, + "PipelineScheduleCreate": { + "type": "object", + "properties": { + "pipeline_id": { + "type": "string", + "description": "Identify a registered Pipeline class.", + "maxLength": 600 + }, + "is_active": { + "type": "boolean", + "nullable": true, + "description": "When set to True, this Pipeline is active. When set to False, this Pipeline is inactive and not run." + }, + "run_interval": { + "type": "integer", + "maximum": 8760, + "minimum": 1, + "description": "Number of hours to wait between run of this pipeline." + }, + "live_logging": { + "type": "boolean", + "description": "When enabled logs will be streamed live during pipeline execution. For legacy importers and improvers, logs are always made available only after execution completes." + }, + "execution_timeout": { + "type": "integer", + "maximum": 72, + "minimum": 1, + "description": "Number hours before pipeline execution is forcefully terminated." + } + }, + "required": [ + "pipeline_id" + ] + }, + "PipelineScheduleUpdate": { + "type": "object", + "properties": { + "is_active": { + "type": "boolean", + "nullable": true, + "description": "When set to True, this Pipeline is active. When set to False, this Pipeline is inactive and not run." + }, + "run_interval": { + "type": "integer", + "maximum": 8760, + "minimum": 1, + "description": "Number of hours to wait between run of this pipeline." + }, + "live_logging": { + "type": "boolean", + "description": "When enabled logs will be streamed live during pipeline execution. For legacy importers and improvers, logs are always made available only after execution completes." + }, + "execution_timeout": { + "type": "integer", + "maximum": 72, + "minimum": 1, + "description": "Number hours before pipeline execution is forcefully terminated." + } + } + }, + "ScoringSystemEnum": { + "enum": [ + "cvssv2", + "cvssv3", + "cvssv3.1", + "cvssv4", + "rhbs", + "rhas", + "archlinux", + "cvssv3.1_qr", + "generic_textual", + "apache_httpd", + "apache_tomcat", + "epss", + "ssvc", + "openssl", + "ubuntu-priority" + ], + "type": "string" + }, + "VulnVulnID": { + "type": "object", + "description": "Serializer for the series of vulnerability IDs.", + "properties": { + "vulnerability": { + "type": "string" + } + }, + "required": [ + "vulnerability" + ] + }, + "Vulnerability": { + "type": "object", + "description": "Base serializer containing common methods.", + "properties": { + "url": { + "type": "string", + "format": "uri", + "readOnly": true + }, + "vulnerability_id": { + "type": "string", + "description": "Unique identifier for a vulnerability in the external representation. It is prefixed with VCID-", + "maxLength": 20 + }, + "summary": { + "type": "string", + "description": "Summary of the vulnerability" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Alias" + } + }, + "fixed_packages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MinimalPackage" + }, + "readOnly": true + }, + "affected_packages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MinimalPackage" + }, + "readOnly": true + }, + "references": { + "type": "string", + "readOnly": true + }, + "weaknesses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Weakness" + } + }, + "exploits": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Exploit" + }, + "readOnly": true + }, + "severity_range_score": { + "type": "string", + "readOnly": true + }, + "exploitability": { + "type": "string", + "format": "decimal", + "pattern": "^-?\\d{0,1}(?:\\.\\d{0,1})?$", + "nullable": true, + "description": "Exploitability indicates the likelihood that a vulnerability in a software package could be used by malicious actors to compromise systems, applications, or networks. This metric is determined automatically based on the discovery of known exploits." + }, + "weighted_severity": { + "type": "string", + "format": "decimal", + "pattern": "^-?\\d{0,2}(?:\\.\\d{0,1})?$", + "nullable": true, + "description": "Weighted severity is the highest value calculated by multiplying each severity by its corresponding weight, divided by 10." + }, + "risk_score": { + "type": "string", + "readOnly": true + }, + "resource_url": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "affected_packages", + "aliases", + "exploits", + "fixed_packages", + "references", + "resource_url", + "risk_score", + "severity_range_score", + "url", + "weaknesses" + ] + }, + "VulnerabilityList": { + "type": "object", + "properties": { + "vulnerability_id": { + "type": "string", + "description": "Unique identifier for a vulnerability in the external representation. It is prefixed with VCID-", + "maxLength": 20 + }, + "url": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "url" + ] + }, + "VulnerabilityReferenceV2": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "reference_type": { + "type": "string" + }, + "reference_id": { + "type": "string" + } + }, + "required": [ + "reference_id", + "reference_type", + "url" + ] + }, + "VulnerabilitySeverityV2": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL to the vulnerability severity", + "maxLength": 1024 + }, + "value": { + "type": "string", + "description": "Example: 9.0, Important, High", + "maxLength": 50 + }, + "scoring_system": { + "allOf": [ + { + "$ref": "#/components/schemas/ScoringSystemEnum" + } + ], + "description": "Identifier for the scoring system used. Available choices are: cvssv2: CVSSv2 Base Score,\ncvssv3: CVSSv3 Base Score,\ncvssv3.1: CVSSv3.1 Base Score,\ncvssv4: CVSSv4 Base Score,\nrhbs: RedHat Bugzilla severity,\nrhas: RedHat Aggregate severity,\narchlinux: Archlinux Vulnerability Group Severity,\ncvssv3.1_qr: CVSSv3.1 Qualitative Severity Rating,\ngeneric_textual: Generic textual severity rating,\napache_httpd: Apache Httpd Severity,\napache_tomcat: Apache Tomcat Severity,\nepss: Exploit Prediction Scoring System,\nssvc: Stakeholder-Specific Vulnerability Categorization,\nopenssl: OpenSSL Severity,\nubuntu-priority: Ubuntu Priority " + }, + "scoring_elements": { + "type": "string", + "nullable": true, + "description": "Supporting scoring elements used to compute the score values. For example a CVSS vector string as used to compute a CVSS score.", + "maxLength": 250 + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "UTC Date of publication of the vulnerability severity" + } + }, + "required": [ + "scoring_system", + "value" + ] + }, + "VulnerabilityV2": { + "type": "object", + "properties": { + "vulnerability_id": { + "type": "string", + "description": "Unique identifier for a vulnerability in the external representation. It is prefixed with VCID-", + "maxLength": 20 + }, + "aliases": { + "type": "string", + "readOnly": true + }, + "summary": { + "type": "string", + "description": "Summary of the vulnerability" + }, + "severities": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VulnerabilitySeverityV2" + } + }, + "weaknesses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WeaknessV2" + } + }, + "references": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VulnerabilityReferenceV2" + } + }, + "exploitability": { + "type": "number", + "format": "double", + "readOnly": true + }, + "weighted_severity": { + "type": "number", + "format": "double", + "readOnly": true + }, + "risk_score": { + "type": "number", + "format": "double", + "readOnly": true + } + }, + "required": [ + "aliases", + "exploitability", + "references", + "risk_score", + "severities", + "weaknesses", + "weighted_severity" + ] + }, + "Weakness": { + "type": "object", + "description": "Used for nesting inside weakness focused APIs.", + "properties": { + "cwe_id": { + "type": "integer", + "maximum": 2147483647, + "minimum": -2147483648, + "description": "CWE id" + }, + "name": { + "type": "string", + "readOnly": true + }, + "description": { + "type": "string", + "readOnly": true + } + }, + "required": [ + "cwe_id", + "description", + "name" + ] + }, + "WeaknessV2": { + "type": "object", + "properties": { + "cwe_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "cwe_id", + "description", + "name" + ] + } + }, + "securitySchemes": { + "cookieAuth": { + "type": "apiKey", + "in": "cookie", + "name": "sessionid" + }, + "tokenAuth": { + "type": "apiKey", + "in": "header", + "name": "Authorization", + "description": "Token-based authentication with required prefix \"Token\"" + } + } + } +} diff --git a/clients/vulnerable-code/src/main/kotlin/Constants.kt b/clients/vulnerable-code/src/main/kotlin/Constants.kt new file mode 100644 index 0000000000000..556a3bcad699c --- /dev/null +++ b/clients/vulnerable-code/src/main/kotlin/Constants.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2026 The ORT Project Copyright Holders + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +package org.ossreviewtoolkit.clients.vulnerablecode + +/** + * Base URL of the public VulnerableCode API. + */ +const val VULNERABLE_CODE_BASE_URL = "https://public.vulnerablecode.io/" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 30ed8d6556197..22e5c96d9187b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,6 +42,7 @@ kaml = "0.104.0" kotest = "6.1.11" kotlinPoet = "2.3.0" kotlinxCoroutines = "1.11.0" +kotlinxDatetime = "0.8.0" kotlinxHtml = "0.12.0" kotlinxSerialization = "1.11.0" kottage = "1.11.0" @@ -143,6 +144,7 @@ kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoet"} 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" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } kotlinx-serialization-toml = { module = "net.peanuuutz.tomlkt:tomlkt", version.ref = "tomlkt" } From 7628f289921d319693783b4bb530809bf588d763 Mon Sep 17 00:00:00 2001 From: Johanna Lamppu Date: Fri, 5 Jun 2026 08:39:28 +0300 Subject: [PATCH 2/3] refactor(clients): Patch schema and add workarounds 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]: https://github.com/hfhbd/kfx/issues/259 [2]: https://github.com/hfhbd/kfx/issues/260 Signed-off-by: Johanna Lamppu --- .../openapi/vulnerablecode.openapi.json | 86 +++++++++++++++---- 1 file changed, 67 insertions(+), 19 deletions(-) diff --git a/clients/vulnerable-code/openapi/vulnerablecode.openapi.json b/clients/vulnerable-code/openapi/vulnerablecode.openapi.json index c017229c80735..4cbe5336ad925 100644 --- a/clients/vulnerable-code/openapi/vulnerablecode.openapi.json +++ b/clients/vulnerable-code/openapi/vulnerablecode.openapi.json @@ -931,11 +931,11 @@ {} ], "responses": { - "201": { + "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PackageV3" + "$ref": "#/components/schemas/PaginatedPurlList" } } }, @@ -1911,11 +1911,8 @@ "maxLength": 50 }, "scoring_system": { - "allOf": [ - { - "$ref": "#/components/schemas/ScoringSystemEnum" - } - ], + "type": "string", + "nullable": true, "description": "Identifier for the scoring system used. Available choices are: cvssv2: CVSSv2 Base Score,\ncvssv3: CVSSv3 Base Score,\ncvssv3.1: CVSSv3.1 Base Score,\ncvssv4: CVSSv4 Base Score,\nrhbs: RedHat Bugzilla severity,\nrhas: RedHat Aggregate severity,\narchlinux: Archlinux Vulnerability Group Severity,\ncvssv3.1_qr: CVSSv3.1 Qualitative Severity Rating,\ngeneric_textual: Generic textual severity rating,\napache_httpd: Apache Httpd Severity,\napache_tomcat: Apache Tomcat Severity,\nepss: Exploit Prediction Scoring System,\nssvc: Stakeholder-Specific Vulnerability Categorization,\nopenssl: OpenSSL Severity,\nubuntu-priority: Ubuntu Priority " }, "scoring_elements": { @@ -1930,14 +1927,7 @@ "nullable": true, "description": "UTC Date of publication of the vulnerability severity" } - }, - "required": [ - "published_at", - "scoring_elements", - "scoring_system", - "url", - "value" - ] + } }, "AdvisoryV3": { "type": "object", @@ -2098,11 +2088,17 @@ "description": "Risk expressed as a number ranging from 0 to 10. Risk is calculated from weighted severity and exploitability values. It is the maximum value of (the weighted severity multiplied by its exploitability) or 10. Risk = min(weighted severity * exploitability, 10)" }, "related_ssvc_trees": { - "type": "string", + "type": "array", + "items": { + "$ref": "#/components/schemas/RelatedSSVCTree" + }, "readOnly": true }, "fixed_by_packages": { - "type": "string", + "type": "array", + "items": { + "type": "string" + }, "readOnly": true } }, @@ -2613,9 +2609,7 @@ }, "required": [ "affected_by_vulnerabilities", - "affected_by_vulnerabilities_url", "fixing_vulnerabilities", - "fixing_vulnerabilities_url", "latest_non_vulnerable_version", "next_non_vulnerable_version", "purl", @@ -2854,6 +2848,34 @@ } } }, + "PaginatedPurlList": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "count", + "results" + ] + }, "PaginatedVulnerabilityList": { "type": "object", "required": [ @@ -3093,6 +3115,32 @@ } } }, + "RelatedSSVCTree": { + "type": "object", + "properties": { + "vector": { + "type": "string" + }, + "decision": { + "type": "string" + }, + "options": { + "type": "array", + "items": { + "type": "object" + } + }, + "source_url": { + "type": "string" + } + }, + "required": [ + "decision", + "options", + "source_url", + "vector" + ] + }, "ScoringSystemEnum": { "enum": [ "cvssv2", From f1d300ed952787e2e944830e35c68276c327f6b7 Mon Sep 17 00:00:00 2001 From: Johanna Lamppu Date: Mon, 8 Jun 2026 13:05:01 +0300 Subject: [PATCH 3/3] refactor(plugins): Migrate to VulnerableCode API v3 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 https://github.com/oss-review-toolkit/ort/issues/11050. Signed-off-by: Johanna Lamppu --- clients/vulnerable-code/build.gradle.kts | 3 - .../src/main/kotlin/VulnerableCodeService.kt | 187 -------------- .../advisors/vulnerable-code/build.gradle.kts | 5 +- .../funTest/kotlin/VulnerableCodeFunTest.kt | 149 ++++++++++-- .../src/main/kotlin/VulnerableCode.kt | 225 ++++++++++++----- .../kotlin/VulnerableCodeConfiguration.kt | 4 +- .../src/test/kotlin/VulnerableCodeTest.kt | 230 ++++++++++++++++-- ...affected_by_advisories_response_junit.json | 34 +++ .../affected_by_advisories_response_lang.json | 57 +++++ ...y_advisories_response_lang_no_results.json | 7 + ...affected_by_advisories_response_log4j.json | 33 +++ ..._advisories_response_log4j_no_aliases.json | 18 ++ ...visories_response_multiple_advisories.json | 60 +++++ ...affected_by_advisories_response_page1.json | 136 +++++++++++ ...affected_by_advisories_response_page2.json | 104 ++++++++ ...sories_response_severities_references.json | 39 +++ ...ffected_by_advisories_response_struts.json | 51 ++++ .../__files/packages_response_junit.json | 8 + .../__files/packages_response_log4j.json | 8 + .../__files/packages_response_packages.json | 9 + .../__files/packages_response_page1.json | 9 + .../__files/packages_response_page2.json | 8 + ...packages_response_unexpected_packages.json | 10 + .../resources/__files/response_junit.json | 57 ----- .../resources/__files/response_log4j.json | 52 ---- .../resources/__files/response_packages.json | 92 ------- .../response_packages_no_vulnerabilities.json | 67 ----- .../__files/response_unexpected_packages.json | 117 --------- 28 files changed, 1108 insertions(+), 671 deletions(-) delete mode 100644 clients/vulnerable-code/src/main/kotlin/VulnerableCodeService.kt create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_junit.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_lang.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_lang_no_results.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_log4j.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_log4j_no_aliases.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_multiple_advisories.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_page1.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_page2.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_severities_references.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_struts.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_junit.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_log4j.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_packages.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_page1.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_page2.json create mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_unexpected_packages.json delete mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/response_junit.json delete mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/response_log4j.json delete mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/response_packages.json delete mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/response_packages_no_vulnerabilities.json delete mode 100644 plugins/advisors/vulnerable-code/src/test/resources/__files/response_unexpected_packages.json diff --git a/clients/vulnerable-code/build.gradle.kts b/clients/vulnerable-code/build.gradle.kts index b3ab27cd911e9..cfae7b1054b29 100644 --- a/clients/vulnerable-code/build.gradle.kts +++ b/clients/vulnerable-code/build.gradle.kts @@ -49,11 +49,8 @@ dependencies { api(libs.kotlinx.datetime) api(libs.kotlinx.serialization.core) api(libs.kotlinx.serialization.json) - api(libs.okhttp) - api(libs.retrofit) implementation(ktorLibs.http) - implementation(libs.retrofit.converter.kotlinxSerialization) } description = "A client to communicate with the API of a VulnerableCode instance." diff --git a/clients/vulnerable-code/src/main/kotlin/VulnerableCodeService.kt b/clients/vulnerable-code/src/main/kotlin/VulnerableCodeService.kt deleted file mode 100644 index 20b3dd6b3b61b..0000000000000 --- a/clients/vulnerable-code/src/main/kotlin/VulnerableCodeService.kt +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2021 The ORT Project Copyright Holders - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ - -package org.ossreviewtoolkit.clients.vulnerablecode - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonNames -import kotlinx.serialization.json.JsonNamingStrategy - -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient - -import retrofit2.Retrofit -import retrofit2.converter.kotlinx.serialization.asConverterFactory -import retrofit2.http.Body -import retrofit2.http.POST - -/** - * Interface for a REST service that allows interaction with the VulnerableCode API to query information about - * vulnerabilities detected for specific packages. - * The list of data sources is documented at https://github.com/aboutcode-org/vulnerablecode/blob/main/SOURCES.rst. - */ -interface VulnerableCodeService { - companion object { - /** - * The URL to version 1 of the API. Version 2 is currently in the works. - */ - const val PUBLIC_SERVER_URL = "https://public.vulnerablecode.io/api/" - - /** - * The JSON (de-)serialization object used by this service. - */ - val JSON = Json { - ignoreUnknownKeys = true - namingStrategy = JsonNamingStrategy.SnakeCase - } - - /** - * Create a new service instance that connects to the [url] specified and uses the optionally provided [client]. - */ - fun create(url: String? = null, apiKey: String? = null, client: OkHttpClient? = null): VulnerableCodeService { - val vulnerableCodeClient = (client ?: OkHttpClient()).run { - takeIf { apiKey == null } ?: run { - newBuilder().addInterceptor { chain -> - val requestBuilder = chain.request().newBuilder().apply { - header("Authorization", "Token $apiKey") - } - - chain.proceed(requestBuilder.build()) - }.build() - } - } - - val contentType = "application/json".toMediaType() - val retrofit = Retrofit.Builder() - .client(vulnerableCodeClient) - .baseUrl(url ?: PUBLIC_SERVER_URL) - .addConverterFactory(JSON.asConverterFactory(contentType)) - .build() - - return retrofit.create(VulnerableCodeService::class.java) - } - } - - /** - * Data class that represents a score assigned to a vulnerability. A source of vulnerability information can - * provide multiple score values using different scoring systems. - * - * See https://github.com/aboutcode-org/vulnerablecode/blob/v36.1.3/vulnerabilities/api.py#L42-L44. - */ - @Serializable - data class Score( - /** The name of the scoring system. */ - val scoringSystem: String, - - /** The individual scoring elements, usually a CVSS vector. */ - val scoringElements: String? = null, - - /** - * The value in this scoring system. This is a string to support scoring systems that do not use numeric - * scores, but literals like _LOW_, _MEDIUM_, etc. - */ - val value: String - ) - - /** - * Data class representing a reference to detailed information about a vulnerability. Information about a single - * vulnerability can come from multiple sources; for each of these sources a reference is added to the data. - * - * See https://github.com/aboutcode-org/vulnerablecode/blob/v36.1.3/vulnerabilities/api.py#L58-L60. - */ - @Serializable - data class VulnerabilityReference( - /** - * The URL of this reference. From this URL, it is also possible to identify the source of information. - */ - val url: String, - - /** - * A (possibly empty) list with [Score] objects that determine the severity this source assigns to this - * vulnerability. - */ - val scores: List - ) - - /** - * Data class representing a single vulnerability with its references to detailed information. - * - * See https://github.com/aboutcode-org/vulnerablecode/blob/v36.1.3/vulnerabilities/api.py#L176-L188. - */ - @Serializable - data class Vulnerability( - /** The VulnerableCode-specific identifier for this vulnerability. */ - val vulnerabilityId: String, - - /** A description of the vulnerability. Older versions of VulnerableCode do not have this field. */ - @SerialName("summary") - val description: String? = null, - - /** A list with [VulnerabilityReference]s pointing to sources of information about this vulnerability. */ - val references: List, - - /** - * A list with strings representing alias identifiers for this vulnerability as they are used by other - * databases. VulnerableCode here returns plain strings without further context information; therefore, it is - * currently only possible to determine the source of a specific identifier from its structure, e.g. if it has - * a well-known prefix like CVE or GHSA. - */ - val aliases: List = emptyList() - ) - - /** - * Data class describing a package in the result of a package query together with the vulnerabilities known for - * this package. - * - * See https://github.com/aboutcode-org/vulnerablecode/blob/v36.1.3/vulnerabilities/api.py#L396-L413. - */ - @Serializable - data class PackageVulnerabilities( - /** The purl identifying this package. */ - val purl: String, - - /** An optional list with vulnerabilities that have not yet been resolved. */ - @JsonNames("unresolved_vulnerabilities") - val affectedByVulnerabilities: List = emptyList(), - - /** An optional list with vulnerabilities that have already been resolved. */ - @JsonNames("resolved_vulnerabilities") - val fixingVulnerabilities: List = emptyList() - ) - - /** - * Data class to represent the bulk request for packages by their IDs. Using this request, the vulnerabilities - * known for a set of packages can be retrieved. The request body is a JSON object with a property containing a - * list of package identifiers. - */ - @Serializable - data class PackagesWrapper( - val purls: Collection - ) - - /** - * Retrieve information about the vulnerabilities assigned to the given [packages][packageUrls]. - * Return a list with information about packages including the resolved and unresolved vulnerabilities for these - * packages. - */ - @POST("packages/bulk_search") - suspend fun getPackageVulnerabilities(@Body packageUrls: PackagesWrapper): List -} diff --git a/plugins/advisors/vulnerable-code/build.gradle.kts b/plugins/advisors/vulnerable-code/build.gradle.kts index 1bdc098301cb6..53a388130cad2 100644 --- a/plugins/advisors/vulnerable-code/build.gradle.kts +++ b/plugins/advisors/vulnerable-code/build.gradle.kts @@ -23,9 +23,12 @@ plugins { } dependencies { + api(projects.clients.vulnerableCodeClient) api(projects.plugins.advisors.advisorApi) - implementation(projects.clients.vulnerableCodeClient) + implementation(ktorLibs.client.contentNegotiation) + implementation(ktorLibs.client.okhttp) + implementation(ktorLibs.serialization.kotlinx.json) implementation(projects.utils.commonUtils) implementation(projects.utils.ortUtils) diff --git a/plugins/advisors/vulnerable-code/src/funTest/kotlin/VulnerableCodeFunTest.kt b/plugins/advisors/vulnerable-code/src/funTest/kotlin/VulnerableCodeFunTest.kt index c98a8c62619e0..d413fee9a1b64 100644 --- a/plugins/advisors/vulnerable-code/src/funTest/kotlin/VulnerableCodeFunTest.kt +++ b/plugins/advisors/vulnerable-code/src/funTest/kotlin/VulnerableCodeFunTest.kt @@ -22,13 +22,19 @@ package org.ossreviewtoolkit.plugins.advisors.vulnerablecode import io.kotest.core.spec.style.WordSpec import io.kotest.matchers.collections.beEmpty import io.kotest.matchers.collections.containAll +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.beNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.should import io.kotest.matchers.shouldBe +import java.net.URI + import org.ossreviewtoolkit.model.Identifier import org.ossreviewtoolkit.model.Package import org.ossreviewtoolkit.model.utils.toPurl +import org.ossreviewtoolkit.model.vulnerabilities.VulnerabilityReference import org.ossreviewtoolkit.plugins.advisors.api.normalizeVulnerabilityData import org.ossreviewtoolkit.plugins.api.Secret @@ -36,7 +42,8 @@ class VulnerableCodeFunTest : WordSpec({ val vc = VulnerableCodeFactory.create(apiKey = System.getenv("VULNERABLECODE_API_KEY")?.let { Secret(it) }) "Vulnerable Go packages" should { - "return findings for QUIC" { + // VulnerableCode v3 API doesn't currently have results for this package. + "return findings for QUIC".config(enabled = false) { val id = Identifier("Go::github.com/quic-go/quic-go:0.40.0") val pkg = Package.EMPTY.copy(id = id, purl = id.toPurl()) @@ -64,9 +71,7 @@ class VulnerableCodeFunTest : WordSpec({ } "Vulnerable Maven packages" should { - // TODO: The test consistently fails with "unexpected end of stream". - // This should be investigated and the test be re-enabled again. - "return findings for Guava".config(enabled = false) { + "return findings for Guava" { val id = Identifier("Maven:com.google.guava:guava:19.0") val pkg = Package.EMPTY.copy(id = id, purl = id.toPurl()) @@ -81,16 +86,46 @@ class VulnerableCodeFunTest : WordSpec({ ) val vulnerability = getValue("CVE-2023-2976") - vulnerability.summary shouldBe "Use of Java's default temporary directory for file creation in `..." + vulnerability.summary shouldBe "Guava vulnerable to insecure use of temporary directory\nUse of J..." vulnerability.references.find { it.url.toString() == "https://nvd.nist.gov/vuln/detail/CVE-2023-2976" } shouldNotBeNull { - scoringSystem shouldBe "cvssv3" - severity shouldBe "HIGH" - score shouldBe 7.1f - vector shouldBe "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N" + scoringSystem should beNull() + severity should beNull() + score should beNull() + vector should beNull() } + + val refs = vulnerability.references.filter { + it.url.toString() == "https://github.com/github/advisory-database/blob/main/advisories/github-" + + "reviewed/2023/06/GHSA-7g45-4rm6-3mm3/GHSA-7g45-4rm6-3mm3.json" + } + + refs shouldHaveSize 2 + + refs shouldContainExactlyInAnyOrder listOf( + VulnerabilityReference( + URI( + "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/" + + "2023/06/GHSA-7g45-4rm6-3mm3/GHSA-7g45-4rm6-3mm3.json" + ), + scoringSystem = "cvssv3.1", + severity = "MEDIUM", + score = 5.5f, + vector = "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N" + ), + VulnerabilityReference( + URI( + "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/" + + "2023/06/GHSA-7g45-4rm6-3mm3/GHSA-7g45-4rm6-3mm3.json" + ), + scoringSystem = "generic_textual", + severity = "MEDIUM", + score = null, + vector = null + ) + ) } } @@ -103,20 +138,52 @@ class VulnerableCodeFunTest : WordSpec({ results.flatMap { it.summary.issues } should beEmpty() with(results.flatMap { it.vulnerabilities }.associateBy { it.id }) { keys should containAll( + "CVE-2024-26308", + "CVE-2024-25710", "CVE-2023-42503" ) val vulnerability = getValue("CVE-2023-42503") - vulnerability.summary shouldBe "Improper Input Validation, Uncontrolled Resource Consumption vul..." + vulnerability.summary shouldBe "Apache Commons Compress denial of service vulnerability\nImproper..." vulnerability.references.find { it.url.toString() == "https://nvd.nist.gov/vuln/detail/CVE-2023-42503" } shouldNotBeNull { - scoringSystem shouldBe "cvssv3" - severity shouldBe "MEDIUM" - score shouldBe 5.5f - vector shouldBe "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H" + scoringSystem should beNull() + severity should beNull() + score should beNull() + vector should beNull() + } + + val refs = vulnerability.references.filter { + it.url.toString() == "https://github.com/github/advisory-database/blob/main/advisories/github-" + + "reviewed/2023/09/GHSA-cgwf-w82q-5jrr/GHSA-cgwf-w82q-5jrr.json" } + + refs shouldHaveSize 2 + + refs shouldContainExactlyInAnyOrder listOf( + VulnerabilityReference( + URI( + "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/" + + "2023/09/GHSA-cgwf-w82q-5jrr/GHSA-cgwf-w82q-5jrr.json" + ), + scoringSystem = "cvssv3.1", + severity = "MEDIUM", + score = 5.5f, + vector = "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H" + ), + VulnerabilityReference( + URI( + "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/" + + "2023/09/GHSA-cgwf-w82q-5jrr/GHSA-cgwf-w82q-5jrr.json" + ), + scoringSystem = "generic_textual", + severity = "MEDIUM", + score = null, + vector = null + ) + ) } } } @@ -131,20 +198,62 @@ class VulnerableCodeFunTest : WordSpec({ results.flatMap { it.summary.issues } should beEmpty() with(results.flatMap { it.vulnerabilities }.associateBy { it.id }) { keys should containAll( - "CVE-2024-48948" + "CVE-2025-14505", + "CVE-2024-48948", + "GHSA-vjh7-7g9h-fjfh" ) val vulnerability = getValue("CVE-2024-48948") - vulnerability.summary shouldBe "The Elliptic package 6.5.7 for Node.js, in its for ECDSA impleme..." + vulnerability.summary shouldBe "Valid ECDSA signatures erroneously rejected in Elliptic\nThe Elli..." vulnerability.references.find { it.url.toString() == "https://github.com/indutny/elliptic" } shouldNotBeNull { - scoringSystem shouldBe "cvssv3.1" - severity shouldBe "MEDIUM" - score shouldBe 4.8f - vector shouldBe "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L" + scoringSystem should beNull() + severity should beNull() + score should beNull() + vector should beNull() } + + val refs = vulnerability.references.filter { + it.url.toString() == "https://github.com/github/advisory-database/blob/main/advisories/github-" + + "reviewed/2024/10/GHSA-fc9h-whq2-v747/GHSA-fc9h-whq2-v747.json" + } + + refs shouldHaveSize 3 + + refs shouldContainExactlyInAnyOrder listOf( + VulnerabilityReference( + URI( + "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/" + + "2024/10/GHSA-fc9h-whq2-v747/GHSA-fc9h-whq2-v747.json" + ), + scoringSystem = "cvssv3.1", + severity = "MEDIUM", + score = 4.8f, + vector = "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:L" + ), + VulnerabilityReference( + URI( + "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/" + + "2024/10/GHSA-fc9h-whq2-v747/GHSA-fc9h-whq2-v747.json" + ), + scoringSystem = "cvssv4", + severity = "LOW", + score = 2.3f, + vector = "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N" + ), + VulnerabilityReference( + URI( + "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/" + + "2024/10/GHSA-fc9h-whq2-v747/GHSA-fc9h-whq2-v747.json" + ), + scoringSystem = "generic_textual", + severity = "LOW", + score = null, + vector = null + ) + ) } } } diff --git a/plugins/advisors/vulnerable-code/src/main/kotlin/VulnerableCode.kt b/plugins/advisors/vulnerable-code/src/main/kotlin/VulnerableCode.kt index 191fe2b1ca25f..cd2b13f7cea4a 100644 --- a/plugins/advisors/vulnerable-code/src/main/kotlin/VulnerableCode.kt +++ b/plugins/advisors/vulnerable-code/src/main/kotlin/VulnerableCode.kt @@ -19,19 +19,40 @@ package org.ossreviewtoolkit.plugins.advisors.vulnerablecode +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.DefaultRequest +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.parameter +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.serialization.kotlinx.json.json + import java.net.URI import java.time.Instant import java.util.concurrent.TimeUnit +import kotlin.collections.orEmpty import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.ensureActive +import kotlinx.serialization.json.Json import org.apache.logging.log4j.kotlin.logger -import org.ossreviewtoolkit.clients.vulnerablecode.VulnerableCodeService -import org.ossreviewtoolkit.clients.vulnerablecode.VulnerableCodeService.PackagesWrapper +import org.ossreviewtoolkit.clients.vulnerablecode.AdvisoryReference +import org.ossreviewtoolkit.clients.vulnerablecode.AdvisorySeverity +import org.ossreviewtoolkit.clients.vulnerablecode.AffectedByAdvisoryV3 +import org.ossreviewtoolkit.clients.vulnerablecode.PackageQuery +import org.ossreviewtoolkit.clients.vulnerablecode.PaginatedPurlList +import org.ossreviewtoolkit.clients.vulnerablecode.client.v3AffectedByAdvisoriesList +import org.ossreviewtoolkit.clients.vulnerablecode.client.v3PackagesCreate import org.ossreviewtoolkit.model.AdvisorDetails import org.ossreviewtoolkit.model.AdvisorResult import org.ossreviewtoolkit.model.AdvisorSummary @@ -50,10 +71,12 @@ import org.ossreviewtoolkit.utils.common.percentEncode import org.ossreviewtoolkit.utils.ort.OkHttpClientHelper /** - * The number of elements to request at once in a bulk request. This value was chosen more or less randomly to keep the - * size of responses reasonably small. + * The number of elements to request at once in a bulk request for the v3/packages endpoint. The request uses + * "details=false", so the response only contains a list of matching PURLs that are affected by/fixing vulnerabilities. + * A relatively large chunk size reduces the number of top-level bulk requests while still keeping individual request + * bodies reasonably small. */ -private const val BULK_REQUEST_SIZE = 100 +private const val BULK_REQUEST_SIZE = 1000 /** * The maximum length for the summary as derived from the description of a vulnerability. @@ -79,12 +102,29 @@ class VulnerableCode( */ override val details = AdvisorDetails(descriptor.id) - private val service by lazy { - val client = OkHttpClientHelper.buildClient { - if (config.readTimeout != null) readTimeout(config.readTimeout, TimeUnit.SECONDS) - } + private val client by lazy { + HttpClient(OkHttp) { + expectSuccess = true - VulnerableCodeService.create(config.serverUrl, config.apiKey?.value, client) + engine { + preconfigured = OkHttpClientHelper.buildClient { + if (config.readTimeout != null) readTimeout(config.readTimeout, TimeUnit.SECONDS) + } + } + + install(DefaultRequest) { + url(config.serverUrl) + header(HttpHeaders.ContentType, ContentType.Application.Json) + + config.apiKey?.value?.also { + header(HttpHeaders.Authorization, "Token $it") + } + } + + install(ContentNegotiation) { + json(Json { ignoreUnknownKeys = true }) + } + } } override suspend fun retrievePackageFindings(packages: Set): Map { @@ -93,16 +133,28 @@ class VulnerableCode( val purls = packages.mapNotNull { pkg -> pkg.purl.ifEmpty { null } } val chunks = purls.chunked(BULK_REQUEST_SIZE) - val allVulnerabilities = mutableMapOf>() + val allVulnerabilities = mutableMapOf>() val issues = mutableListOf() chunks.forEachIndexed { index, chunk -> runCatching { - val chunkVulnerabilities = service.getPackageVulnerabilities(PackagesWrapper(chunk)).filter { - it.affectedByVulnerabilities.isNotEmpty() + val request = PackageQuery(chunk, details = false) + var page = client.v3PackagesCreate(request) + val queriedPurls = page.results.toMutableSet() + + while (true) { + val nextUrl = page.next ?: break + + page = client.post(nextUrl) { + setBody(request) + }.body() + + queriedPurls += page.results } - allVulnerabilities += chunkVulnerabilities.associate { it.purl to it.affectedByVulnerabilities } + val chunkVulnerabilities = client.getAffectedByAdvisories(queriedPurls.filter { it in chunk }) + + allVulnerabilities += chunkVulnerabilities }.onFailure { if (it is CancellationException) currentCoroutineContext().ensureActive() @@ -125,7 +177,7 @@ class VulnerableCode( return packages.mapNotNullTo(mutableListOf()) { pkg -> allVulnerabilities[pkg.purl]?.let { packageVulnerabilities -> - val vulnerabilities = packageVulnerabilities.map { it.toModel(issues) } + val vulnerabilities = packageVulnerabilities.map { it.toModel(issues) }.mergeVulnerabilities() val summary = AdvisorSummary(startTime, endTime, issues) pkg to AdvisorResult(details, summary, vulnerabilities = vulnerabilities) } @@ -133,64 +185,127 @@ class VulnerableCode( } /** - * Convert this vulnerability from the VulnerableCode data model to a [Vulnerability]. Populate [issues] if this + * Retrieve all advisories affecting the given [purls]. Filter out packages that are not affected by any advisory. + */ + private suspend fun HttpClient.getAffectedByAdvisories( + purls: Collection + ): Map> = + purls.associateWith { purl -> getAllAffectedByAdvisories(purl) } + .filterValues { advisories -> advisories.isNotEmpty() } + + /** + * Retrieve all advisories affecting the given [purl]. + */ + private suspend fun HttpClient.getAllAffectedByAdvisories(purl: String): List { + var page = v3AffectedByAdvisoriesList { + parameter("purl", purl) + } + + val advisories = page.results.toMutableList() + + while (true) { + val nextUrl = page.next ?: break + + page = get(nextUrl).body() + advisories += page.results + } + + return advisories + } + + /** + * Convert this advisory from the VulnerableCode data model to a [Vulnerability]. Populate [issues] if this * fails. */ - private fun VulnerableCodeService.Vulnerability.toModel(issues: MutableList): Vulnerability { - val description = description?.ifBlank { null } + private fun AffectedByAdvisoryV3.toModel(issues: MutableList): Vulnerability { + val normalizedSummary = summary?.ifBlank { null } + return Vulnerability( id = preferredCommonId(), - // VulnerableCode API v1 has no dedicated summary field (its summary actually is the description), so try to - // summarize the description. - summary = description?.take(MAX_SUMMARY_LENGTH)?.let { - if (it.length < description.length) "$it..." else it + // The VulnerableCode API v3 summary is actually a more detailed description of the vulnerability, so use it + // as description and derive a shorter summary from it. + summary = normalizedSummary?.take(MAX_SUMMARY_LENGTH)?.let { + if (it.length < normalizedSummary.length) "$it..." else it }, - description = description, - references = references.flatMap { it.toModel(issues) } + description = normalizedSummary, + references = toReferences(issues) ) } /** - * Convert this reference from the VulnerableCode data model to a list of [VulnerabilityReference] objects. - * In the VulnerableCode model, the reference can be assigned multiple scores in different scoring systems. - * For each of these scores, a single [VulnerabilityReference] is created. If no score is available, return a - * list with a single [VulnerabilityReference] with limited data. Populate [issues] in case of a failure, - * e.g. if the conversion to a URI fails. + * Convert this advisory from the VulnerableCode data model to a list of [VulnerabilityReference] objects. The + * advisory contains two fields that contain the relevant information, references and severities, which are both + * converted to [VulnerabilityReference] objects. If there are no entries in either of these fields, a reference is + * created from the advisory's URL. Populate [issues] if this fails. */ - private fun VulnerableCodeService.VulnerabilityReference.toModel( - issues: MutableList - ): List = - runCatching { - val sourceUri = URI(url.fixupUrlEscaping()) + private fun AffectedByAdvisoryV3.toReferences(issues: MutableList): List { + val advisoryReferences = references.mapNotNull { it.toModel(issues) } + val scoredReferences = severities.mapNotNull { it.toModel(url, issues) } - if (scores.isEmpty()) return listOf(VulnerabilityReference(sourceUri, null, null, null, null)) - - return scores.map { - // In VulnerableCode's data model, a Score class's value is either a numeric score or a severity string. - val score = it.value.toFloatOrNull() - val severity = it.value.takeUnless { score != null } + return (advisoryReferences + scoredReferences).ifEmpty { + url.toUri(issues)?.let { listOf(VulnerabilityReference(it, null, null, null, null)) }.orEmpty() + } + } - val vector = it.scoringElements?.ifEmpty { null } + /** + * Convert this advisory reference from the VulnerableCode data model to a [VulnerabilityReference] object. + * Populate [issues] if this fails. + */ + private fun AdvisoryReference.toModel(issues: MutableList): VulnerabilityReference? = + url.toUri(issues)?.let { VulnerabilityReference(it, null, null, null, null) } - VulnerabilityReference(sourceUri, it.scoringSystem, severity, score, vector) - } - }.onFailure { - issues += createAndLogIssue("Failed to map $this to ORT model due to $it.", Severity.HINT) - }.getOrElse { emptyList() } + /** + * 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): VulnerabilityReference? { + val score = value?.toFloatOrNull() + val textualSeverity = value.takeUnless { score != null } + val vector = scoring_elements?.ifEmpty { null } + val sourceUrl = url?.takeUnless { it.isBlank() } ?: fallbackUrl + + return sourceUrl.toUri(issues)?.let { + VulnerabilityReference(it, scoring_system, textualSeverity, score, vector) + } + } /** - * Return a meaningful identifier for this vulnerability that can be used in reports. Obtain this identifier from - * the defined aliases if there are any. The data model of VulnerableCode supports multiple aliases while ORT's - * [Vulnerability] has just one identifier. To resolve this discrepancy, prefer CVEs over other identifiers. If - * there are no aliases referencing CVEs, use an arbitrary alias, assuming that every alias is preferable over - * the provider-specific ID of VulnerableCode. Only if no aliases are defined, use the latter as fallback. Note - * that it should still be possible via the references to find mentions of aliases that have been dropped. + * Return a meaningful identifier for this vulnerability that can be used in reports. Consider the defined aliases + * and the last path segment of the advisory ID as candidate identifiers, because the advisory ID often embeds a + * public identifier such as a GHSA or CVE in its final path segment. To resolve the discrepancy between + * VulnerableCode's multiple identifiers and ORT's single [Vulnerability] identifier, prefer a CVE if one is + * available. Otherwise, use the last path segment of the advisory ID. */ - private fun VulnerableCodeService.Vulnerability.preferredCommonId(): String { - if (aliases.isEmpty()) return vulnerabilityId + private fun AffectedByAdvisoryV3.preferredCommonId(): String { + val advisoryIdSegment = advisory_id.substringAfterLast('/') + val allIds = buildList { + addAll(aliases) + add(advisoryIdSegment) + } - return aliases.firstOrNull { it.startsWith("cve", ignoreCase = true) } ?: aliases.first() + return allIds.firstOrNull { it.startsWith("cve", ignoreCase = true) } + ?: advisoryIdSegment } + + /** + * Merge vulnerabilities with the same ID into a single vulnerability, combining their references. + */ + private fun Collection.mergeVulnerabilities(): List = + groupBy { it.id }.values.map { vulnerabilitiesWithSameId -> + val references = vulnerabilitiesWithSameId.flatMapTo(mutableSetOf()) { it.references } + val entry = vulnerabilitiesWithSameId.find { it.summary != null || it.description != null } + ?: vulnerabilitiesWithSameId.first() + + entry.copy(references = references.toList()) + } + + /** + * Convert this string to a [URI] object. Populate [issues] if this fails. + */ + private fun String.toUri(issues: MutableList): URI? = + runCatching { URI(fixupUrlEscaping()) }.onFailure { + issues += createAndLogIssue("Failed to map $this to ORT model due to $it.", Severity.HINT) + }.getOrNull() } private val BACKSLASH_ESCAPE_REGEX = """\\\\?(.)""".toRegex() diff --git a/plugins/advisors/vulnerable-code/src/main/kotlin/VulnerableCodeConfiguration.kt b/plugins/advisors/vulnerable-code/src/main/kotlin/VulnerableCodeConfiguration.kt index 6b62b0d8e1179..63ca4ca7c3a26 100644 --- a/plugins/advisors/vulnerable-code/src/main/kotlin/VulnerableCodeConfiguration.kt +++ b/plugins/advisors/vulnerable-code/src/main/kotlin/VulnerableCodeConfiguration.kt @@ -19,7 +19,7 @@ package org.ossreviewtoolkit.plugins.advisors.vulnerablecode -import org.ossreviewtoolkit.clients.vulnerablecode.VulnerableCodeService +import org.ossreviewtoolkit.clients.vulnerablecode.VULNERABLE_CODE_BASE_URL import org.ossreviewtoolkit.plugins.api.OrtPluginOption import org.ossreviewtoolkit.plugins.api.Secret @@ -30,7 +30,7 @@ data class VulnerableCodeConfiguration( /** * The base URL of the VulnerableCode REST API. By default, the public VulnerableCode instance is used. */ - @OrtPluginOption(defaultValue = VulnerableCodeService.PUBLIC_SERVER_URL) + @OrtPluginOption(defaultValue = VULNERABLE_CODE_BASE_URL) val serverUrl: String, /** diff --git a/plugins/advisors/vulnerable-code/src/test/kotlin/VulnerableCodeTest.kt b/plugins/advisors/vulnerable-code/src/test/kotlin/VulnerableCodeTest.kt index 5ff0909377fcf..bbff887afe8d6 100644 --- a/plugins/advisors/vulnerable-code/src/test/kotlin/VulnerableCodeTest.kt +++ b/plugins/advisors/vulnerable-code/src/test/kotlin/VulnerableCodeTest.kt @@ -23,6 +23,7 @@ import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.client.WireMock.aResponse import com.github.tomakehurst.wiremock.client.WireMock.equalTo import com.github.tomakehurst.wiremock.client.WireMock.equalToJson +import com.github.tomakehurst.wiremock.client.WireMock.get import com.github.tomakehurst.wiremock.client.WireMock.post import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo import com.github.tomakehurst.wiremock.core.WireMockConfiguration @@ -33,11 +34,15 @@ import io.kotest.matchers.collections.beEmpty import io.kotest.matchers.collections.containExactly import io.kotest.matchers.collections.containExactlyInAnyOrder import io.kotest.matchers.collections.shouldBeSingleton +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.maps.beEmpty as beEmptyMap import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNot +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.string.shouldStartWith import java.net.URI @@ -72,12 +77,16 @@ class VulnerableCodeTest : WordSpec({ "VulnerableCode" should { "return vulnerability information" { - server.stubPackagesRequest("response_packages.json") + server.stubPackagesRequest("packages_response_packages.json") + server.stubAffectedByAdvisoriesRequest(idLang, "affected_by_advisories_response_lang.json") + server.stubAffectedByAdvisoriesRequest(idStruts, "affected_by_advisories_response_struts.json") + val vulnerableCode = createVulnerableCode(server) val packagesToAdvise = inputPackagesFromAnalyzerResult() val result = vulnerableCode.retrievePackageFindings(packagesToAdvise).mapKeys { it.key.id } + result.values.flatMap { it.summary.issues } should beEmpty() result shouldNot beEmptyMap() result.keys should containExactlyInAnyOrder(idLang, idStruts) @@ -153,7 +162,9 @@ class VulnerableCodeTest : WordSpec({ } "extract the CVE ID from an alias" { - 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") + val vulnerableCode = createVulnerableCode(server) val packagesToAdvise = inputPackagesFromIdentifiers(idJUnit) @@ -182,8 +193,9 @@ class VulnerableCodeTest : WordSpec({ containExactly(expJunitVulnerability) } - "extract other official identifiers from aliases" { - server.stubPackagesRequest("response_log4j.json", generatePackagesRequest(idLog4j)) + "extract other official identifiers from the advisory ID" { + server.stubPackagesRequest("packages_response_log4j.json", generatePackagesRequest(idLog4j)) + server.stubAffectedByAdvisoriesRequest(idLog4j, "affected_by_advisories_response_log4j_no_aliases.json") val vulnerableCode = createVulnerableCode(server) val packagesToAdvise = inputPackagesFromIdentifiers(idLog4j) @@ -191,24 +203,41 @@ class VulnerableCodeTest : WordSpec({ val expLog4jVulnerabilities = listOf( Vulnerability( - id = "GHSA-jfh8-c2jp-5v3q", - summary = "Remote code injection in Log4j", - description = "Remote code injection in Log4j", + id = "GHSA-8489-44mv-ggj8", references = listOf( VulnerabilityReference( - URI("http://ref.com/files/165225/Apache-Log4j2-2.14.1-Remote-Code-Execution.html"), + URI("https://github.com/advisories/GHSA-8489-44mv-ggj8.json"), scoringSystem = null, severity = null, score = null, vector = null ) ) - ), + ) + ) + result.getValue(idLog4j).vulnerabilities.normalizeVulnerabilityData() should + containExactlyInAnyOrder(expLog4jVulnerabilities) + } + + "prefer CVE ID from the advisory ID over aliases" { + server.stubPackagesRequest("packages_response_log4j.json", generatePackagesRequest(idLog4j)) + server.stubAffectedByAdvisoriesRequest(idLog4j, "affected_by_advisories_response_log4j.json") + val vulnerableCode = createVulnerableCode(server) + val packagesToAdvise = inputPackagesFromIdentifiers(idLog4j) + + val result = vulnerableCode.retrievePackageFindings(packagesToAdvise).mapKeys { it.key.id } + + val expLog4jVulnerabilities = listOf( Vulnerability( id = "CVE-2021-44832", - summary = "Improper Input Validation and Injection in Apache Log4j2", - description = "Improper Input Validation and Injection in Apache Log4j2", references = listOf( + VulnerabilityReference( + URI("https://github.com/advisories/GHSA-8489-44mv-ggj8.json"), + scoringSystem = null, + severity = null, + score = null, + vector = null + ), VulnerabilityReference( URI("https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2021-44832.json"), scoringSystem = "cvssv3", @@ -223,9 +252,156 @@ class VulnerableCodeTest : WordSpec({ containExactlyInAnyOrder(expLog4jVulnerabilities) } + "gather vulnerability references from advisory severities and references" { + server.stubPackagesRequest("packages_response_log4j.json", generatePackagesRequest(idLog4j)) + server.stubAffectedByAdvisoriesRequest( + idLog4j, + "affected_by_advisories_response_severities_references.json" + ) + val vulnerableCode = createVulnerableCode(server) + val packagesToAdvise = inputPackagesFromIdentifiers(idLog4j) + + val result = vulnerableCode.retrievePackageFindings(packagesToAdvise).mapKeys { it.key.id } + + val expLog4jVulnerabilities = listOf( + Vulnerability( + id = "CVE-2026-34480", + summary = "Apache Log4j Core: Silent log event loss in XmlLayout due to une...", + description = "Apache Log4j Core: Silent log event loss in XmlLayout due to unescaped XML 1.0 " + + "forbidden characters", + references = listOf( + VulnerabilityReference( + URI("https://github.com/apache/logging-log4j2/pull/4077"), + scoringSystem = null, + severity = null, + score = null, + vector = null + ), + VulnerabilityReference( + URI("https://github.com/advisories/GHSA-3pxv-7cmr-fjr4/GHSA-3pxv-7cmr-fjr4.json"), + scoringSystem = "cvssv4", + severity = "MEDIUM", + score = 6.9f, + vector = "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N" + ), + VulnerabilityReference( + URI("https://github.com/advisories/GHSA-3pxv-7cmr-fjr4/GHSA-3pxv-7cmr-fjr4.json"), + scoringSystem = "generic_textual", + severity = "MEDIUM", + score = null, + vector = null + ) + ) + ) + ) + result.getValue(idLog4j).vulnerabilities.normalizeVulnerabilityData() should + containExactlyInAnyOrder(expLog4jVulnerabilities) + } + + "combine and deduplicate results from multiple advisories for the same vulnerability" { + server.stubPackagesRequest("packages_response_log4j.json", generatePackagesRequest(idLog4j)) + server.stubAffectedByAdvisoriesRequest(idLog4j, "affected_by_advisories_response_multiple_advisories.json") + val vulnerableCode = createVulnerableCode(server) + val packagesToAdvise = inputPackagesFromIdentifiers(idLog4j) + + val result = vulnerableCode.retrievePackageFindings(packagesToAdvise).mapKeys { it.key.id } + + val expLog4jVulnerabilities = listOf( + Vulnerability( + id = "CVE-2026-34480", + summary = "Apache Log4j Core: Silent log event loss in XmlLayout due to une...", + description = "Apache Log4j Core: Silent log event loss in XmlLayout due to unescaped XML 1.0 " + + "forbidden characters", + references = listOf( + VulnerabilityReference( + URI("https://logging.apache.org/cyclonedx/vdr.xml"), + scoringSystem = null, + severity = null, + score = null, + vector = null + ), + VulnerabilityReference( + URI("https://github.com/advisories/GHSA-3pxv-7cmr-fjr4.json"), + scoringSystem = "cvssv4", + severity = "MEDIUM", + score = 6.9f, + vector = "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N" + ), + VulnerabilityReference( + URI("https://gitlab.com/advisories-community/CVE-2026-34480.yml"), + scoringSystem = "cvssv3.1", + severity = "None", + score = 5.3f, + vector = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N" + ) + ) + ) + ) + + result.getValue(idLog4j).vulnerabilities.normalizeVulnerabilityData() should + containExactlyInAnyOrder(expLog4jVulnerabilities) + } + + "gather purls from paginated response" { + server.stubPackagesRequest("packages_response_page1.json") + server.stubFor( + post(urlPathEqualTo("/api/v3/packages/")) + .withQueryParam("page", equalTo("2")) + .withRequestBody(equalToJson(packagesRequestJson, true, false)) + .willReturn( + aResponse().withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("packages_response_page2.json") + ) + ) + + server.stubAffectedByAdvisoriesRequest(idJUnit, "affected_by_advisories_response_junit.json") + server.stubAffectedByAdvisoriesRequest(idLang, "affected_by_advisories_response_lang.json") + server.stubAffectedByAdvisoriesRequest(idStruts, "affected_by_advisories_response_struts.json") + + val vulnerableCode = createVulnerableCode(server) + val packagesToAdvise = inputPackagesFromAnalyzerResult() + + val result = vulnerableCode.retrievePackageFindings(packagesToAdvise).mapKeys { it.key.id } + + result.values.flatMap { it.summary.issues } should beEmpty() + result shouldNot beEmptyMap() + result.keys should containExactlyInAnyOrder(idJUnit, idLang, idStruts) + } + + "gather vulnerabilities from paginated advisories" { + server.stubPackagesRequest("packages_response_log4j.json", generatePackagesRequest(idLog4j)) + server.stubAffectedByAdvisoriesRequest(idLog4j, "affected_by_advisories_response_page1.json") + server.stubFor( + get(urlPathEqualTo("/api/v3/affected-by-advisories/")) + .withQueryParam("purl", equalTo(idLog4j.toPurl())) + .withQueryParam("page", equalTo("2")) + .willReturn( + aResponse().withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile("affected_by_advisories_response_page2.json") + ) + ) + + val vulnerableCode = createVulnerableCode(server) + val packagesToAdvise = inputPackagesFromIdentifiers(idLog4j) + + val result = vulnerableCode.retrievePackageFindings(packagesToAdvise).mapKeys { it.key.id } + + result.keys shouldBe setOf(idLog4j) + result.getValue(idLog4j).vulnerabilities shouldHaveSize 3 + + with(result.getValue(idLog4j)) { + vulnerabilities shouldHaveSize 3 + vulnerabilities.map { + it.id + } shouldContainExactlyInAnyOrder setOf("CVE-2026-34480", "CVE-2026-34477", "CVE-2021-44832") + } + } + "handle a failure response from the server" { server.stubFor( - post(urlPathEqualTo("/packages/bulk_search")) + post(urlPathEqualTo("/api/v3/packages/")) .willReturn( aResponse().withStatus(500) ) @@ -244,7 +420,8 @@ class VulnerableCodeTest : WordSpec({ vulnerabilities should beEmpty() summary.issues.shouldBeSingleton { issue -> issue.severity shouldBe Severity.ERROR - issue.message shouldBe "HttpException: HTTP 500 Server Error" + issue.message shouldStartWith "ServerResponseException" + issue.message shouldContain "500 Server Error" } } } @@ -252,7 +429,9 @@ class VulnerableCodeTest : WordSpec({ } "filter out packages without vulnerabilities" { - server.stubPackagesRequest("response_packages_no_vulnerabilities.json") + server.stubPackagesRequest("packages_response_packages.json") + server.stubAffectedByAdvisoriesRequest(idLang, "affected_by_advisories_response_lang_no_results.json") + server.stubAffectedByAdvisoriesRequest(idStruts, "affected_by_advisories_response_struts.json") val vulnerableCode = createVulnerableCode(server) val packagesToAdvise = inputPackagesFromAnalyzerResult() @@ -262,7 +441,9 @@ class VulnerableCodeTest : WordSpec({ } "handle unexpected packages in the query result" { - server.stubPackagesRequest("response_unexpected_packages.json") + server.stubPackagesRequest("packages_response_unexpected_packages.json") + server.stubAffectedByAdvisoriesRequest(idLang, "affected_by_advisories_response_lang.json") + server.stubAffectedByAdvisoriesRequest(idStruts, "affected_by_advisories_response_struts.json") val vulnerableCode = createVulnerableCode(server) val packagesToAdvise = inputPackagesFromAnalyzerResult() @@ -335,13 +516,26 @@ private val packagesRequestJson = generatePackagesRequest() */ private fun WireMockServer.stubPackagesRequest(responseFile: String, request: String = packagesRequestJson) { stubFor( - post(urlPathEqualTo("/packages/bulk_search")) + post(urlPathEqualTo("/api/v3/packages/")) .withRequestBody( equalToJson(request, /* ignoreArrayOrder = */ true, /* ignoreExtraElements = */ false) ) - .withHeader("Content-Type", equalTo("application/json; charset=UTF-8")) + .withHeader("Content-Type", equalTo("application/json")) + .willReturn( + aResponse().withStatus(200) + .withHeader("Content-Type", "application/json") + .withBodyFile(responseFile) + ) + ) +} + +private fun WireMockServer.stubAffectedByAdvisoriesRequest(id: Identifier, responseFile: String) { + stubFor( + get(urlPathEqualTo("/api/v3/affected-by-advisories/")) + .withQueryParam("purl", equalTo(id.toPurl())) .willReturn( aResponse().withStatus(200) + .withHeader("Content-Type", "application/json") .withBodyFile(responseFile) ) ) @@ -378,7 +572,7 @@ private fun inputPackagesFromIdentifiers(vararg identifiers: Identifier): Set = packages): String = - purls.joinToString(prefix = "{ \"purls\": [", postfix = "] }") { "\"$it\"" } + purls.joinToString(prefix = "{ \"purls\": [", postfix = "],\"details\":false }") { "\"$it\"" } /** * Generate the JSON body of the request to query vulnerability information about the [Package] with the given [id]. diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_junit.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_junit.json new file mode 100644 index 0000000000000..afe2f5decacc4 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_junit.json @@ -0,0 +1,34 @@ +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "advisory_id": "github_osv_importer_v2/GHSA-269g-pwp5-87pp", + "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2020/10/GHSA-269g-pwp5-87pp/GHSA-269g-pwp5-87pp.json", + "aliases": [ + "CVE-2020-15250", + "GHSA-269g-pwp5-87pp" + ], + "summary": null, + "severities": [ + { + "url": "http://people.canonical.com/~ubuntu-security/cve/2020/CVE-2020-15250.html", + "value": "Medium", + "scoring_system": "generic_textual", + "scoring_elements": "" + }, + { + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2020-15250.json", + "value": "4.0", + "scoring_system": "cvssv3", + "scoring_elements": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N" + } + ], + "weaknesses": [], + "references": [], + "related_ssvc_trees": [], + "fixed_by_packages": [] + } + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_lang.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_lang.json new file mode 100644 index 0000000000000..04604b410b4e5 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_lang.json @@ -0,0 +1,57 @@ + +{ + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "advisory_id": "github_osv_importer_v2/GHSA-2cxf-6567-7pp6", + "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2021/03/GHSA-2cxf-6567-7pp6/GHSA-2cxf-6567-7pp6.json", + "aliases": [ + "CVE-2014-8242" + ], + "severities": [ + { + "url": "https://github.com/apache/commons-lang/security/advisories/GHSA-2cxf-6567-7pp6", + "value": "LOW", + "scoring_system": "cvssv3.1_qr", + "scoring_elements": null + } + ], + "weaknesses": [], + "references": [ + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-8242", + "reference_type": "", + "reference_id": "CVE-2014-8242" + } + ], + "related_ssvc_trees": [], + "fixed_by_packages": [] + }, + { + "advisory_id": "gitlab_importer_v2/maven/org.apache.commons/commons-lang3/CVE-2014-8242", + "url": "https://gitlab.com/gitlab-org/advisories-community/-/blob/main/maven/org.apache.commons/commons-lang3/CVE-2014-8242.yml", + "aliases": [ + "CVE-2014-8242", + "GHSA-2cxf-6567-7pp6" + ], + "severities": [], + "weaknesses": [], + "references": [ + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-8242", + "reference_type": "", + "reference_id": "CVE-2014-8242" + }, + { + "url": "https://github.com/advisories/GHSA-2cxf-6567-7pp6", + "reference_type": "", + "reference_id": "GHSA-2cxf-6567-7pp6" + } + ], + "related_ssvc_trees": [], + "fixed_by_packages": [] + } + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_lang_no_results.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_lang_no_results.json new file mode 100644 index 0000000000000..1f150cb9d70ea --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_lang_no_results.json @@ -0,0 +1,7 @@ + +{ + "count": 0, + "next": null, + "previous": null, + "results": [] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_log4j.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_log4j.json new file mode 100644 index 0000000000000..bcd8baf3421e9 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_log4j.json @@ -0,0 +1,33 @@ +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "advisory_id": "project-kb-statements_v2/CVE-2021-44832", + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2021-44832.json", + "aliases": [ + "GHSA-jfh8-c2jp-5v3q" + ], + "summary": null, + "severities": [ + { + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2021-44832.json", + "value": "6.6", + "scoring_system": "cvssv3", + "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H" + } + ], + "weaknesses": [], + "references": [ + { + "url": "https://github.com/advisories/GHSA-8489-44mv-ggj8.json", + "reference_type": "other", + "reference_id": "4077" + } + ], + "related_ssvc_trees": [], + "fixed_by_packages": [] + } + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_log4j_no_aliases.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_log4j_no_aliases.json new file mode 100644 index 0000000000000..fb827c3699246 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_log4j_no_aliases.json @@ -0,0 +1,18 @@ +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "advisory_id": "github_osv_importer_v2/GHSA-8489-44mv-ggj8", + "url": "https://github.com/advisories/GHSA-8489-44mv-ggj8.json", + "aliases": [], + "summary": null, + "severities": [], + "weaknesses": [], + "references": [], + "related_ssvc_trees": [], + "fixed_by_packages": [] + } + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_multiple_advisories.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_multiple_advisories.json new file mode 100644 index 0000000000000..2624d9edfde01 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_multiple_advisories.json @@ -0,0 +1,60 @@ +{ + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "advisory_id": "github_osv_importer_v2/GHSA-3pxv-7cmr-fjr4", + "url": "https://github.com/advisories/GHSA-3pxv-7cmr-fjr4.json", + "aliases": [ + "CVE-2026-34480" + ], + "summary": "Apache Log4j Core: Silent log event loss in XmlLayout due to unescaped XML 1.0 forbidden characters", + "severities": [ + { + "url": "https://github.com/advisories/GHSA-3pxv-7cmr-fjr4.json", + "value": "6.9", + "scoring_system": "cvssv4", + "scoring_elements": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N" + } + ], + "weaknesses": [], + "references": [ + { + "url": "https://logging.apache.org/cyclonedx/vdr.xml", + "reference_type": "", + "reference_id": "" + } + ], + "related_ssvc_trees": [], + "fixed_by_packages": [] + }, + { + "advisory_id": "gitlab_importer_v2/maven/org.apache.logging.log4j/log4j-core/CVE-2026-34480", + "url": "https://gitlab.com/advisories-community/CVE-2026-34480.yml", + "aliases": [ + "CVE-2026-34480", + "GHSA-3pxv-7cmr-fjr4" + ], + "summary": "Apache Log4j Core: Silent log event loss in XmlLayout due to unescaped XML 1.0 forbidden characters", + "severities": [ + { + "url": "https://gitlab.com/advisories-community/CVE-2026-34480.yml", + "value": "None", + "scoring_system": "cvssv3.1", + "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N" + } + ], + "weaknesses": [], + "references": [ + { + "url": "https://logging.apache.org/cyclonedx/vdr.xml", + "reference_type": "", + "reference_id": "" + } + ], + "related_ssvc_trees": [], + "fixed_by_packages": [] + } + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_page1.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_page1.json new file mode 100644 index 0000000000000..d71e504279288 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_page1.json @@ -0,0 +1,136 @@ +{ + "count": 3, + "next": "/api/v3/affected-by-advisories/?purl=pkg:maven%2Forg.apache.logging.log4j%2Flog4j-core%402.17.0&page=2", + "previous": null, + "results": [ + { + "advisory_id": "github_osv_importer_v2/GHSA-3pxv-7cmr-fjr4", + "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-3pxv-7cmr-fjr4/GHSA-3pxv-7cmr-fjr4.json", + "aliases": [ + "CVE-2026-34480" + ], + "summary": "Apache Log4j Core: Silent log event loss in XmlLayout due to unescaped XML 1.0 forbidden characters", + "severities": [ + { + "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-3pxv-7cmr-fjr4/GHSA-3pxv-7cmr-fjr4.json", + "value": "6.9", + "scoring_system": "cvssv4", + "scoring_elements": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N" + }, + { + "url": null, + "value": "MODERATE", + "scoring_system": "generic_textual", + "scoring_elements": "" + } + ], + "weaknesses": [], + "references": [ + { + "url": "https://github.com/apache/logging-log4j2", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://logging.apache.org/cyclonedx/vdr.xml", + "reference_type": "", + "reference_id": "" + }, + { + "url": "http://www.openwall.com/lists/oss-security/2026/04/10/9", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://github.com/apache/logging-log4j2/pull/4077", + "reference_type": "other", + "reference_id": "4077" + }, + { + "url": "https://lists.apache.org/thread/5x0hcnng0chhghp6jgjdp3qmbbhfjzhb", + "reference_type": "advisory", + "reference_id": "5x0hcnng0chhghp6jgjdp3qmbbhfjzhb" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34480", + "reference_type": "", + "reference_id": "CVE-2026-34480" + }, + { + "url": "https://logging.apache.org/log4j/2.x/manual/layouts.html#XmlLayout", + "reference_type": "other", + "reference_id": "layouts.html#XmlLayout" + }, + { + "url": "https://logging.apache.org/security.html#CVE-2026-34480", + "reference_type": "advisory", + "reference_id": "security.html#CVE-2026-34480" + } + ], + "related_ssvc_trees": [], + "fixed_by_packages": [] + }, + { + "advisory_id": "github_osv_importer_v2/GHSA-6hg6-v5c8-fphq", + "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-6hg6-v5c8-fphq/GHSA-6hg6-v5c8-fphq.json", + "aliases": [ + "CVE-2026-34477" + ], + "summary": "Apache Log4j Core: `verifyHostName` attribute silently ignored in TLS configuration", + "severities": [ + { + "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-6hg6-v5c8-fphq/GHSA-6hg6-v5c8-fphq.json", + "value": "6.3", + "scoring_system": "cvssv4", + "scoring_elements": "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:L/SA:N" + }, + { + "url": null, + "value": "MODERATE", + "scoring_system": "generic_textual", + "scoring_elements": "" + } + ], + "weaknesses": [], + "references": [ + { + "url": "https://github.com/apache/logging-log4j2", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://logging.apache.org/cyclonedx/vdr.xml", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://logging.apache.org/log4j/2.x/manual/appenders/network.html#SslConfiguration-attr-verifyHostName", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://github.com/apache/logging-log4j2/pull/4075", + "reference_type": "other", + "reference_id": "4075" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34477", + "reference_type": "", + "reference_id": "CVE-2026-34477" + }, + { + "url": "https://lists.apache.org/thread/lkx8cl46t2bvkcwfcb2pd43ygc097lq4", + "reference_type": "advisory", + "reference_id": "lkx8cl46t2bvkcwfcb2pd43ygc097lq4" + }, + { + "url": "https://logging.apache.org/security.html#CVE-2026-34477", + "reference_type": "advisory", + "reference_id": "security.html#CVE-2026-34477" + } + ], + "related_ssvc_trees": [], + "fixed_by_packages": [] + } + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_page2.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_page2.json new file mode 100644 index 0000000000000..75de5612624e0 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_page2.json @@ -0,0 +1,104 @@ +{ + "count": 3, + "next": null, + "previous": "/api/v3/affected-by-advisories/?purl=pkg:maven%2Forg.apache.logging.log4j%2Flog4j-core%402.17.0&page=1", + "results": [ + { + "advisory_id": "github_osv_importer_v2/GHSA-8489-44mv-ggj8", + "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/01/GHSA-8489-44mv-ggj8/GHSA-8489-44mv-ggj8.json", + "aliases": [ + "CVE-2021-44832" + ], + "summary": "Improper Input Validation and Injection in Apache Log4j2", + "severities": [ + { + "url": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/01/GHSA-8489-44mv-ggj8/GHSA-8489-44mv-ggj8.json", + "value": "6.6", + "scoring_system": "cvssv3.1", + "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H" + }, + { + "url": null, + "value": "MODERATE", + "scoring_system": "generic_textual", + "scoring_elements": "" + } + ], + "weaknesses": [], + "references": [ + { + "url": "https://cert-portal.siemens.com/productcert/pdf/ssa-784507.pdf", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://github.com/apache/logging-log4j2", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://issues.apache.org/jira/browse/LOG4J2-3293", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://lists.apache.org/thread/s1o5vlo78ypqxnzn6p8zf6t9shtq5143", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2021/12/msg00036.html", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EVV25FXL4FU5X6X5BSL7RLQ7T6F65MRA", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/T57MPJUW3MA6QGWZRTMCHHMMPQNVKGFC", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-apache-log4j-qRuKNEbd", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20220104-0001", + "reference_type": "", + "reference_id": "" + }, + { + "url": "http://www.openwall.com/lists/oss-security/2021/12/28/1", + "reference_type": "", + "reference_id": "" + }, + { + "url": "https://www.oracle.com/security-alerts/cpuapr2022.html", + "reference_type": "other", + "reference_id": "cpuapr2022.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpujan2022.html", + "reference_type": "other", + "reference_id": "cpujan2022.html" + }, + { + "url": "https://www.oracle.com/security-alerts/cpujul2022.html", + "reference_type": "other", + "reference_id": "cpujul2022.html" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-44832", + "reference_type": "", + "reference_id": "CVE-2021-44832" + } + ], + "related_ssvc_trees": [], + "fixed_by_packages": [] + } + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_severities_references.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_severities_references.json new file mode 100644 index 0000000000000..945306acfdb02 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_severities_references.json @@ -0,0 +1,39 @@ +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "advisory_id": "github_osv_importer_v2/GHSA-3pxv-7cmr-fjr4", + "url": "https://github.com/advisories/GHSA-3pxv-7cmr-fjr4/GHSA-3pxv-7cmr-fjr4.json", + "aliases": [ + "CVE-2026-34480" + ], + "summary": "Apache Log4j Core: Silent log event loss in XmlLayout due to unescaped XML 1.0 forbidden characters", + "severities": [ + { + "url": "https://github.com/advisories/GHSA-3pxv-7cmr-fjr4/GHSA-3pxv-7cmr-fjr4.json", + "value": "6.9", + "scoring_system": "cvssv4", + "scoring_elements": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N" + }, + { + "url": null, + "value": "MODERATE", + "scoring_system": "generic_textual", + "scoring_elements": "" + } + ], + "weaknesses": [], + "references": [ + { + "url": "https://github.com/apache/logging-log4j2/pull/4077", + "reference_type": "other", + "reference_id": "4077" + } + ], + "related_ssvc_trees": [], + "fixed_by_packages": [] + } + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_struts.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_struts.json new file mode 100644 index 0000000000000..7919ea455f73e --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/affected_by_advisories_response_struts.json @@ -0,0 +1,51 @@ +{ + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "advisory_id": "github_osv_importer_v2/GHSA-v7xh-cg3m-mx2g", + "url": "https://example.org/advisories/GHSA-v7xh-cg3m-mx2g", + "aliases": [ + "CVE-2009-1382" + ], + "severities": [ + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2009-1382", + "value": "7.0", + "scoring_system": "cvssv2", + "scoring_elements": null + } + ], + "weaknesses": [], + "references": [], + "related_ssvc_trees": [], + "fixed_by_packages": [] + }, + { + "advisory_id": "github_osv_importer_v2/GHSA-struts-2019-cov19", + "url": "https://example.org/advisories/GHSA-struts-2019-cov19", + "aliases": [ + "CVE-2019-CoV19" + ], + "severities": [ + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-CoV19", + "value": "10.0", + "scoring_system": "cvssv3", + "scoring_elements": null + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-CoV19", + "value": "HIGH", + "scoring_system": "cvssv3.1_qr", + "scoring_elements": null + } + ], + "weaknesses": [], + "references": [], + "related_ssvc_trees": [], + "fixed_by_packages": [] + } + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_junit.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_junit.json new file mode 100644 index 0000000000000..8b044bd595c1e --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_junit.json @@ -0,0 +1,8 @@ +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + "pkg:maven/junit/junit@4.12" + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_log4j.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_log4j.json new file mode 100644 index 0000000000000..8771199a140d3 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_log4j.json @@ -0,0 +1,8 @@ +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0" + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_packages.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_packages.json new file mode 100644 index 0000000000000..cdae2bbf9356b --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_packages.json @@ -0,0 +1,9 @@ +{ + "count": 2, + "next": null, + "previous": null, + "results": [ + "pkg:maven/org.apache.commons/commons-lang3@3.5", + "pkg:maven/org.apache.struts/struts2-assembly@2.5.14.1" + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_page1.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_page1.json new file mode 100644 index 0000000000000..59e3343e60d63 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_page1.json @@ -0,0 +1,9 @@ +{ + "count": 3, + "next": "/api/v3/packages/?page=2", + "previous": null, + "results": [ + "pkg:maven/junit/junit@4.12", + "pkg:maven/org.apache.commons/commons-lang3@3.5" + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_page2.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_page2.json new file mode 100644 index 0000000000000..7aad16b5bfb89 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_page2.json @@ -0,0 +1,8 @@ +{ + "count": 3, + "next": null, + "previous": "/api/v3/packages/?page=1", + "results": [ + "pkg:maven/org.apache.struts/struts2-assembly@2.5.14.1" + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_unexpected_packages.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_unexpected_packages.json new file mode 100644 index 0000000000000..b4e7ab4f7bb27 --- /dev/null +++ b/plugins/advisors/vulnerable-code/src/test/resources/__files/packages_response_unexpected_packages.json @@ -0,0 +1,10 @@ +{ + "count": 3, + "next": null, + "previous": null, + "results": [ + "pkg:maven/org.apache.commons/commons-lang3@3.5", + "pkg:maven/org.unknown/unexpected@4.2", + "pkg:maven/org.apache.struts/struts2-assembly@2.5.14.1" + ] +} diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_junit.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/response_junit.json deleted file mode 100644 index cddedd55f3249..0000000000000 --- a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_junit.json +++ /dev/null @@ -1,57 +0,0 @@ -[ - { - "url": "http://public.vulnerablecode.io/api/packages/168702", - "purl": "pkg:maven/junit/junit@4.12", - "type": "maven", - "namespace": "junit", - "name": "junit", - "version": "4.12", - "qualifiers": {}, - "subpath": "", - "affected_by_vulnerabilities": [ - { - "url": "http://public.vulnerablecode.io/api/vulnerabilities/1265", - "vulnerability_id": "VCID-e1bu-4uh4-aaac", - "summary": "", - "references": [ - { - "reference_url": "http://people.canonical.com/~ubuntu-security/cve/2020/CVE-2020-15250.html", - "reference_id": "", - "scores": [ - { - "value": "Medium", - "scoring_system": "generic_textual", - "scoring_elements": "" - } - ], - "url": "http://people.canonical.com/~ubuntu-security/cve/2020/CVE-2020-15250.html" - }, - { - "reference_url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2020-15250.json", - "reference_id": "", - "scores": [ - { - "value": "4.0", - "scoring_system": "cvssv3", - "scoring_elements": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N" - } - ], - "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2020-15250.json" - } - ], - "aliases": [ - "CVE-2020-15250", - "GHSA-269g-pwp5-87pp" - ], - "fixed_packages": [ - { - "url": "http://public.vulnerablecode.io/api/packages/99502", - "purl": "pkg:maven/junit/junit@4.13.1", - "is_vulnerable": false - } - ] - } - ], - "fixing_vulnerabilities": [] - } -] diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_log4j.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/response_log4j.json deleted file mode 100644 index 5aa49c32f9846..0000000000000 --- a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_log4j.json +++ /dev/null @@ -1,52 +0,0 @@ -[ - { - "url": "http://public.vulnerablecode.io/api/packages/124759", - "purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0", - "type": "maven", - "namespace": "org.apache.logging.log4j", - "name": "log4j-core", - "version": "2.17.0", - "qualifiers": {}, - "subpath": "", - "unresolved_vulnerabilities": [ - { - "url": "http://public.vulnerablecode.io/api/vulnerabilities/8905", - "vulnerability_id": "VCID-bk15-3vac-aaaj", - "summary": "Remote code injection in Log4j", - "references": [ - { - "reference_url": "http://ref.com/files/165225/Apache-Log4j2-2.14.1-Remote-Code-Execution.html", - "reference_id": "", - "scores": [], - "url": "http://ref.com/files/165225/Apache-Log4j2-2.14.1-Remote-Code-Execution.html" - } - ], - "aliases": [ - "GHSA-jfh8-c2jp-5v3q" - ] - }, - { - "url": "http://public.vulnerablecode.io/api/vulnerabilities/8994", - "vulnerability_id": "VCID-y1c6-8gmx-aaar", - "summary": "Improper Input Validation and Injection in Apache Log4j2", - "references": [ - { - "reference_url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2021-44832.json", - "reference_id": "", - "scores": [ - { - "value": "6.6", - "scoring_system": "cvssv3", - "scoring_elements": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H" - } - ], - "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2021-44832.json" - } - ], - "aliases": [ - "CVE-2021-44832" - ] - } - ] - } -] diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_packages.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/response_packages.json deleted file mode 100644 index 3ed0350c5aa89..0000000000000 --- a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_packages.json +++ /dev/null @@ -1,92 +0,0 @@ -[ - { - "name": "commons-lang3", - "namespace": "org.apache.commons", - "purl": "pkg:maven/org.apache.commons/commons-lang3@3.5", - "qualifiers": {}, - "resolved_vulnerabilities": [], - "subpath": "", - "type": "maven", - "version": "3.5", - "url": "http://testserver/api/packages/3467", - "unresolved_vulnerabilities": [ - { - "references": [ - { - "reference_id": "", - "scores": [], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-8242" - }, - { - "reference_id": "GHSA-2cxf-6567-7pp6", - "scores": [{"scoring_system": "cvssv3.1_qr", "value": "LOW"}], - "source": "", - "url": "https://github.com/apache/commons-lang/security/advisories/GHSA-2cxf-6567-7pp6" - }, - { - "reference_id": "GHSA-2cxf-6567-7pp6", - "scores": [], - "source": "", - "url": "https://github.com/advisories/GHSA-2cxf-6567-7pp6" - } - ], - "url": "http://testserver/api/vulnerabilities/60", - "vulnerability_id": "CVE-2014-8242" - } - ] - }, - { - "name": "struts2-assembly", - "namespace": "org.apache.struts", - "purl": "pkg:maven/org.apache.struts/struts2-assembly@2.5.14.1", - "qualifiers": {}, - "resolved_vulnerabilities": [ - { - "references": [ - { - "reference_id": "", - "scores": [{"scoring_system": "cvssv3.1", "value": "6"}], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-8242" - } - ], - "url": "http://testserver/api/vulnerabilities/61", - "vulnerability_id": "CVE-2009-2459" - } - ], - "subpath": "", - "type": "maven", - "version": "2.5.14.1", - "url": "http://testserver/api/packages/3468", - "unresolved_vulnerabilities": [ - { - "references": [ - { - "reference_id": "", - "scores": [{"scoring_system": "cvssv2", "value": "7"}], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2009-1382" - } - ], - "url": "http://testserver/api/vulnerabilities/62", - "vulnerability_id": "CVE-2009-1382" - }, - { - "references": [ - { - "reference_id": "", - "scores": [ - {"scoring_system": "cvssv3", "value": "10"}, - {"scoring_system": "cvssv3.1_qr", "value": "HIGH" } - ], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-CoV19" - } - ], - "url": "http://testserver/api/vulnerabilities/63", - "vulnerability_id": "CVE-2019-CoV19" - } - ] - } -] diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_packages_no_vulnerabilities.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/response_packages_no_vulnerabilities.json deleted file mode 100644 index eac199184208d..0000000000000 --- a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_packages_no_vulnerabilities.json +++ /dev/null @@ -1,67 +0,0 @@ -[ - { - "name": "commons-lang3", - "namespace": "org.apache.commons", - "purl": "pkg:maven/org.apache.commons/commons-lang3@3.5", - "qualifiers": {}, - "resolved_vulnerabilities": [], - "subpath": "", - "type": "maven", - "version": "3.5", - "url": "http://testserver/api/packages/3467", - "unresolved_vulnerabilities": [] - }, - { - "name": "struts2-assembly", - "namespace": "org.apache.struts", - "purl": "pkg:maven/org.apache.struts/struts2-assembly@2.5.14.1", - "qualifiers": {}, - "resolved_vulnerabilities": [ - { - "references": [ - { - "reference_id": "", - "scores": [{"scoring_system": "cvssv3.1", "value": "6"}], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-8242" - } - ], - "url": "http://testserver/api/vulnerabilities/61", - "vulnerability_id": "CVE-2009-2459" - } - ], - "subpath": "", - "type": "maven", - "version": "2.5.14.1", - "url": "http://testserver/api/packages/3468", - "unresolved_vulnerabilities": [ - { - "references": [ - { - "reference_id": "", - "scores": [{"scoring_system": "cvssv2", "value": "7"}], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2009-1382" - } - ], - "url": "http://testserver/api/vulnerabilities/62", - "vulnerability_id": "CVE-2009-1382" - }, - { - "references": [ - { - "reference_id": "", - "scores": [ - {"scoring_system": "cvssv3", "value": "10"}, - {"scoring_system": "cvssv3.1_qr", "value": "HIGH" } - ], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-CoV19" - } - ], - "url": "http://testserver/api/vulnerabilities/63", - "vulnerability_id": "CVE-2019-CoV19" - } - ] - } -] diff --git a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_unexpected_packages.json b/plugins/advisors/vulnerable-code/src/test/resources/__files/response_unexpected_packages.json deleted file mode 100644 index b7d9235504633..0000000000000 --- a/plugins/advisors/vulnerable-code/src/test/resources/__files/response_unexpected_packages.json +++ /dev/null @@ -1,117 +0,0 @@ -[ - { - "name": "commons-lang3", - "namespace": "org.apache.commons", - "purl": "pkg:maven/org.apache.commons/commons-lang3@3.5", - "qualifiers": {}, - "resolved_vulnerabilities": [], - "subpath": "", - "type": "maven", - "version": "3.5", - "url": "http://testserver/api/packages/3467", - "unresolved_vulnerabilities": [ - { - "references": [ - { - "reference_id": "", - "scores": [], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-8242" - }, - { - "reference_id": "GHSA-2cxf-6567-7pp6", - "scores": [{"scoring_system": "cvssv3.1_qr", "value": "LOW"}], - "source": "", - "url": "https://github.com/apache/commons-lang/security/advisories/GHSA-2cxf-6567-7pp6" - }, - { - "reference_id": "GHSA-2cxf-6567-7pp6", - "scores": [], - "source": "", - "url": "https://github.com/advisories/GHSA-2cxf-6567-7pp6" - } - ], - "url": "http://testserver/api/vulnerabilities/60", - "vulnerability_id": "CVE-2014-8242" - } - ] - }, - { - "name": "unexpected", - "namespace": "org.unknown", - "purl": "pkg:maven/org.unknown/unexpected@4.2", - "qualifiers": {}, - "resolved_vulnerabilities": [], - "subpath": "", - "type": "maven", - "version": "4.2", - "url": "http://testserver/api/packages/3333", - "unresolved_vulnerabilities": [ - { - "references": [ - { - "reference_id": "", - "scores": [{"scoring_system": "cvssv3.1", "value": "6.5"}], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-9876" - } - ], - "url": "http://testserver/api/vulnerabilities/42", - "vulnerability_id": "CVE-2021-9876" - } - ] - }, - { - "name": "struts2-assembly", - "namespace": "org.apache.struts", - "purl": "pkg:maven/org.apache.struts/struts2-assembly@2.5.14.1", - "qualifiers": {}, - "resolved_vulnerabilities": [ - { - "references": [ - { - "reference_id": "", - "scores": [{"scoring_system": "cvssv3.1", "value": "6"}], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-8242" - } - ], - "url": "http://testserver/api/vulnerabilities/61", - "vulnerability_id": "CVE-2009-2459" - } - ], - "subpath": "", - "type": "maven", - "version": "2.5.14.1", - "url": "http://testserver/api/packages/3468", - "unresolved_vulnerabilities": [ - { - "references": [ - { - "reference_id": "", - "scores": [{"scoring_system": "cvssv2", "value": "7"}], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2009-1382" - } - ], - "url": "http://testserver/api/vulnerabilities/62", - "vulnerability_id": "CVE-2009-1382" - }, - { - "references": [ - { - "reference_id": "", - "scores": [ - {"scoring_system": "cvssv3", "value": "10"}, - {"scoring_system": "cvssv3.1_qr", "value": "HIGH" } - ], - "source": "", - "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-CoV19" - } - ], - "url": "http://testserver/api/vulnerabilities/63", - "vulnerability_id": "CVE-2019-CoV19" - } - ] - } -]