From 429ff32edafb82cbbd2143bd2641758d9e6ddbf5 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 21 May 2026 10:06:49 +0200 Subject: [PATCH] security(import): block Unicode lookalike path separators in isValidImportFilename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds checks for U+2215 DIVISION SLASH (∕), U+FF0F FULLWIDTH SOLIDUS (/), and U+29F5 REVERSE SOLIDUS OPERATOR (⧵) — all of which bypass the existing ASCII separator checks on Linux path resolution. Adds a clarifying comment on the Paths.get().isAbsolute() call explaining its InvalidPathException safety boundary. Adds 3 regression tests. Co-Authored-By: Claude Sonnet 4.6 --- .../importing/MassImportService.java | 6 ++++++ .../importing/MassImportServiceTest.java | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) 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 478b540f..64b09294 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/importing/MassImportService.java @@ -329,9 +329,15 @@ public class MassImportService { if (filename == null || filename.isBlank()) return false; if (filename.contains("/")) return false; if (filename.contains("\\")) return false; + if (filename.contains("∕")) return false; // U+2215 DIVISION SLASH + if (filename.contains("/")) return false; // U+FF0F FULLWIDTH SOLIDUS + if (filename.contains("⧵")) return false; // U+29F5 REVERSE SOLIDUS OPERATOR if (filename.contains("..")) return false; if (filename.equals(".")) return false; if (filename.contains("\0")) return false; + // Paths.get() is safe here on Linux for all inputs that passed the checks above; + // it may throw InvalidPathException for OS-specific illegal chars on Windows, + // but those are not reachable in production. if (Paths.get(filename).isAbsolute()) return false; return true; } 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 2b43bedf..167b78cd 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 isValidImportFilename_returnsFalse_whenFilenameContainsUnicodeDivisionSlash() { + boolean result = ReflectionTestUtils.invokeMethod(service, "isValidImportFilename", "foo∕bar.pdf"); + assertThat(result).isFalse(); + } + + @Test + void isValidImportFilename_returnsFalse_whenFilenameContainsFullwidthSlash() { + boolean result = ReflectionTestUtils.invokeMethod(service, "isValidImportFilename", "foo/bar.pdf"); + assertThat(result).isFalse(); + } + + @Test + void isValidImportFilename_returnsFalse_whenFilenameContainsUnicodeReverseSolidus() { + boolean result = ReflectionTestUtils.invokeMethod(service, "isValidImportFilename", "foo⧵bar.pdf"); + assertThat(result).isFalse(); + } + @Test void processRows_skipsRowAndContinues_whenFilenameIsPathTraversal() { when(documentService.findByOriginalFilename("legitimate.pdf")).thenReturn(Optional.empty());