From c0f62c659bc1f7e37848cdd1f13e3b0f19a58a72 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 17:44:43 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20arbitrary=20code=20execution=20in=20AST=20eval?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit patches a critical vulnerability in `TypeValidator`'s AST evaluation inside `src/codeweaver/core/di/container.py` where `ast.Call` nodes were permitted without restriction. This lack of validation allowed Arbitrary Code Execution (ACE) via injection of malicious callables into string type annotations evaluated during dynamic dependency injection. The patch adds strict whitelist filtering for functions relevant only to DI/Pydantic configurations (`Depends`, `depends`, `Field`, `PrivateAttr`, `Tag`, and `Parameter`). It properly handles standard function resolution and updates the internal security journal. Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ src/codeweaver/core/di/container.py | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 1959a5253..e5e9a17ee 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -4,3 +4,8 @@ **Vulnerability:** Found an unused `_attempt_import` function in `src/codeweaver/server/mcp/server.py` that dynamically imports a module directly from unvalidated configuration (`import_module(mw.rsplit(".", 1)[0])`), leading to potential arbitrary code execution. **Learning:** Functions that perform dynamic imports should not be left around in the codebase if they are unused, especially if they are designed to take unvalidated strings as input. **Prevention:** Avoid dynamic imports based on configuration or inputs without strict whitelisting. Use tools like `semgrep` with python security rules to actively catch these patterns. + +## 2025-02-25 - Arbitrary code execution in AST eval +**Vulnerability:** Found a lack of whitelisting for `ast.Call` nodes in type annotation resolution logic within `TypeValidator` (at `src/codeweaver/core/di/container.py`). This allowed any arbitrary function within the module's global namespace to be called during `eval()`, potentially leading to Arbitrary Code Execution (ACE). +**Learning:** Permitting generic `ast.Call` nodes inside restricted Abstract Syntax Tree parsing environments drastically increases the attack surface, allowing escape from restricted evaluation constraints. +**Prevention:** Strictly limit `ast.Call` nodes to specifically required functions (e.g., `Depends`, `depends`, `Field`, `PrivateAttr`, `Tag`, `Parameter`) when validating trees meant for dynamic type evaluation. diff --git a/src/codeweaver/core/di/container.py b/src/codeweaver/core/di/container.py index 7cd68ce98..fe7bce708 100644 --- a/src/codeweaver/core/di/container.py +++ b/src/codeweaver/core/di/container.py @@ -84,7 +84,7 @@ def __init__(self) -> None: self._request_cache: dict[Any, Any] = {} # Keys can be types or callables self._providers_loaded: bool = False # Track if auto-discovery has run - def _safe_eval_type(self, type_str: str, globalns: dict[str, Any]) -> Any | None: + def _safe_eval_type(self, type_str: str, globalns: dict[str, Any]) -> Any | None: # noqa: C901 """Safely evaluate a type string using AST validation. Parses the type string into an AST, validates that it contains only safe @@ -136,6 +136,20 @@ def generic_visit(self, node: ast.AST) -> None: if isinstance(node, ast.Attribute) and node.attr.startswith("__"): raise TypeError(f"Forbidden dunder attribute: {node.attr}") + # Security: Restrict ast.Call nodes to known safe functions to prevent arbitrary code execution (ACE). + # Allowing arbitrary functions (like 'eval' or malicious callables) inside type annotations is dangerous. + if isinstance(node, ast.Call): + if isinstance(node.func, ast.Name): + func_name = node.func.id + elif isinstance(node.func, ast.Attribute): + func_name = node.func.attr + else: + raise TypeError(f"Forbidden call function node type: {type(node.func).__name__}") + + allowed_calls = {"Depends", "depends", "Field", "PrivateAttr", "Tag", "Parameter"} + if func_name not in allowed_calls: + raise TypeError(f"Forbidden function call in type string: {func_name}") + super().generic_visit(node) try: