Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +6 to +9
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment on lines +135 to +138
try:
path = candidate.resolve(strict=True)
except FileNotFoundError as error:
Expand Down Expand Up @@ -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)
Comment on lines +221 to +226
try:
profile_path = profile_candidate.resolve(strict=True)
except FileNotFoundError as error:
Expand Down
20 changes: 20 additions & 0 deletions services/analysis-engine/tests/test_separation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down