Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM golang:1.22-alpine as go_stage
RUN go install github.com/google/osv-scanner/cmd/osv-scanner@v1
FROM golang:1.27-alpine as go_stage
RUN go install github.com/google/osv-scanner/v2/cmd/osv-scanner@v2.6.0

FROM python:3.14-alpine as base
FROM base as builder
Expand Down
11 changes: 6 additions & 5 deletions agent/osv_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,14 +750,15 @@ def _process_fingerprint_file(self, message: m.Message) -> None:

def _is_valid_osv_result(results: str | None) -> bool:
"""Check if the results are valid."""
if results is None:
return False

if results == "":
if results is None or results == "":
return False

try:
if json.loads(results) == {"results": []}:
parsed = json.loads(results)
if isinstance(parsed, dict) is False:
return False
results_list = parsed.get("results")
if isinstance(results_list, list) is False or len(results_list) == 0:
Comment thread
azizelbelaychy marked this conversation as resolved.
return False
except json.JSONDecodeError:
return False
Expand Down
126 changes: 126 additions & 0 deletions tests/osv_agent_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Unittests for OSV agent."""

import json
import subprocess
from collections.abc import Callable
from typing import Any
Expand Down Expand Up @@ -1585,3 +1586,128 @@ def testAgentOSV_whenContentUrlBasenameIsSupportedFileName_shouldScanOnlyThatFor
"package-lock.json" in call.args[0] for call in subprocess_mock.call_args_list
)
assert len(agent_mock) == 1


def testAgentOSV_whenV2OutputHasEmptyResultsWithExperimentalConfig_shouldNotEmitVulnerability(
test_agent: osv_agent.OSVAgent,
agent_mock: list[message.Message],
agent_persist_mock: dict[str | bytes, str | bytes],
scan_message_file: message.Message,
mocker: plugin.MockerFixture,
) -> None:
"""When OSV v2 returns empty results with experimental_config, no vulnerabilities should be emitted."""
v2_empty_output = (
'{"results": [], "experimental_config": {"licenses": {"summary": false}}}'
)
mocker.patch(
"subprocess.run",
return_value=subprocess.CompletedProcess([], 0, v2_empty_output, None),
)

test_agent.process(scan_message_file)

assert len(agent_mock) == 0


def testAgentOSV_whenV1OutputHasEmptyResults_shouldNotEmitVulnerability(
test_agent: osv_agent.OSVAgent,
agent_mock: list[message.Message],
agent_persist_mock: dict[str | bytes, str | bytes],
scan_message_file: message.Message,
mocker: plugin.MockerFixture,
) -> None:
"""When OSV v1 returns empty results, no vulnerabilities should be emitted."""
mocker.patch(
"subprocess.run",
return_value=subprocess.CompletedProcess([], 0, '{"results": []}', None),
)

test_agent.process(scan_message_file)

assert len(agent_mock) == 0


def testAgentOSV_whenOutputIsMalformedJson_shouldNotEmitVulnerability(
test_agent: osv_agent.OSVAgent,
agent_mock: list[message.Message],
agent_persist_mock: dict[str | bytes, str | bytes],
scan_message_file: message.Message,
mocker: plugin.MockerFixture,
) -> None:
"""When OSV scanner returns malformed JSON, no vulnerabilities should be emitted."""
mocker.patch(
"subprocess.run",
return_value=subprocess.CompletedProcess([], 0, "not-a-json", None),
)

test_agent.process(scan_message_file)

assert len(agent_mock) == 0


def testAgentOSV_whenV2OutputHasVulnerabilities_shouldEmitVulnerability(
test_agent: osv_agent.OSVAgent,
agent_mock: list[message.Message],
agent_persist_mock: dict[str | bytes, str | bytes],
scan_message_file: message.Message,
mocker: plugin.MockerFixture,
fake_osv_output: str,
osv_output_as_dict: dict[str, Any],
) -> None:
"""When OSV v2 returns populated results alongside experimental_config, vulnerabilities should be emitted."""
parsed = json.loads(fake_osv_output)
parsed["experimental_config"] = {"licenses": {"summary": False}}
v2_vuln_output = json.dumps(parsed)

cve_data = cve_service_api.CVE(
risk="HIGH",
description="description",
fixed_version="2",
cvss_v3_vector=None,
)
mocker.patch("agent.cve_service_api.get_cve_data_from_api", return_value=cve_data)
mocker.patch("agent.osv_output_handler.calculate_risk_rating", return_value="HIGH")
mocker.patch(
"subprocess.run",
return_value=subprocess.CompletedProcess([], 0, v2_vuln_output, None),
)

test_agent.process(scan_message_file)

assert len(agent_mock) > 0
Comment thread
azizelbelaychy marked this conversation as resolved.


def testAgentOSV_whenOutputIsNullJson_shouldNotEmitVulnerability(
test_agent: osv_agent.OSVAgent,
agent_mock: list[message.Message],
agent_persist_mock: dict[str | bytes, str | bytes],
scan_message_file: message.Message,
mocker: plugin.MockerFixture,
) -> None:
"""When OSV scanner returns 'null' JSON, no vulnerabilities should be emitted."""
mocker.patch(
"subprocess.run",
return_value=subprocess.CompletedProcess([], 0, "null", None),
)

test_agent.process(scan_message_file)

assert len(agent_mock) == 0


def testAgentOSV_whenOutputIsJsonArray_shouldNotEmitVulnerability(
test_agent: osv_agent.OSVAgent,
agent_mock: list[message.Message],
agent_persist_mock: dict[str | bytes, str | bytes],
scan_message_file: message.Message,
mocker: plugin.MockerFixture,
) -> None:
"""When OSV scanner returns a JSON array '[]', no vulnerabilities should be emitted."""
mocker.patch(
"subprocess.run",
return_value=subprocess.CompletedProcess([], 0, "[]", None),
)

test_agent.process(scan_message_file)

assert len(agent_mock) == 0
Loading