diff --git a/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java b/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java index 955b883e..478b540f 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java @@ -291,6 +291,11 @@ public class MassImportService { if (index.isBlank()) continue; String filename = index.contains(".") ? index : index + ".pdf"; + if (!isValidImportFilename(filename)) { + log.warn("Skipping import row {}: filename rejected — {}", i, filename); + skippedFiles.add(new SkippedFile(filename, "INVALID_FILENAME_PATH_TRAVERSAL")); + continue; + } Optional fileOnDisk = findFileRecursive(filename); if (fileOnDisk.isEmpty()) { log.warn("Datei nicht gefunden, importiere nur Metadaten: {}", filename); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/importing/MassImportServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/importing/MassImportServiceTest.java index 4804ffcb..2b43bedf 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/importing/MassImportServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/importing/MassImportServiceTest.java @@ -494,6 +494,24 @@ class MassImportServiceTest { assertThat(result).isTrue(); } + @Test + void processRows_skipsRowAndContinues_whenFilenameIsPathTraversal() { + when(documentService.findByOriginalFilename("legitimate.pdf")).thenReturn(Optional.empty()); + when(documentService.save(any())).thenAnswer(inv -> inv.getArgument(0)); + + List> rows = List.of( + List.of("header"), + minimalCells("../evil"), // row 1: path traversal — should be skipped + minimalCells("legitimate.pdf") // row 2: valid — should be processed + ); + MassImportService.ProcessResult result = ReflectionTestUtils.invokeMethod(service, "processRows", rows); + + assertThat(result.processed()).isEqualTo(1); + assertThat(result.skippedFiles()) + .extracting(MassImportService.SkippedFile::reason) + .containsExactly("INVALID_FILENAME_PATH_TRAVERSAL"); + } + // ─── importSingleDocument — non-blank optional fields ──────────────────── @Test