Skip to content

πŸ›‘οΈ Sentinel: [CRITICAL] Prevent Arbitrary Code Execution in DI Container Type Resolution#356

Open
bashandbone wants to merge 1 commit into
mainfrom
sentinel-di-ace-fix-9261490303314997612
Open

πŸ›‘οΈ Sentinel: [CRITICAL] Prevent Arbitrary Code Execution in DI Container Type Resolution#356
bashandbone wants to merge 1 commit into
mainfrom
sentinel-di-ace-fix-9261490303314997612

Conversation

@bashandbone
Copy link
Copy Markdown
Contributor

@bashandbone bashandbone commented May 21, 2026

🚨 Severity: CRITICAL
πŸ’‘ Vulnerability: Found a vulnerability in _safe_eval_type in src/codeweaver/core/di/container.py where allowing generic ast.Call nodes during AST validation before an eval() call could lead to Arbitrary Code Execution (ACE) if an attacker injects a malicious function call.
🎯 Impact: An attacker could execute arbitrary code within the host machine's environment if they are able to control the string evaluated.
πŸ”§ Fix: Added a visit_Call method inside TypeValidator to strictly whitelist ast.Call nodes to specific, required functions (Depends, Field, depends, PrivateAttr).
βœ… Verification: Verified by targeted testing of security tests uv run pytest tests/di/test_container_security.py as well as full unit tests.


PR created automatically by Jules for task 9261490303314997612 started by @bashandbone

Summary by Sourcery

Harden dynamic type evaluation by restricting allowed function calls in AST validation to prevent arbitrary code execution.

Bug Fixes:

  • Prevent arbitrary code execution in _safe_eval_type by whitelisting allowed ast.Call targets in the DI container type resolution.
  • Document the newly identified AST-based eval vulnerability and its mitigation steps in the Sentinel security log.

Documentation:

  • Extend .jules/sentinel.md with details of the AST call restriction vulnerability, lessons learned, and preventive guidance.

…ner Type Resolution

This commit fixes a vulnerability in `_safe_eval_type` in `src/codeweaver/core/di/container.py` where allowing generic `ast.Call` nodes during AST validation before an `eval()` call could lead to Arbitrary Code Execution (ACE). The `ast.Call` nodes are now strictly whitelisted to specific, required functions (`Depends`, `Field`, `depends`, `PrivateAttr`). A journal entry has also been added to `.jules/sentinel.md` documenting this finding.

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

πŸ‘‹ Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a πŸ‘€ emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings May 21, 2026 17:52
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 21, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Tightens AST-based validation in the DI container’s safe type evaluation to prevent arbitrary code execution by whitelisting allowed function calls, and documents the new Sentinel finding in the security log.

File-Level Changes

Change Details Files
Harden AST validation during safe type evaluation to only allow specific whitelisted function calls.
  • Introduce a visit_Call method in the TypeValidator AST visitor to intercept all ast.Call nodes.
  • Enforce that call targets must be simple ast.Name nodes whose identifiers are one of the allowed functions: Depends, Field, depends, PrivateAttr.
  • Raise a TypeError with a descriptive message for any non-whitelisted or non-simple function call encountered during validation.
  • Delegate to generic_visit to continue recursively validating arguments and child nodes of allowed calls.
src/codeweaver/core/di/container.py
Record the discovered AST call whitelisting vulnerability and remediation in the Sentinel security notes.
  • Add a new dated Sentinel entry describing the ACE risk from generic ast.Call allowance in _safe_eval_type.
  • Document the security learning around eval()-based type parsing and the requirement for strict call whitelisting.
  • Describe prevention guidance emphasizing restricting ast.Call usage to specific framework helpers.
.jules/sentinel.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions
Copy link
Copy Markdown
Contributor

πŸ€– Hi @bashandbone, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@github-actions
Copy link
Copy Markdown
Contributor

πŸ€– I'm sorry @bashandbone, but I was unable to process your request. Please see the logs for more details.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • Consider extracting the allowed function names for visit_Call into a shared constant or class attribute so the whitelist is easier to discover, reuse, and update consistently across any future validators.
  • Right now visit_Call only permits ast.Name targets; if any legitimate usage calls these functions via attributes (e.g. di.Depends(...)), you may want to explicitly decide whether to support ast.Attribute targets and either handle or block them with a clear rationale.
  • To aid debugging and log analysis when a forbidden call is hit, consider including lineno/col_offset from the AST node in the TypeError message so callers can more easily locate the problematic type string.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider extracting the allowed function names for `visit_Call` into a shared constant or class attribute so the whitelist is easier to discover, reuse, and update consistently across any future validators.
- Right now `visit_Call` only permits `ast.Name` targets; if any legitimate usage calls these functions via attributes (e.g. `di.Depends(...)`), you may want to explicitly decide whether to support `ast.Attribute` targets and either handle or block them with a clear rationale.
- To aid debugging and log analysis when a forbidden call is hit, consider including `lineno`/`col_offset` from the AST node in the `TypeError` message so callers can more easily locate the problematic type string.

## Individual Comments

### Comment 1
<location path="src/codeweaver/core/di/container.py" line_range="141-150" />
<code_context>

                 super().generic_visit(node)

+            def visit_Call(self, node: ast.Call) -> None:
+                # Restrict ast.Call nodes to a specific whitelist to prevent Arbitrary Code Execution (ACE)
+                # vulnerabilities during dynamic type string evaluation using restricted eval.
+                if not (
+                    isinstance(node.func, ast.Name)
+                    and node.func.id in {"Depends", "Field", "depends", "PrivateAttr"}
+                ):
+                    func_name = getattr(node.func, "id", type(node.func).__name__)
+                    raise TypeError(f"Forbidden function call in type string: {func_name}")
+                self.generic_visit(node)
+
         try:
</code_context>
<issue_to_address>
**suggestion:** Consider whether this strict ast.Call whitelist is too narrow for legitimate use cases.

This only permits calls where `node.func` is an `ast.Name` in `{Depends, Field, depends, PrivateAttr}`. Attribute calls (e.g. `some_module.Depends`, `pydantic.Field`) and aliases will be rejected, as will other safe helpers you may add later. If your eval context allows qualified calls or aliases, consider either:

- Supporting `ast.Attribute` where both the base and attribute are whitelisted, or
- Centralizing the whitelist (e.g., from the evaluation environment) to avoid divergence.

Otherwise, valid type strings using qualified names or aliases may fail unexpectedly.

Suggested implementation:

```python
                super().generic_visit(node)

            def visit_Call(self, node: ast.Call) -> None:
                # Restrict ast.Call nodes to a specific whitelist to prevent Arbitrary Code Execution (ACE)
                # vulnerabilities during dynamic type string evaluation using restricted eval.
                #
                # We support both bare names (e.g. Depends, Field) and qualified attributes
                # (e.g. some_module.Depends, pydantic.Field) as long as the fully-qualified
                # call target is in the explicit whitelist below.
                allowed_call_targets = {
                    "Depends",
                    "Field",
                    "depends",
                    "PrivateAttr",
                    # qualified / attribute-call variants that are considered safe in the eval env
                    "fastapi.Depends",
                    "fastapi.params.Depends",
                    "pydantic.Field",
                    "pydantic.fields.Field",
                    "pydantic.PrivateAttr",
                }

                def _call_target_name(func: ast.expr) -> str:
                    """Resolve a function expression to a dotted name for whitelisting."""
                    # Bare name: Depends
                    if isinstance(func, ast.Name):
                        return func.id
                    # Attribute chain: pydantic.Field, fastapi.params.Depends, etc.
                    parts = []
                    current = func
                    while isinstance(current, ast.Attribute):
                        parts.append(current.attr)
                        current = current.value
                    if isinstance(current, ast.Name):
                        parts.append(current.id)
                        return ".".join(reversed(parts))
                    # Fallback: return the node type name for error reporting
                    return type(func).__name__

                target_name = _call_target_name(node.func)
                if target_name not in allowed_call_targets:
                    raise TypeError(f"Forbidden function call in type string: {target_name}")

                self.generic_visit(node)

```

To fully align this with your evaluation environment and avoid whitelist divergence:

1. Consider extracting `allowed_call_targets` into a shared constant (e.g., `SAFE_TYPE_EVAL_CALL_TARGETS`) that is also used when constructing the eval context/globals, and import/use that constant here instead of duplicating the set.
2. Adjust the contents of `allowed_call_targets` to exactly mirror the symbols and qualified names you expose in the restricted eval environment (for example, if you expose `Depends` under a different alias or only as `fastapi.Depends`, update the set accordingly).
3. If you introduce new safe helper functions for type strings later, add their fully-qualified names to this shared whitelist rather than modifying this visitor in multiple places.
</issue_to_address>

### Comment 2
<location path="src/codeweaver/core/di/container.py" line_range="148-149" />
<code_context>
+                    isinstance(node.func, ast.Name)
+                    and node.func.id in {"Depends", "Field", "depends", "PrivateAttr"}
+                ):
+                    func_name = getattr(node.func, "id", type(node.func).__name__)
+                    raise TypeError(f"Forbidden function call in type string: {func_name}")
+                self.generic_visit(node)
+
</code_context>
<issue_to_address>
**suggestion:** Improve the reported function name for non-ast.Name calls to aid debugging.

When `node.func` is not an `ast.Name`, `func_name` will end up as the node class name (e.g. `'Attribute'`), which isn’t very helpful. Consider:

- Special-casing `ast.Attribute` to use `ast.unparse(node.func)` (Py3.9+) or manually build `value.attr`, and
- Only falling back to the node type name when no better identifier is available.

That will make the `TypeError` for forbidden calls more informative.

Suggested implementation:

```python
                if not (
                    isinstance(node.func, ast.Name)
                    and node.func.id in {"Depends", "Field", "depends", "PrivateAttr"}
                ):
                    if isinstance(node.func, ast.Name):
                        func_name = node.func.id
                    elif isinstance(node.func, ast.Attribute):
                        # Prefer ast.unparse when available (Python 3.9+)
                        if hasattr(ast, "unparse"):
                            func_name = ast.unparse(node.func)
                        else:
                            # Manually reconstruct the attribute chain (e.g. module.submodule.func)
                            attrs: list[str] = []
                            current = node.func
                            while isinstance(current, ast.Attribute):
                                attrs.append(current.attr)
                                current = current.value
                            if isinstance(current, ast.Name):
                                attrs.append(current.id)
                            attrs.reverse()
                            func_name = ".".join(attrs) if attrs else type(node.func).__name__
                    else:
                        func_name = type(node.func).__name__

```

From the context you provided, the `TypeError` raising line is not visible. Ensure that immediately after the new `func_name` block you still have:

```python
raise TypeError(f"Forbidden function call in type string: {func_name}")
```

and that `self.generic_visit(node)` is called after the `if not (...)` block so that allowed calls are still traversed:

```python
if not (...):
    ...
    raise TypeError(...)
self.generic_visit(node)
```

No additional imports should be necessary as `ast` is already used in this file.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click πŸ‘ or πŸ‘Ž on each comment and I'll use the feedback to improve your reviews.

Comment on lines +141 to +150
def visit_Call(self, node: ast.Call) -> None:
# Restrict ast.Call nodes to a specific whitelist to prevent Arbitrary Code Execution (ACE)
# vulnerabilities during dynamic type string evaluation using restricted eval.
if not (
isinstance(node.func, ast.Name)
and node.func.id in {"Depends", "Field", "depends", "PrivateAttr"}
):
func_name = getattr(node.func, "id", type(node.func).__name__)
raise TypeError(f"Forbidden function call in type string: {func_name}")
self.generic_visit(node)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider whether this strict ast.Call whitelist is too narrow for legitimate use cases.

This only permits calls where node.func is an ast.Name in {Depends, Field, depends, PrivateAttr}. Attribute calls (e.g. some_module.Depends, pydantic.Field) and aliases will be rejected, as will other safe helpers you may add later. If your eval context allows qualified calls or aliases, consider either:

  • Supporting ast.Attribute where both the base and attribute are whitelisted, or
  • Centralizing the whitelist (e.g., from the evaluation environment) to avoid divergence.

Otherwise, valid type strings using qualified names or aliases may fail unexpectedly.

Suggested implementation:

                super().generic_visit(node)

            def visit_Call(self, node: ast.Call) -> None:
                # Restrict ast.Call nodes to a specific whitelist to prevent Arbitrary Code Execution (ACE)
                # vulnerabilities during dynamic type string evaluation using restricted eval.
                #
                # We support both bare names (e.g. Depends, Field) and qualified attributes
                # (e.g. some_module.Depends, pydantic.Field) as long as the fully-qualified
                # call target is in the explicit whitelist below.
                allowed_call_targets = {
                    "Depends",
                    "Field",
                    "depends",
                    "PrivateAttr",
                    # qualified / attribute-call variants that are considered safe in the eval env
                    "fastapi.Depends",
                    "fastapi.params.Depends",
                    "pydantic.Field",
                    "pydantic.fields.Field",
                    "pydantic.PrivateAttr",
                }

                def _call_target_name(func: ast.expr) -> str:
                    """Resolve a function expression to a dotted name for whitelisting."""
                    # Bare name: Depends
                    if isinstance(func, ast.Name):
                        return func.id
                    # Attribute chain: pydantic.Field, fastapi.params.Depends, etc.
                    parts = []
                    current = func
                    while isinstance(current, ast.Attribute):
                        parts.append(current.attr)
                        current = current.value
                    if isinstance(current, ast.Name):
                        parts.append(current.id)
                        return ".".join(reversed(parts))
                    # Fallback: return the node type name for error reporting
                    return type(func).__name__

                target_name = _call_target_name(node.func)
                if target_name not in allowed_call_targets:
                    raise TypeError(f"Forbidden function call in type string: {target_name}")

                self.generic_visit(node)

To fully align this with your evaluation environment and avoid whitelist divergence:

  1. Consider extracting allowed_call_targets into a shared constant (e.g., SAFE_TYPE_EVAL_CALL_TARGETS) that is also used when constructing the eval context/globals, and import/use that constant here instead of duplicating the set.
  2. Adjust the contents of allowed_call_targets to exactly mirror the symbols and qualified names you expose in the restricted eval environment (for example, if you expose Depends under a different alias or only as fastapi.Depends, update the set accordingly).
  3. If you introduce new safe helper functions for type strings later, add their fully-qualified names to this shared whitelist rather than modifying this visitor in multiple places.

Comment on lines +148 to +149
func_name = getattr(node.func, "id", type(node.func).__name__)
raise TypeError(f"Forbidden function call in type string: {func_name}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Improve the reported function name for non-ast.Name calls to aid debugging.

When node.func is not an ast.Name, func_name will end up as the node class name (e.g. 'Attribute'), which isn’t very helpful. Consider:

  • Special-casing ast.Attribute to use ast.unparse(node.func) (Py3.9+) or manually build value.attr, and
  • Only falling back to the node type name when no better identifier is available.

That will make the TypeError for forbidden calls more informative.

Suggested implementation:

                if not (
                    isinstance(node.func, ast.Name)
                    and node.func.id in {"Depends", "Field", "depends", "PrivateAttr"}
                ):
                    if isinstance(node.func, ast.Name):
                        func_name = node.func.id
                    elif isinstance(node.func, ast.Attribute):
                        # Prefer ast.unparse when available (Python 3.9+)
                        if hasattr(ast, "unparse"):
                            func_name = ast.unparse(node.func)
                        else:
                            # Manually reconstruct the attribute chain (e.g. module.submodule.func)
                            attrs: list[str] = []
                            current = node.func
                            while isinstance(current, ast.Attribute):
                                attrs.append(current.attr)
                                current = current.value
                            if isinstance(current, ast.Name):
                                attrs.append(current.id)
                            attrs.reverse()
                            func_name = ".".join(attrs) if attrs else type(node.func).__name__
                    else:
                        func_name = type(node.func).__name__

From the context you provided, the TypeError raising line is not visible. Ensure that immediately after the new func_name block you still have:

raise TypeError(f"Forbidden function call in type string: {func_name}")

and that self.generic_visit(node) is called after the if not (...) block so that allowed calls are still traversed:

if not (...):
    ...
    raise TypeError(...)
self.generic_visit(node)

No additional imports should be necessary as ast is already used in this file.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the DI container’s string-based type resolution by tightening AST validation before a restricted eval(), preventing arbitrary function invocation during type parsing.

Changes:

  • Added a dedicated TypeValidator.visit_Call to whitelist only specific callables (Depends, Field, depends, PrivateAttr) during _safe_eval_type AST validation.
  • Documented the security learning/prevention in .jules/sentinel.md.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/codeweaver/core/di/container.py Restricts ast.Call nodes to an explicit whitelist during safe type-string evaluation to reduce ACE risk.
.jules/sentinel.md Records the vulnerability, lesson learned, and prevention guidance for the AST/eval hardening.

πŸ’‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Copy Markdown
Contributor

Security Review - PR #356

Summary

This PR addresses a CRITICAL security vulnerability in the dependency injection container's type resolution system. The fix properly restricts nodes during AST validation to prevent arbitrary code execution (ACE) vulnerabilities.

Code Quality Assessment βœ…

What Was Fixed

The vulnerability was in _safe_eval_type method in src/codeweaver/core/di/container.py:87-176. The existing AST validation allowed generic ast.Call nodes, which could permit execution of arbitrary callables if an attacker could control the type string being evaluated.

The Fix

Added a visit_Call method (lines 141-150) that whitelists only specific, required functions:

  • Depends
  • Field
  • depends
  • PrivateAttr

Any other function calls are rejected with a descriptive TypeError.

Security Analysis πŸ›‘οΈ

Strengths:

  1. Defense in Depth: This adds an additional layer on top of existing dunder blocking (lines 134-137)
  2. Fail-Safe: Returns None on validation failure rather than proceeding unsafely
  3. Minimal Attack Surface: Whitelist approach is more secure than blacklist
  4. Well-Tested: All 5 security tests pass, including test_malicious_type_resolution
  5. Properly Documented: Sentinel log updated with vulnerability details, learning, and prevention guidance

Implementation Notes:

  • The whitelist is appropriately restrictive for the DI container's use case
  • The error message includes the forbidden function name for debugging
  • Correctly calls self.generic_visit(node) to continue traversing child nodes

Testing βœ…

All security tests pass:

  • test_safe_type_resolution - Validates legitimate type strings work
  • test_malicious_type_resolution - Blocks 5 different attack vectors including:
    • __import__('os').system(...)
    • eval(...)
    • getattr(...)
    • Lambda expressions
  • test_dunder_blocking - Verifies dunder prevention still works
  • test_safe_builtins_resolution - Basic types resolve correctly
  • test_type_builtin_not_exploitable - type() cannot be abused

Potential Considerations

  1. Whitelist Maintenance: If legitimate use cases require additional functions, the whitelist in line 146 will need updates. Consider documenting this in code comments.

  2. Case Sensitivity: The current check uses exact string matching (node.func.id in {"Depends", ...}). This is correct for Python, but worth noting.

  3. Attribute Calls: The validation only checks isinstance(node.func, ast.Name). Attribute-based calls like module.func() would be rejected. This appears intentional and correct for this security context.

CI Status Note

Two checks failed, but both are infrastructure-related, not code-related:

  1. Build Docker Image: Failed during Docker build (unrelated to code changes)
  2. review / review: Failed due to Gemini CLI trust configuration ("not running in a trusted directory")

Passing checks:

  • βœ… Lint / Lint and Format (1m41s)
  • βœ… All CodeQL security scans
  • βœ… Socket Security
  • βœ… Sourcery review

Python tests (3.12, 3.13, 3.14) are pending but security tests pass locally.

Recommendation

APPROVE βœ…

This is a well-implemented security fix that:

  • Addresses a critical vulnerability effectively
  • Uses security best practices (whitelist, fail-safe)
  • Includes comprehensive test coverage
  • Documents the issue properly in Sentinel logs
  • Has no impact on legitimate use cases

The failing CI checks are infrastructure issues unrelated to the code changes. Once Python tests complete successfully, this PR should be merged promptly given the critical severity.

Additional Context

The vulnerability was correctly identified and the fix follows security hardening principles. The documentation in .jules/sentinel.md provides valuable learning for the team about AST validation risks when using eval().

Risk Assessment: High severity vulnerability, low-risk fix with no breaking changes to legitimate functionality.

@github-actions
Copy link
Copy Markdown
Contributor

Security Review - PR #356

Summary

This PR addresses a CRITICAL security vulnerability in the dependency injection container's type resolution system. The fix properly restricts ast.Call nodes during AST validation to prevent arbitrary code execution (ACE) vulnerabilities.

Code Quality Assessment βœ…

What Was Fixed

The vulnerability was in _safe_eval_type method in src/codeweaver/core/di/container.py:87-176. The existing AST validation allowed generic ast.Call nodes, which could permit execution of arbitrary callables if an attacker could control the type string being evaluated.

The Fix

Added a visit_Call method (lines 141-150) that whitelists only specific, required functions:

  • Depends
  • Field
  • depends
  • PrivateAttr

Any other function calls are rejected with a descriptive TypeError.

Security Analysis πŸ›‘οΈ

Strengths:

  1. Defense in Depth: This adds an additional layer on top of existing dunder blocking (lines 134-137)
  2. Fail-Safe: Returns None on validation failure rather than proceeding unsafely
  3. Minimal Attack Surface: Whitelist approach is more secure than blacklist
  4. Well-Tested: All 5 security tests pass, including test_malicious_type_resolution
  5. Properly Documented: Sentinel log updated with vulnerability details, learning, and prevention guidance

Implementation Notes:

  • The whitelist is appropriately restrictive for the DI container's use case
  • The error message includes the forbidden function name for debugging
  • Correctly calls self.generic_visit(node) to continue traversing child nodes

Testing βœ…

All security tests pass:

  • test_safe_type_resolution - Validates legitimate type strings work
  • test_malicious_type_resolution - Blocks 5 different attack vectors
  • test_dunder_blocking - Verifies dunder prevention still works
  • test_safe_builtins_resolution - Basic types resolve correctly
  • test_type_builtin_not_exploitable - type() cannot be abused

Potential Considerations

  1. Whitelist Maintenance: If legitimate use cases require additional functions, the whitelist in line 146 will need updates.

  2. Case Sensitivity: The current check uses exact string matching. This is correct for Python.

  3. Attribute Calls: The validation only checks isinstance(node.func, ast.Name). Attribute-based calls like module.func() would be rejected. This appears intentional and correct.

CI Status Note

Two checks failed, but both are infrastructure-related, not code-related:

  1. Build Docker Image: Failed during Docker build (unrelated to code changes)
  2. review / review: Failed due to Gemini CLI trust configuration

Passing checks:

  • βœ… Lint / Lint and Format (1m41s)
  • βœ… All CodeQL security scans
  • βœ… Socket Security
  • βœ… Sourcery review

Recommendation

APPROVE βœ…

This is a well-implemented security fix that:

  • Addresses a critical vulnerability effectively
  • Uses security best practices (whitelist, fail-safe)
  • Includes comprehensive test coverage
  • Documents the issue properly in Sentinel logs
  • Has no impact on legitimate use cases

The failing CI checks are infrastructure issues unrelated to the code changes. Once Python tests complete successfully, this PR should be merged promptly given the critical severity.

Risk Assessment: High severity vulnerability, low-risk fix with no breaking changes to legitimate functionality.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review - CRITICAL Vulnerability Fix

Summary

This PR addresses a critical security vulnerability in the DI container's type resolution system. The fix properly restricts nodes during AST validation to prevent Arbitrary Code Execution (ACE).

Vulnerability Analysis βœ…

The Problem:
The _safe_eval_type method in src/codeweaver/core/di/container.py:141-150 previously allowed generic ast.Call nodes during AST validation before eval(). This created an ACE vulnerability where an attacker could inject malicious function calls if they controlled the type string being evaluated.

The Fix:
Added a visit_Call method that whitelists only specific, required functions:

  • Depends, Field, depends, PrivateAttr

Any other function call now raises a TypeError and returns None, preventing execution.

Code Quality Review βœ…

Strengths:

  1. Defense in Depth: This fix adds another layer of protection beyond the existing dunder blocking
  2. Minimal Whitelist: Only allows the exact functions needed for legitimate type annotations
  3. Clear Error Messages: Provides helpful error messages when blocked (line 149)
  4. Proper Documentation: The inline comment clearly explains the security rationale
  5. Well-Tested: Security tests verify both allowed and blocked scenarios

Implementation Quality:

  • Clean, focused change (11 lines added, 0 deleted)
  • Follows existing code style and patterns
  • Properly integrated into the existing TypeValidator visitor class
  • Uses self.generic_visit(node) to continue traversing child nodes

Test Coverage βœ…

I verified the security tests in tests/di/test_container_security.py:

Passing Tests:

  • βœ… test_safe_type_resolution - Valid types still resolve correctly
  • βœ… test_malicious_type_resolution - Malicious strings are blocked
  • βœ… test_dunder_blocking - Dunder access is prevented
  • βœ… test_safe_builtins_resolution - Builtins work without globals
  • βœ… test_type_builtin_not_exploitable - type() cannot be exploited

Additional Manual Testing:
I verified the whitelist works correctly:

  • βœ… All 4 whitelisted functions (Depends, Field, depends, PrivateAttr) are allowed
  • βœ… All dangerous functions (open, eval, exec, compile, __import__, getattr, type) are blocked

Documentation βœ…

The .jules/sentinel.md documentation is excellent:

  • Clear description of the vulnerability
  • Explains the learning and prevention strategy
  • Will serve as valuable reference for future security reviews

Recommendations

Minor Enhancement (Optional):
Consider adding a dedicated test case to test_container_security.py that explicitly tests the new visit_Call whitelist:

def test_call_whitelist() -> None:
    """Verify that only whitelisted function calls are allowed."""
    container = Container()
    globalns = {
        'Annotated': Annotated,
        'Depends': Depends,
        'Field': lambda: None,
        'int': int,
        'open': open,
        'eval': eval,
    }
    
    # Whitelisted calls should work
    assert container._resolve_string_type('Annotated[int, Depends()]', globalns) is not None
    assert container._resolve_string_type('Annotated[int, Field()]', globalns) is not None
    
    # Non-whitelisted calls should be blocked
    assert container._resolve_string_type('open("file")', globalns) is None
    assert container._resolve_string_type('eval("1+1")', globalns) is None

However, the existing test coverage is already quite comprehensive.

CI Status ⚠️

I noticed two failing CI checks:

  • ❌ Build Docker Image
  • ❌ review / review

These appear to be unrelated to the security fix itself (the tests are passing). The failures might be due to:

  1. Transient CI infrastructure issues
  2. Unrelated build configuration problems

I recommend:

  1. Checking the CI logs for the specific error messages
  2. Re-running the failed jobs to see if they're transient failures
  3. If the failures persist, they should be addressed separately as they don't impact the security fix

Verdict

APPROVED βœ…

This is a critical security fix that:

  • βœ… Properly addresses the ACE vulnerability
  • βœ… Implements a minimal, focused solution
  • βœ… Includes comprehensive test coverage
  • βœ… Follows security best practices (defense in depth, least privilege)
  • βœ… Is well-documented for future reference

The code quality is excellent, the fix is sound, and the security impact is properly mitigated. I strongly recommend merging this PR once the CI issues are resolved.

Security Impact: This fix prevents potential arbitrary code execution if an attacker can control type strings being evaluated. While the attack vector requires specific conditions (attacker controlling type annotations), the fix properly closes this vulnerability.

Great work on identifying and fixing this security issue! πŸ›‘οΈ

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review - CRITICAL Vulnerability Fix

Summary

This PR addresses a critical security vulnerability in the DI container's type resolution system. The fix properly restricts ast.Call nodes during AST validation to prevent Arbitrary Code Execution (ACE).

Vulnerability Analysis βœ…

The Problem:
The _safe_eval_type method in src/codeweaver/core/di/container.py:141-150 previously allowed generic ast.Call nodes during AST validation before eval(). This created an ACE vulnerability where an attacker could inject malicious function calls if they controlled the type string being evaluated.

The Fix:
Added a visit_Call method that whitelists only specific, required functions:

  • Depends, Field, depends, PrivateAttr

Any other function call now raises a TypeError and returns None, preventing execution.

Code Quality Review βœ…

Strengths:

  1. Defense in Depth: This fix adds another layer of protection beyond the existing dunder blocking
  2. Minimal Whitelist: Only allows the exact functions needed for legitimate type annotations
  3. Clear Error Messages: Provides helpful error messages when blocked (line 149)
  4. Proper Documentation: The inline comment clearly explains the security rationale
  5. Well-Tested: Security tests verify both allowed and blocked scenarios

Implementation Quality:

  • Clean, focused change (11 lines added, 0 deleted)
  • Follows existing code style and patterns
  • Properly integrated into the existing TypeValidator visitor class
  • Uses self.generic_visit(node) to continue traversing child nodes

Test Coverage βœ…

I verified the security tests in tests/di/test_container_security.py:

Passing Tests:

  • βœ… test_safe_type_resolution - Valid types still resolve correctly
  • βœ… test_malicious_type_resolution - Malicious strings are blocked
  • βœ… test_dunder_blocking - Dunder access is prevented
  • βœ… test_safe_builtins_resolution - Builtins work without globals
  • βœ… test_type_builtin_not_exploitable - type() cannot be exploited

Additional Manual Testing:
I verified the whitelist works correctly:

  • βœ… All 4 whitelisted functions (Depends, Field, depends, PrivateAttr) are allowed
  • βœ… All dangerous functions (open, eval, exec, compile, import, getattr, type) are blocked

Documentation βœ…

The .jules/sentinel.md documentation is excellent:

  • Clear description of the vulnerability
  • Explains the learning and prevention strategy
  • Will serve as valuable reference for future security reviews

CI Status ⚠️

I noticed two failing CI checks:

  • ❌ Build Docker Image
  • ❌ review / review

These appear to be unrelated to the security fix itself (the tests are passing). The failures might be due to:

  1. Transient CI infrastructure issues
  2. Unrelated build configuration problems

I recommend:

  1. Checking the CI logs for the specific error messages
  2. Re-running the failed jobs to see if they're transient failures
  3. If the failures persist, they should be addressed separately as they don't impact the security fix

Verdict

APPROVED βœ…

This is a critical security fix that:

  • βœ… Properly addresses the ACE vulnerability
  • βœ… Implements a minimal, focused solution
  • βœ… Includes comprehensive test coverage
  • βœ… Follows security best practices (defense in depth, least privilege)
  • βœ… Is well-documented for future reference

The code quality is excellent, the fix is sound, and the security impact is properly mitigated. I strongly recommend merging this PR once the CI issues are resolved.

Security Impact: This fix prevents potential arbitrary code execution if an attacker can control type strings being evaluated. While the attack vector requires specific conditions (attacker controlling type annotations), the fix properly closes this vulnerability.

Great work on identifying and fixing this security issue! πŸ›‘οΈ

@github-actions
Copy link
Copy Markdown
Contributor

This is an excellent observation about potential limitations. Let me analyze the current implementation:

Current Behavior Analysis:

Looking at the codebase, the globalns dictionary passed to _safe_eval_type typically contains bare names like Depends, Field, etc. from the imports in the module being evaluated. The current implementation correctly handles this common case.

Why the Narrow Whitelist Works Here:

  1. Actual Usage Pattern: In _get_signature_and_hints (line 749), Annotated is explicitly injected if missing: globalns["Annotated"] = Annotated. This suggests type strings in this codebase use bare names.

  2. Import Context: The type strings are evaluated in the module's namespace (_get_globalns, line 697), where these symbols are typically imported as bare names.

  3. Security First: For a security fix, starting narrow and expanding if needed is safer than starting permissive.

However, you raise valid points about maintainability:

The suggestion to support ast.Attribute and centralize the whitelist is architecturally sound. If this codebase evolves to support qualified names (e.g., pydantic.Field in type strings), the current implementation would need updating.

Recommendation:

For this critical security fix, I recommend:

  1. Merge as-is to quickly address the vulnerability
  2. Follow-up enhancement: Create a task to implement the more robust whitelisting strategy you suggested, including:
    • Support for ast.Attribute nodes
    • Centralized SAFE_TYPE_EVAL_CALL_TARGETS constant
    • Documentation of supported patterns

This approach balances urgency (fixing a critical vulnerability) with long-term code quality (your architectural improvements).

Would you like me to create a follow-up issue for the enhancement?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants