From b46bf9c76536d30adcb9a47e997409e64c7be7da Mon Sep 17 00:00:00 2001 From: Mrityunjay Raj Date: Thu, 1 Jan 2026 10:40:52 +0530 Subject: [PATCH] When ClamAV returns scan results for files that no longer exist in the CodebaseResource database (e.g., temporary files that were cleaned up), the function would crash with ObjectDoesNotExist. This fix catches that exception and logs an error message instead of crashing, allowing the scan to continue processing other files. Fixes #2019 Signed-off-by: Mrityunjay Raj --- scanpipe/pipes/clamav.py | 15 +++++++++++++- scanpipe/tests/pipes/test_clamav.py | 31 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/scanpipe/pipes/clamav.py b/scanpipe/pipes/clamav.py index 78a8f45b70..9e4f5a4d40 100644 --- a/scanpipe/pipes/clamav.py +++ b/scanpipe/pipes/clamav.py @@ -23,6 +23,7 @@ from pathlib import Path from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist import clamd @@ -47,7 +48,19 @@ def scan_for_virus(project): status, reason = results resource_path = Path(resource_location).relative_to(project.codebase_path) - resource = project.codebaseresources.get(path=resource_path) + try: + resource = project.codebaseresources.get(path=str(resource_path)) + except ObjectDoesNotExist: + project.add_error( + description="CodebaseResource not found for ClamAV result", + model="ScanForVirus", + details={ + "resource_location": resource_location, + "resource_path": str(resource_path), + }, + ) + continue + virus_report = { "calmav": { "status": status, diff --git a/scanpipe/tests/pipes/test_clamav.py b/scanpipe/tests/pipes/test_clamav.py index 26e966b698..358338b8ff 100644 --- a/scanpipe/tests/pipes/test_clamav.py +++ b/scanpipe/tests/pipes/test_clamav.py @@ -67,3 +67,34 @@ def test_scanpipe_pipes_clamav_scan_for_virus(self, mock_multiscan): } } self.assertEqual(expected_virus_report_extra_data, resource1.extra_data) + + @mock.patch("clamd.ClamdNetworkSocket.multiscan") + def test_scanpipe_pipes_clamav_scan_for_virus_missing_resource(self, mock_multiscan): + """Test that scan_for_virus handles missing CodebaseResource gracefully.""" + project = Project.objects.create(name="project") + r1 = make_resource_file(project=project, path="existing.zip") + + # Simulate ClamAV returning results for both existing and non-existing resources + mock_multiscan.return_value = { + r1.location: ("FOUND", "Win.Test.EICAR_HDB-1"), + str(project.codebase_path / "missing_file.exe"): ("FOUND", "Trojan.Test"), + } + + # Should not raise an exception + clamav.scan_for_virus(project) + + # Should have 2 error messages: one for virus, one for missing resource + messages = list(project.projectmessages.all()) + self.assertEqual(2, len(messages)) + + # Check that missing resource error was logged + missing_resource_error = project.projectmessages.filter( + description="CodebaseResource not found for ClamAV result" + ).first() + self.assertIsNotNone(missing_resource_error) + self.assertEqual("missing_file.exe", missing_resource_error.details["resource_path"]) + + # Check that existing resource still got processed + virus_error = project.projectmessages.filter(description="Virus detected").first() + self.assertIsNotNone(virus_error) + self.assertEqual("existing.zip", virus_error.details["resource_path"])