fix(security): resolve CodeQL path-injection alerts in UI data loaders

After the relative_to() boundary check, reassign the path variable using
resolved_root / resolved_path.relative_to(resolved_root) so all subsequent
file operations use a path derived from the trusted application root rather
than the original user-supplied value. This breaks CodeQL's taint chain
(py/path-injection) while preserving identical runtime behaviour.

Fixes alerts #41, #42, #43, #44.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
TPTBusiness
2026-04-17 21:49:30 +02:00
parent f24f678713
commit 9d623f0fbb
2 changed files with 8 additions and 6 deletions
+4 -3
View File
@@ -87,12 +87,12 @@ def extract_stage(tag: str) -> str:
def get_valid_sessions(log_folder: Path, safe_root: Path | None = None) -> list[str]:
"""Get list of valid session directories, optionally validating against a safe root."""
# Validate path is within safe_root if provided
if safe_root is not None:
try:
resolved_root = safe_root.expanduser().resolve()
resolved_folder = log_folder.expanduser().resolve()
resolved_folder.relative_to(resolved_root)
# Reconstruct from the trusted root so file ops use a root-derived path.
log_folder = resolved_root / resolved_folder.relative_to(resolved_root)
except ValueError:
return []
@@ -379,7 +379,8 @@ def load_ft_session(log_path: Path, safe_root: Path | None = None) -> Session:
try:
resolved_root = safe_root.expanduser().resolve()
resolved_path = log_path.expanduser().resolve()
resolved_path.relative_to(resolved_root)
# Reconstruct from the trusted root so file ops use a root-derived path.
log_path = resolved_root / resolved_path.relative_to(resolved_root)
except ValueError:
return Session() # Return empty session if path is outside allowed root
+4 -3
View File
@@ -74,12 +74,12 @@ def extract_stage(tag: str) -> str:
def get_valid_sessions(log_folder: Path, safe_root: Path | None = None) -> list[str]:
"""Get list of valid session directories, optionally validating against a safe root."""
# Validate path is within safe_root if provided
if safe_root is not None:
try:
resolved_root = safe_root.expanduser().resolve()
resolved_folder = log_folder.expanduser().resolve()
resolved_folder.relative_to(resolved_root)
# Reconstruct from the trusted root so file ops use a root-derived path.
log_folder = resolved_root / resolved_folder.relative_to(resolved_root)
except ValueError:
return []
@@ -251,7 +251,8 @@ def load_session(log_path: Path, safe_root: Path | None = None) -> Session:
try:
resolved_root = safe_root.expanduser().resolve()
resolved_path = log_path.expanduser().resolve()
resolved_path.relative_to(resolved_root)
# Reconstruct from the trusted root so file ops use a root-derived path.
log_path = resolved_root / resolved_path.relative_to(resolved_root)
except ValueError:
return Session() # Return empty session if path is outside allowed root