From de4307daec5fa8d77afcb650944b297b1f2b9395 Mon Sep 17 00:00:00 2001 From: yuhsiang Date: Tue, 9 Jun 2026 23:42:19 +0800 Subject: [PATCH 1/3] fix(security): patch RCE, command injection, path traversal, and SSTI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cluster_modules: eval(LLM response) → json.loads() (CWE-95) - str_replace_editor: shell=True → list args ×2 (CWE-78) - str_replace_editor: add is_relative_to() path boundary guard (CWE-22) - template_utils: Environment → SandboxedEnvironment (SSTI) - Dockerfile: run as non-root appuser uid 1001 (CWE-250) Co-Authored-By: Claude Sonnet 4.6 --- .../src/be/agent_tools/str_replace_editor.py | 19 +++++++++++++------ codewiki/src/be/cluster_modules.py | 3 ++- codewiki/src/fe/template_utils.py | 5 +++-- docker/Dockerfile | 7 +++++++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/codewiki/src/be/agent_tools/str_replace_editor.py b/codewiki/src/be/agent_tools/str_replace_editor.py index 3d71e68d..461963de 100644 --- a/codewiki/src/be/agent_tools/str_replace_editor.py +++ b/codewiki/src/be/agent_tools/str_replace_editor.py @@ -212,9 +212,9 @@ def flake8(file_path: str) -> str: """Run flake8 on a given file and return the output as a string""" if Path(file_path).suffix != ".py": return "" - cmd = "flake8 --isolated --select=F821,F822,F831,E111,E112,E113,E999,E902 {file_path}" + cmd = ["flake8", "--isolated", "--select=F821,F822,F831,E111,E112,E113,E999,E902", file_path] # don't use capture_output because it's not compatible with python3.6 - out = subprocess.run(cmd.format(file_path=file_path), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # Use errors="replace" so non-UTF-8 bytes (e.g. GBK-encoded paths on Windows) don't crash decoding. return out.stdout.decode("utf-8", errors="replace") @@ -488,8 +488,8 @@ def view(self, path: Path, view_range: Optional[List[int]] = None): return out = subprocess.run( - rf"find {path} -maxdepth 2 -not -path '*/\.*'", - shell=True, + ["find", str(path), "-maxdepth", "2", "-not", "-path", "*/\\.*"], + shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) @@ -772,9 +772,16 @@ async def str_replace_editor( tool = EditTool(ctx.deps.registry, ctx.deps.absolute_docs_path) if working_dir == "docs": - absolute_path = str(Path(ctx.deps.absolute_docs_path) / path) + base_dir = Path(ctx.deps.absolute_docs_path).resolve() + absolute_path = str(base_dir / path) else: - absolute_path = str(Path(ctx.deps.absolute_repo_path) / path) + base_dir = Path(ctx.deps.absolute_repo_path).resolve() + absolute_path = str(base_dir / path) + + # Guard against absolute path escape (e.g. Path("/base") / "/etc/passwd" = "/etc/passwd") + resolved = Path(absolute_path).resolve() + if not resolved.is_relative_to(base_dir): + return f"Error: Path '{path}' escapes the allowed directory. Access denied." # validate command if command != "view" and working_dir == "repo": diff --git a/codewiki/src/be/cluster_modules.py b/codewiki/src/be/cluster_modules.py index 61771b2b..1e2669e7 100644 --- a/codewiki/src/be/cluster_modules.py +++ b/codewiki/src/be/cluster_modules.py @@ -1,5 +1,6 @@ from typing import List, Dict, Any, Callable, Optional from collections import defaultdict +import json import logging import traceback logger = logging.getLogger(__name__) @@ -121,7 +122,7 @@ def cluster_modules( return {} response_content = response.split("")[1].split("")[0] - module_tree = eval(response_content) + module_tree = json.loads(response_content) if not isinstance(module_tree, dict): logger.error(f"Invalid module tree format - expected dict, got {type(module_tree)}") diff --git a/codewiki/src/fe/template_utils.py b/codewiki/src/fe/template_utils.py index 4d9d7e76..0fdd544b 100644 --- a/codewiki/src/fe/template_utils.py +++ b/codewiki/src/fe/template_utils.py @@ -3,7 +3,8 @@ Template utilities for FastAPI applications using Jinja2. """ -from jinja2 import Environment, BaseLoader, select_autoescape +from jinja2 import BaseLoader, select_autoescape +from jinja2.sandbox import SandboxedEnvironment from typing import Dict, Any @@ -29,7 +30,7 @@ def render_template(template: str, context: Dict[str, Any]) -> str: Rendered HTML string """ # Create Jinja2 environment with string template - env = Environment( + env = SandboxedEnvironment( loader=StringTemplateLoader(template), autoescape=select_autoescape(['html', 'xml']), trim_blocks=True, diff --git a/docker/Dockerfile b/docker/Dockerfile index d0a70d16..ddf1de8b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -27,6 +27,10 @@ COPY README.md . # Create output directories RUN mkdir -p output/cache output/temp output/docs output/dependency_graphs +# Create non-root user and transfer /app ownership so the process can write to output +RUN useradd --system --uid 1001 --shell /sbin/nologin appuser \ + && chown -R appuser:appuser /app + # Set environment variables ENV PYTHONPATH=/app ENV PYTHONUNBUFFERED=1 @@ -38,5 +42,8 @@ EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/ || exit 1 +# Switch to non-root user +USER appuser + # Default command CMD ["python", "codewiki/run_web_app.py", "--host", "0.0.0.0", "--port", "8000"] From f4ed079a9a67361cd2ac928158f49b011b85322a Mon Sep 17 00:00:00 2001 From: yuhsiang Date: Tue, 9 Jun 2026 23:42:31 +0800 Subject: [PATCH 2/3] fix(security): bump dependency minimums to patch known CVEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitPython >=3.1.40 → ==3.1.50 (CVE-2024-22190, CVE-2026-44243/44244, GHSA-mv93-w799-cj2w) litellm >=1.77.0 → >=1.83.7 (GHSA-69x8-hrgq-fjj8, CVE-2026-42271/35029/35030) pydantic-ai >=1.0.6 → >=1.56.0 (CVE-2026-25580 SSRF) requests >=2.32.4 → >=2.33.0 (CVE-2026-25645) python-multipart >=0.0.20 → >=0.0.27 (CVE-2026-24486/40347/42561) python-dotenv >=1.1.1 → >=1.2.2 (CVE-2026-28684) Co-Authored-By: Claude Sonnet 4.6 --- pyproject.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b618d572..cf38e422 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ dependencies = [ "click>=8.1.0", "keyring>=24.0.0", - "GitPython>=3.1.40", + "GitPython==3.1.50", "Jinja2>=3.1.6", "tree-sitter>=0.23.2", "tree-sitter-language-pack>=0.8.0", @@ -40,12 +40,12 @@ dependencies = [ "tree-sitter-php>=0.23.0", "tree-sitter-kotlin>=1.1.0", "openai>=1.107.0", - "litellm>=1.77.0", + "litellm>=1.83.7", "pydantic>=2.11.7", "pydantic-settings>=2.10.1", - "pydantic-ai>=1.0.6", - "requests>=2.32.4", - "python-dotenv>=1.1.1", + "pydantic-ai>=1.56.0", + "requests>=2.33.0", + "python-dotenv>=1.2.2", "rich>=14.1.0", "networkx>=3.5", "psutil>=7.0.0", @@ -54,7 +54,7 @@ dependencies = [ "mermaid-py>=0.8.0", "fastapi>=0.116.0", "uvicorn>=0.35.0", - "python-multipart>=0.0.20", + "python-multipart>=0.0.27", "colorama>=0.4.6", "logfire>=4.1.0", "coding-agent-wrapper>=0.1.2" From ae255fbb82da4269209f3f465f88243dfb65565e Mon Sep 17 00:00:00 2001 From: yuhsiang Date: Tue, 9 Jun 2026 23:56:17 +0800 Subject: [PATCH 3/3] fix(security): regenerate requirements.txt with patched dependency versions Re-generated via `uv export --no-hashes --python 3.12` to ensure supply chain CVE fixes in pyproject.toml are reflected in the lock file. Co-Authored-By: Claude Sonnet 4.6 --- requirements.txt | 309 ++++++++++++++++++++++++----------------------- 1 file changed, 161 insertions(+), 148 deletions(-) diff --git a/requirements.txt b/requirements.txt index e2dce481..c0f1f47d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,165 +1,178 @@ -ag-ui-protocol==0.1.8 -aiodns==3.5.0 -aiohappyeyeballs==2.6.1 -aiohttp==3.12.15 +ag-ui-protocol==0.1.19 +aiofile==3.11.1 +aiohappyeyeballs==2.6.2 +aiohttp==3.14.0 aiosignal==1.4.0 +annotated-doc==0.0.4 annotated-types==0.7.0 -anthropic==0.67.0 -anyio==4.10.0 -appnope==0.1.4 -argcomplete==3.6.2 -asttokens==3.0.0 -attrs==25.3.0 -boto3==1.40.2 -botocore==1.40.2 -Brotli==1.1.0 -cachetools==5.5.2 -certifi==2025.8.3 -cffi==2.0.0 -charset-normalizer==3.4.2 -click==8.2.1 -cohere==5.18.0 +anthropic==0.106.0 +anyio==4.13.0 +argcomplete==3.6.3 +attrs==26.1.0 +authlib==1.7.2 +beartype==0.22.9 +boto3==1.43.24 +botocore==1.43.24 +cachetools==7.1.4 +caio==0.9.25 +certifi==2026.5.20 +cffi==2.0.0 ; platform_python_implementation != 'PyPy' +charset-normalizer==3.4.7 +click==8.4.1 +coding-agent-wrapper==0.1.6 +cohere==7.0.3 ; sys_platform != 'emscripten' colorama==0.4.6 -comm==0.2.3 -debugpy==1.8.15 -decorator==5.2.1 +cryptography==48.0.0 +cyclopts==4.16.1 distro==1.9.0 -eval_type_backport==0.2.2 -executing==2.2.0 -fastapi==0.116.1 -fastavro==1.12.0 -fastuuid==0.12.0 -filelock==3.18.0 -frozenlist==1.7.0 -fsspec==2025.7.0 -genai-prices==0.0.27 +dnspython==2.8.0 +docstring-parser==0.18.0 +email-validator==2.3.0 +eval-type-backport==0.4.0 +exceptiongroup==1.3.1 +executing==2.2.1 +fastapi==0.136.3 +fastavro==1.12.2 ; sys_platform != 'emscripten' +fastmcp==3.4.2 +fastmcp-slim==3.4.2 +fastuuid==0.14.0 +filelock==3.29.1 +frozenlist==1.8.0 +fsspec==2026.4.0 +genai-prices==0.0.64 gitdb==4.0.12 -GitPython==3.1.40 -google-auth==2.40.3 -google-genai==1.36.0 -googleapis-common-protos==1.70.0 -griffe==1.9.0 -groq==0.30.0 +gitpython==3.1.50 +google-auth==2.53.0 +google-genai==2.8.0 +googleapis-common-protos==1.75.0 +griffelib==2.0.2 +groq==1.4.0 +grpcio==1.81.0 h11==0.16.0 -hf-xet==1.1.5 +hf-xet==1.4.3 ; platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' httpcore==1.0.9 +httpcore2==2.3.0 httpx==0.28.1 -httpx-sse==0.4.0 -huggingface-hub==0.34.3 -keyring>=24.0.0 -idna==3.10 -importlib_metadata==8.7.0 -invoke==2.2.0 -ipykernel==6.30.1 -ipython==9.4.0 -ipython_pygments_lexers==1.1.1 -jedi==0.19.2 -Jinja2==3.1.6 -jiter==0.10.0 -jmespath==1.0.1 -jsonschema==4.25.0 -jsonschema-specifications==2025.4.1 -jupyter_client==8.6.3 -jupyter_core==5.8.1 -litellm==1.77.0 -logfire==4.1.0 -logfire-api==4.1.0 +httpx-sse==0.4.3 +httpx2==2.3.0 +huggingface-hub==1.18.0 +idna==3.18 +importlib-metadata==8.7.1 +jaraco-classes==3.4.0 +jaraco-context==6.1.2 +jaraco-functools==4.5.0 +jeepney==0.9.0 ; sys_platform == 'linux' +jinja2==3.1.6 +jiter==0.15.0 +jmespath==1.1.0 +joserfc==1.7.0 +jsonpath-python==1.1.6 +jsonref==1.1.0 +jsonschema==4.26.0 +jsonschema-path==0.5.0 +jsonschema-specifications==2025.9.1 +keyring==25.7.0 +litellm==1.87.1 +logfire==4.35.0 +logfire-api==4.35.0 loguru==0.7.3 -markdown-it-py==3.0.0 -MarkupSafe==3.0.2 -matplotlib-inline==0.1.7 -mcp==1.12.3 +markdown-it-py==4.2.0 +markupsafe==3.0.3 +mcp==1.27.2 mdurl==0.1.2 -mermaid-parser-py==0.0.2 -mermaid-py==0.8.0 -mistralai==1.9.10 -multidict==6.6.3 -nest-asyncio==1.6.0 -networkx==3.5 -nexus-rpc==1.1.0 -openai==1.107.1 -openai-agents==0.3.0 -opentelemetry-api==1.36.0 -opentelemetry-exporter-otlp-proto-common==1.36.0 -opentelemetry-exporter-otlp-proto-http==1.36.0 -opentelemetry-instrumentation==0.57b0 -opentelemetry-instrumentation-httpx==0.57b0 -opentelemetry-proto==1.36.0 -opentelemetry-sdk==1.36.0 -opentelemetry-semantic-conventions==0.57b0 -opentelemetry-util-http==0.57b0 +mermaid-parser-py==0.0.4 +mermaid-py==0.8.4 +mistralai==2.4.9 +more-itertools==11.1.0 +multidict==6.7.1 +networkx==3.6.1 +nexus-rpc==1.4.0 +openai==2.41.0 +openapi-pydantic==0.5.1 +opentelemetry-api==1.39.1 +opentelemetry-exporter-otlp-proto-common==1.39.1 +opentelemetry-exporter-otlp-proto-http==1.39.1 +opentelemetry-instrumentation==0.60b1 +opentelemetry-instrumentation-httpx==0.60b1 +opentelemetry-proto==1.39.1 +opentelemetry-sdk==1.39.1 +opentelemetry-semantic-conventions==0.60b1 +opentelemetry-util-http==0.60b1 packaging==25.0 -parso==0.8.4 -pathspec==0.12.1 -pexpect==4.9.0 -platformdirs==4.3.8 -pminit==1.2.0 -prompt_toolkit==3.0.51 -propcache==0.3.2 -protobuf==5.29.5 -psutil==7.0.0 -ptyprocess==0.7.0 -pure_eval==0.2.3 -pyasn1==0.6.1 -pyasn1_modules==0.4.2 -pycares==4.11.0 -pycparser==2.23 -pydantic==2.11.7 -pydantic-ai==1.0.6 -pydantic-ai-slim==1.0.6 -pydantic-evals==1.0.6 -pydantic-graph==1.0.6 -pydantic-settings==2.10.1 -pydantic_core==2.33.2 -Pygments==2.19.2 -pyperclip==1.9.0 +pathable==0.6.0 +platformdirs==4.10.0 +pminit==1.3.1 +prompt-toolkit==3.0.52 +propcache==0.5.2 +protobuf==6.33.6 +psutil==7.2.2 +py-key-value-aio==0.4.5 +pyasn1==0.6.3 +pyasn1-modules==0.4.2 +pycparser==3.0 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' +pydantic==2.13.4 +pydantic-ai==1.106.0 +pydantic-ai-slim==1.106.0 +pydantic-core==2.46.4 +pydantic-evals==1.106.0 +pydantic-graph==1.106.0 +pydantic-handlebars==0.2.1 +pydantic-settings==2.14.1 +pygments==2.20.0 +pyjwt==2.13.0 +pyperclip==1.11.0 +pyreadline3==3.5.6 ; sys_platform == 'win32' python-dateutil==2.9.0.post0 -python-dotenv==1.1.1 -python-multipart==0.0.20 -pythonmonkey==1.2.0 -pytz==2025.2 -PyYAML==6.0.2 -pyzmq==27.0.1 -referencing==0.36.2 -regex==2025.7.34 -requests==2.32.4 -rich==14.1.0 -rpds-py==0.26.0 -rsa==4.9.1 -s3transfer==0.13.1 +python-dotenv==1.2.2 +python-multipart==0.0.32 +pythonmonkey==1.3.1 +pywin32==312 ; sys_platform == 'win32' +pywin32-ctypes==0.2.3 ; sys_platform == 'win32' +pyyaml==6.0.3 +referencing==0.37.0 +regex==2026.5.9 +requests==2.34.2 +rich==15.0.0 +rich-rst==2.0.1 +rpds-py==2026.5.1 +s3transfer==0.18.0 +secretstorage==3.5.0 ; sys_platform == 'linux' +shellingham==1.5.4 six==1.17.0 -smmap==5.0.2 +smmap==5.0.3 sniffio==1.3.1 -sse-starlette==3.0.2 -stack-data==0.6.3 -starlette==0.47.2 -temporalio==1.17.0 -tenacity==8.5.0 -tiktoken==0.10.0 -tokenizers==0.21.4 -tornado==6.5.1 -tqdm==4.67.1 -traitlets==5.14.3 -tree-sitter==0.23.2 -tree-sitter-c==0.21.4 -tree-sitter-c-sharp==0.23.1 +sse-starlette==3.4.4 +starlette==1.2.1 +temporalio==1.28.0 +tenacity==9.1.4 +tiktoken==0.13.0 +tokenizers==0.23.1 +tqdm==4.68.1 +tree-sitter==0.25.2 +tree-sitter-c==0.24.2 +tree-sitter-c-sharp==0.23.5 tree-sitter-cpp==0.23.4 -tree-sitter-embedded-template==0.23.2 tree-sitter-java==0.23.5 -tree-sitter-javascript==0.21.4 +tree-sitter-javascript==0.25.0 tree-sitter-kotlin==1.1.0 -tree-sitter-language-pack==0.8.0 -tree-sitter-python==0.23.6 -tree-sitter-typescript==0.21.2 -tree-sitter-yaml==0.7.1 -types-protobuf==6.30.2.20250822 -types-requests==2.32.4.20250611 -typing-inspection==0.4.1 -typing_extensions==4.14.1 -urllib3==2.5.0 -uvicorn==0.35.0 -wcwidth==0.2.13 -websockets==15.0.1 -wrapt==1.17.2 -yarl==1.20.1 -zipp==3.23.0 +tree-sitter-language-pack==1.8.1 +tree-sitter-php==0.24.1 +tree-sitter-python==0.25.0 +tree-sitter-typescript==0.23.2 +truststore==0.10.4 +typer==0.25.1 +types-protobuf==6.32.1.20260221 +types-requests==2.33.0.20260518 ; sys_platform != 'emscripten' +typing-extensions==4.15.0 +typing-inspection==0.4.2 +uncalled-for==0.3.2 +urllib3==2.7.0 +uvicorn==0.49.0 +watchfiles==1.2.0 +wcwidth==0.8.0 +websockets==16.0 +win32-setctime==1.2.0 ; sys_platform == 'win32' +wrapt==1.17.3 +xai-sdk==1.15.0 +yarl==1.24.2 +zipp==4.1.0