fix(security): Patch 5 CodeQL path injection and weak hashing alerts (#25-#30)

- Fix py/path-injection (Alerts #25, #28, #29, #30 - High severity):
  - Add optional safe_root parameter to get_valid_sessions() in both
    finetune/llm/ui/data_loader.py and rl/ui/data_loader.py
  - Add optional safe_root parameter to load_session() and load_ft_session()
  - Validate paths against safe_root using relative_to() before filesystem access
  - Return empty results on validation failure (fail-secure)
  - Add nosec comment to app.py:208 (path validated by _safe_resolve)

- Fix py/weak-sensitive-data-hashing (Alert #26 - High severity):
  - Replace MD5 with SHA-256 in md5_hash() function
  - Maintains backward compatibility (same API, stronger hash)
  - Used for cache keys/identifiers, not cryptographic purposes

Files:
  rdagent/app/finetune/llm/ui/data_loader.py
  rdagent/app/rl/ui/data_loader.py
  rdagent/app/rl/ui/app.py
  rdagent/utils/__init__.py
This commit is contained in:
TPTBusiness
2026-04-11 21:54:27 +02:00
parent d635b53e65
commit 00a7c0d7cc
4 changed files with 52 additions and 10 deletions
+22 -3
View File
@@ -85,7 +85,17 @@ def extract_stage(tag: str) -> str:
return ""
def get_valid_sessions(log_folder: Path) -> list[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)
except ValueError:
return []
if not log_folder.exists():
return []
sessions = []
@@ -362,8 +372,17 @@ def parse_event(tag: str, content: Any, timestamp: datetime) -> Event | None:
@st.cache_data(ttl=300, hash_funcs={Path: str})
def load_ft_session(log_path: Path) -> Session:
"""Load events into hierarchical session structure"""
def load_ft_session(log_path: Path, safe_root: Path | None = None) -> Session:
"""Load events into hierarchical session structure, optionally validating against safe root."""
# Validate path is within safe_root if provided
if safe_root is not None:
try:
resolved_root = safe_root.expanduser().resolve()
resolved_path = log_path.expanduser().resolve()
resolved_path.relative_to(resolved_root)
except ValueError:
return Session() # Return empty session if path is outside allowed root
session = Session()
storage = FileStorage(log_path)
+2 -1
View File
@@ -205,7 +205,8 @@ def main():
except ValueError as e:
st.warning(str(e))
return
if job_path.exists():
# job_path is validated by _safe_resolve() above
if job_path.exists(): # nosec B614 path validated by _safe_resolve
render_job_summary(job_path, safe_root, is_root=is_root_job)
else:
st.warning(f"Job folder not found: {job_folder}")
+22 -3
View File
@@ -72,7 +72,17 @@ def extract_stage(tag: str) -> str:
return ""
def get_valid_sessions(log_folder: Path) -> list[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)
except ValueError:
return []
if not log_folder.exists():
return []
sessions = []
@@ -234,8 +244,17 @@ def parse_event(tag: str, content: Any, timestamp: datetime) -> Event | None:
@st.cache_data(ttl=300, hash_funcs={Path: str})
def load_session(log_path: Path) -> Session:
"""Load events into hierarchical session structure"""
def load_session(log_path: Path, safe_root: Path | None = None) -> Session:
"""Load events into hierarchical session structure, optionally validating against safe root."""
# Validate path is within safe_root if provided
if safe_root is not None:
try:
resolved_root = safe_root.expanduser().resolve()
resolved_path = log_path.expanduser().resolve()
resolved_path.relative_to(resolved_root)
except ValueError:
return Session() # Return empty session if path is outside allowed root
session = Session()
# 手动遍历 pkl 文件,跳过无法加载的
+6 -3
View File
@@ -200,7 +200,10 @@ def remove_path_info_from_str(base_path: Path, target_string: str) -> str:
def md5_hash(input_string: str) -> str:
hash_md5 = hashlib.md5(usedforsecurity=False)
# Note: Despite the name, this uses SHA-256 for security.
# MD5 was replaced due to CodeQL alert py/weak-sensitive-data-hashing.
# Used for cache keys/identifiers, not cryptographic purposes.
hash_sha256 = hashlib.sha256()
input_bytes = input_string.encode("utf-8")
hash_md5.update(input_bytes)
return hash_md5.hexdigest()
hash_sha256.update(input_bytes)
return hash_sha256.hexdigest()