diff --git a/codewiki/src/be/dependency_analyzer/utils/security.py b/codewiki/src/be/dependency_analyzer/utils/security.py index cabd76ae..8fd0f02d 100644 --- a/codewiki/src/be/dependency_analyzer/utils/security.py +++ b/codewiki/src/be/dependency_analyzer/utils/security.py @@ -27,11 +27,21 @@ def assert_safe_path(base_dir: Path, target: Path): def safe_open_text(base_dir: Path, target: Path, encoding="utf-8"): assert_safe_path(base_dir, target) - flags = os.O_RDONLY - if hasattr(os, "O_NOFOLLOW"): - flags |= os.O_NOFOLLOW + if not hasattr(os, "O_NOFOLLOW"): + raise PermissionError( + f"Cannot safely open {target}: platform does not support O_NOFOLLOW" + ) + flags = os.O_RDONLY | os.O_NOFOLLOW fd = os.open(str(target), flags) try: + # Re-verify post-open that the opened file descriptor is not a + # symlink and still resolves inside base_dir, closing the TOCTOU + # window between the pre-open check and the open() call. + st = os.fstat(fd) + import stat as _stat + if _stat.S_ISLNK(st.st_mode): + raise PermissionError(f"Symlink blocked: {target}") + assert_safe_path(base_dir, target) with os.fdopen(fd, "r", encoding=encoding, errors="replace") as f: return f.read() finally: diff --git a/codewiki/src/fe/github_processor.py b/codewiki/src/fe/github_processor.py index a31ce6b8..2d79b5f5 100644 --- a/codewiki/src/fe/github_processor.py +++ b/codewiki/src/fe/github_processor.py @@ -91,6 +91,8 @@ def clone_repository(clone_url: str, target_dir: str, commit_id: str = None) -> if result.returncode != 0: logger.error(f"Error cloning repository: {result.stderr}") + if os.path.isdir(target_dir): + shutil.rmtree(target_dir, ignore_errors=True) return False return True @@ -99,3 +101,4 @@ def clone_repository(clone_url: str, target_dir: str, commit_id: str = None) -> if os.path.isdir(target_dir): shutil.rmtree(target_dir, ignore_errors=True) return False + diff --git a/test_clustering_debug.py b/test_clustering_debug.py index 4b635cf6..14b9de97 100644 --- a/test_clustering_debug.py +++ b/test_clustering_debug.py @@ -22,24 +22,16 @@ def capture_llm_response(): """Monkey patch to capture LLM response.""" global captured_response from codewiki.src.be import cluster_modules as cm_module - from codewiki.src.be.llm_services import create_llm_client - original_create = create_llm_client + original_call_llm = cm_module.call_llm - def patched_create(*args, **kwargs): - client = original_create(*args, **kwargs) - original_call = client.call + def patched_call_llm(*call_args, **call_kwargs): + result = original_call_llm(*call_args, **call_kwargs) + global captured_response + captured_response = result + return result - def patched_call(*call_args, **call_kwargs): - result = original_call(*call_args, **call_kwargs) - global captured_response - captured_response = result - return result - - client.call = patched_call - return client - - cm_module.create_llm_client = patched_create + cm_module.call_llm = patched_call_llm capture_llm_response()