diff --git a/app/apps/import_app/services/v1.py b/app/apps/import_app/services/v1.py index 5aad0b9e..2bc16981 100644 --- a/app/apps/import_app/services/v1.py +++ b/app/apps/import_app/services/v1.py @@ -1131,7 +1131,8 @@ class ImportService: Validates that the file path is within the allowed temporary directory. Returns the absolute path. """ - abs_path = os.path.abspath(file_path) + # resolve() like IMPORT_TEMP_ROOT, so symlinks can't point outside it + abs_path = str(Path(file_path).resolve()) if not Path(abs_path).is_relative_to(settings.IMPORT_TEMP_ROOT): raise ValueError( f"Invalid file path. File must be in {settings.IMPORT_TEMP_ROOT}" diff --git a/app/apps/import_app/tests/test_paths.py b/app/apps/import_app/tests/test_paths.py index 33c6d32d..a9eb7807 100644 --- a/app/apps/import_app/tests/test_paths.py +++ b/app/apps/import_app/tests/test_paths.py @@ -149,3 +149,13 @@ class ImportTempRootTests(ImportObjectPermissionTestCase): ): with self.subTest(path=path), self.assertRaises(ValueError): service._validate_file_path(str(path)) + + def test_service_rejects_symlinks_pointing_outside_temp_root(self): + link = self.temp_root / "link.csv" + try: + link.symlink_to("/etc/passwd") + except OSError: + self.skipTest("symlinks not available") + + with self.assertRaises(ValueError): + self._service()._validate_file_path(str(link)) diff --git a/docker/prod/django/Dockerfile b/docker/prod/django/Dockerfile index 9ad00cb2..fd83829f 100644 --- a/docker/prod/django/Dockerfile +++ b/docker/prod/django/Dockerfile @@ -69,9 +69,7 @@ RUN sed -i 's/\r$//g' /start && \ COPY --chown=app:app ./app . RUN chown -R app:app /usr/src/app && \ - chown -R app:app /usr/src/frontend && \ - mkdir /temp && \ - chown -R app:app /temp + chown -R app:app /usr/src/frontend USER app