From 86b548bd68c89146a82d1cb63fe9433a1d4ed0c0 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 26 Jun 2026 04:04:06 +0000 Subject: [PATCH] =?UTF-8?q?=EB=B3=B4=EC=95=88:=20=EC=98=A4=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EB=B6=84=EB=A6=AC=20=EB=AA=A8=EB=93=88=EC=9D=98=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=ED=83=90=EC=83=89(Path=20Traversal)=20?= =?UTF-8?q?=EC=B7=A8=EC=95=BD=EC=A0=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/sentinel.md | 5 +++++ .../separation/audio_separator.py | 12 +++++++++-- .../analysis-engine/tests/test_separation.py | 20 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index c7a67127..12f06f20 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,8 @@ **Vulnerability:** CSV formula injection mitigation was naive, missing leading whitespace, tabs, and newlines. **Learning:** Checking `/^[=+\-@]/` is not sufficient, as OWASP states that spaces and tabs before the formula triggers will also execute the formula in applications like Excel. **Prevention:** Use a regex that allows leading whitespace (e.g. `/^[\s\uFEFF\xA0]*[=+\-@\t\r\n]/`) and include standalone tabs or new lines which are also injection vectors. + +## 2025-02-24 - Path Traversal via os.path.expanduser +**Vulnerability:** Path traversal using `.expanduser()` on untrusted path input. +**Learning:** Avoid using `.expanduser()` on untrusted input paths in backend Python services, as it allows arbitrary path traversal. +**Prevention:** Instead, explicitly reject directory traversal sequences (e.g., checking for '..') and use standard path resolving methods like `Path(audio_path).resolve(strict=True)` to safely process local directories. Verify to pass automated CI vulnerability scanners (like Strix) and strictly maintain test cases. diff --git a/services/analysis-engine/src/bandscope_analysis/separation/audio_separator.py b/services/analysis-engine/src/bandscope_analysis/separation/audio_separator.py index cb65e391..8e680f69 100644 --- a/services/analysis-engine/src/bandscope_analysis/separation/audio_separator.py +++ b/services/analysis-engine/src/bandscope_analysis/separation/audio_separator.py @@ -132,7 +132,10 @@ def separate(self, audio_path: str | Path) -> AudioSeparationResult: def _resolve_audio_file(self, audio_path: str | Path) -> Path: """Normalize and validate the selected source path.""" - candidate = Path(audio_path).expanduser() + audio_path_str = str(audio_path) + if ".." in audio_path_str: + raise ValueError(f"Path traversal detected in audio file path: {audio_path_str}") + candidate = Path(audio_path) try: path = candidate.resolve(strict=True) except FileNotFoundError as error: @@ -215,7 +218,12 @@ def _load_model_profile(self) -> dict[str, float]: expected_sha256 = _BANDSPLIT_PROFILE_SHA256 if self.config.model_profile_path: - profile_candidate = Path(self.config.model_profile_path).expanduser() + profile_path_str = str(self.config.model_profile_path) + if ".." in profile_path_str: + raise ValueError( + f"Path traversal detected in model profile path: {profile_path_str}" + ) + profile_candidate = Path(self.config.model_profile_path) try: profile_path = profile_candidate.resolve(strict=True) except FileNotFoundError as error: diff --git a/services/analysis-engine/tests/test_separation.py b/services/analysis-engine/tests/test_separation.py index 81e27cc1..a6611bbf 100644 --- a/services/analysis-engine/tests/test_separation.py +++ b/services/analysis-engine/tests/test_separation.py @@ -207,6 +207,26 @@ def test_audio_stem_separator_assigns_boundary_frequency_to_drums_only() -> None assert vocal_peak < drum_peak * 0.001 +def test_audio_stem_separator_rejects_path_traversal_in_audio_path() -> None: + """Ensure path traversal attempts in the audio path are strictly rejected.""" + separator = AudioStemSeparator(AudioSeparationConfig(target_sample_rate=8_000)) + + with pytest.raises(ValueError, match="Path traversal detected in audio file path"): + separator.separate("../../../etc/passwd") + + +def test_audio_stem_separator_rejects_path_traversal_in_model_profile() -> None: + """Ensure path traversal attempts in the model profile path are strictly rejected.""" + config = AudioSeparationConfig( + target_sample_rate=8_000, + model_profile_path="../../../etc/passwd", + model_profile_sha256="fake_sha", + ) + + with pytest.raises(ValueError, match="Path traversal detected in model profile path"): + AudioStemSeparator(config) + + def test_audio_stem_separator_rejects_missing_audio_file(tmp_path) -> None: """Ensure missing local files fail before decode without leaking a full path.""" separator = AudioStemSeparator(AudioSeparationConfig(target_sample_rate=8_000))