security(import): wire isValidImportFilename guard into processRows
All checks were successful
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m26s
CI / fail2ban Regex (pull_request) Successful in 45s
CI / Semgrep Security Scan (pull_request) Successful in 21s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m0s
CI / Unit & Component Tests (pull_request) Successful in 3m30s
All checks were successful
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m26s
CI / fail2ban Regex (pull_request) Successful in 45s
CI / Semgrep Security Scan (pull_request) Successful in 21s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m0s
CI / Unit & Component Tests (pull_request) Successful in 3m30s
Rejects path-traversal filenames before findFileRecursive runs. Guard runs on the derived filename (after the ternary) as specified. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -291,6 +291,11 @@ public class MassImportService {
|
|||||||
if (index.isBlank()) continue;
|
if (index.isBlank()) continue;
|
||||||
|
|
||||||
String filename = index.contains(".") ? index : index + ".pdf";
|
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<File> fileOnDisk = findFileRecursive(filename);
|
Optional<File> fileOnDisk = findFileRecursive(filename);
|
||||||
if (fileOnDisk.isEmpty()) {
|
if (fileOnDisk.isEmpty()) {
|
||||||
log.warn("Datei nicht gefunden, importiere nur Metadaten: {}", filename);
|
log.warn("Datei nicht gefunden, importiere nur Metadaten: {}", filename);
|
||||||
|
|||||||
@@ -494,6 +494,24 @@ class MassImportServiceTest {
|
|||||||
assertThat(result).isTrue();
|
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<List<String>> 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 ────────────────────
|
// ─── importSingleDocument — non-blank optional fields ────────────────────
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user