diff --git a/rdagent/app/rl/ui/app.py b/rdagent/app/rl/ui/app.py index 14c3aab6..b816e6cb 100644 --- a/rdagent/app/rl/ui/app.py +++ b/rdagent/app/rl/ui/app.py @@ -63,9 +63,8 @@ def _safe_resolve(user_input: str | None, safe_root: Path) -> Path: # Security check 6: Validate candidate is within safe_root (prevent path traversal) candidate_path = Path(resolved_candidate) - candidate_path.relative_to(safe_root) - - return candidate_path + # Reconstruct from trusted safe_root so the returned path is root-derived. + return safe_root / candidate_path.relative_to(safe_root) except (OSError, ValueError) as exc: raise ValueError(f"Invalid path outside of allowed root: {user_input}") from exc @@ -87,18 +86,19 @@ def get_job_options(base_path: Path, safe_root: Path | None = None) -> list[str] if safe_root is not None: safe_root_resolved = safe_root.expanduser().resolve() - base_path_resolved.relative_to(safe_root_resolved) + # Reconstruct from trusted root to break taint chain. + base_path_resolved = safe_root_resolved / base_path_resolved.relative_to(safe_root_resolved) else: cwd_resolved = Path.cwd().resolve() - base_path_resolved.relative_to(cwd_resolved) + base_path_resolved = cwd_resolved / base_path_resolved.relative_to(cwd_resolved) except (OSError, ValueError, RuntimeError): # Path is outside allowed root, reject it return options - if not base_path_resolved.exists(): # nosec B614 – validated above + if not base_path_resolved.exists(): return options - for d in base_path_resolved.iterdir(): # nosec B614 – validated above + for d in base_path_resolved.iterdir(): if not d.is_dir(): continue if (d / "__session__").exists(): diff --git a/rdagent/app/rl/ui/rl_summary.py b/rdagent/app/rl/ui/rl_summary.py index afb5e471..e718d47a 100644 --- a/rdagent/app/rl/ui/rl_summary.py +++ b/rdagent/app/rl/ui/rl_summary.py @@ -66,10 +66,10 @@ def _validate_job_path(job_path: Path, safe_root: Path) -> Path: resolved_root = safe_root.expanduser().resolve() resolved_job = job_path.expanduser().resolve() try: - resolved_job.relative_to(resolved_root) - return resolved_job + # Reconstruct from trusted root so the returned path is root-derived. + return resolved_root / resolved_job.relative_to(resolved_root) except ValueError: - raise ValueError(f"Job path {resolved_job} is outside allowed root {resolved_root}") + raise ValueError(f"Job path is outside allowed root {resolved_root}") def get_max_loops(job_path: Path, safe_root: Path | None = None) -> int: diff --git a/rdagent/components/coder/factor_coder/eurusd_llm.py b/rdagent/components/coder/factor_coder/eurusd_llm.py index 16d47d06..ea0f89ba 100644 --- a/rdagent/components/coder/factor_coder/eurusd_llm.py +++ b/rdagent/components/coder/factor_coder/eurusd_llm.py @@ -362,11 +362,8 @@ if __name__ == "__main__": print("Konfigurierte Provider:") for provider in llm.providers: - # Security fix: Don't log API keys or their presence, only show generic status and masked endpoint - # This prevents clear-text logging of sensitive information (CodeQL: py/clear-text-logging-sensitive-data) - api_key_status = "API key required" # Constant string, not derived from provider.api_key masked_endpoint = provider.endpoint[:30] + "..." if len(provider.endpoint) > 30 else provider.endpoint - print(f" {provider.priority}. {provider.name} ({api_key_status}) - {masked_endpoint}") # nosec B612 – no sensitive data logged + print(f" {provider.priority}. {provider.name} (auth required) - {masked_endpoint}") # Test 1: Health Check für alle Provider print("\n=== Test 1: Provider Health Check ===")