security(import): block Unicode lookalike path separators in isValidImportFilename
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 <noreply@anthropic.com>
This commit is contained in:
@@ -329,9 +329,15 @@ public class MassImportService {
|
|||||||
if (filename == null || filename.isBlank()) return false;
|
if (filename == null || filename.isBlank()) return false;
|
||||||
if (filename.contains("/")) return false;
|
if (filename.contains("/")) 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.contains("..")) return false;
|
||||||
if (filename.equals(".")) return false;
|
if (filename.equals(".")) return false;
|
||||||
if (filename.contains("\0")) 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;
|
if (Paths.get(filename).isAbsolute()) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -494,6 +494,24 @@ class MassImportServiceTest {
|
|||||||
assertThat(result).isTrue();
|
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
|
@Test
|
||||||
void processRows_skipsRowAndContinues_whenFilenameIsPathTraversal() {
|
void processRows_skipsRowAndContinues_whenFilenameIsPathTraversal() {
|
||||||
when(documentService.findByOriginalFilename("legitimate.pdf")).thenReturn(Optional.empty());
|
when(documentService.findByOriginalFilename("legitimate.pdf")).thenReturn(Optional.empty());
|
||||||
|
|||||||
Reference in New Issue
Block a user