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..cfae7b1054b29 100644 --- a/clients/vulnerable-code/build.gradle.kts +++ b/clients/vulnerable-code/build.gradle.kts @@ -17,21 +17,40 @@ * 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(libs.retrofit.converter.kotlinxSerialization) + implementation(ktorLibs.http) } description = "A client to communicate with the API of a VulnerableCode instance." 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..4cbe5336ad925 --- /dev/null +++ b/clients/vulnerable-code/openapi/vulnerablecode.openapi.json @@ -0,0 +1,3467 @@ +{ + "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", + "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": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaginatedPurlList" + } + } + }, + "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": { + "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": { + "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" + } + } + }, + "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": "array", + "items": { + "$ref": "#/components/schemas/RelatedSSVCTree" + }, + "readOnly": true + }, + "fixed_by_packages": { + "type": "array", + "items": { + "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", + "fixing_vulnerabilities", + "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" + } + } + } + }, + "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": [ + "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." + } + } + }, + "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", + "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/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/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" } 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" - } - ] - } -]