Skip to content

🛡️ Sentinel: [CRITICAL] Restrict ast.Call in type string evaluation#355

Open
bashandbone wants to merge 1 commit into
mainfrom
sentinel-fix-eval-ast-call-9014954985282109260
Open

🛡️ Sentinel: [CRITICAL] Restrict ast.Call in type string evaluation#355
bashandbone wants to merge 1 commit into
mainfrom
sentinel-fix-eval-ast-call-9014954985282109260

Conversation

@bashandbone
Copy link
Copy Markdown
Contributor

@bashandbone bashandbone commented May 20, 2026

Fixed an Arbitrary Code Execution vulnerability in _safe_eval_type where generic ast.Call nodes were allowed to execute during dynamic type evaluation.

🚨 Severity: CRITICAL
💡 Vulnerability: The AST validation logic meant to ensure safety before running eval() on type strings allowed unrestricted ast.Call nodes. This could allow an attacker to execute arbitrary code (e.g., os.system(...)) via functions available in the evaluation context.
🎯 Impact: Arbitrary code execution when parsing malicious user-provided type strings or configuration.
🔧 Fix: Modified TypeValidator to strictly limit ast.Call instances to the safe Depends function.
✅ Verification: Ran unit tests and the AST validation is now strict.


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

Summary by Sourcery

Harden AST-based validation used in type string evaluation to prevent arbitrary code execution and document the newly discovered vulnerability and its mitigation.

Bug Fixes:

  • Block all function calls in type string AST validation except for the explicitly allowed Depends() helper to close an arbitrary code execution vulnerability.

Documentation:

  • Extend the Sentinel security log with details of the ast.Call-based arbitrary code execution vulnerability, lessons learned, and prevention guidance.

Fixed a vulnerability in `src/codeweaver/core/di/container.py` where the AST validator for type string evaluation allowed generic `ast.Call` nodes. This permitted arbitrary code execution during the subsequent `eval()` call if an attacker supplied a malicious string executing available functions in the environment (e.g., `os.system`). The fix restricts `ast.Call` nodes exclusively to the `Depends` function.
Also updated `.jules/sentinel.md` with learnings on this vulnerability.

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 20, 2026 17:33
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 20, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Tightens the AST-based type string validator to forbid all function calls except the DI helper Depends(), preventing arbitrary code execution during _safe_eval_type, and documents the security issue and fix in the Sentinel security log.

File-Level Changes

Change Details Files
Harden AST validation for type-string evaluation to only allow Depends() calls and reject all other ast.Call nodes to prevent arbitrary code execution.
  • Extend the TypeValidator.generic_visit implementation to check for ast.Call nodes.
  • Add a guard that only allows ast.Call where the callee is a simple ast.Name with identifier 'Depends'.
  • Raise a TypeError for any other call expression encountered during validation before eval() is executed.
src/codeweaver/core/di/container.py
Document the newly discovered arbitrary code execution vulnerability and its remediation in the Sentinel security log.
  • Add an entry describing how unrestricted ast.Call in _safe_eval_type enabled arbitrary code execution when evaluating type strings.
  • Record the security learning about whitelisting allowed calls in AST validators.
  • Describe prevention guidance for future dynamic-eval patterns.
.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

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 1 issue, and left some high level feedback:

  • In the ast.Call restriction, consider including the offending function name in the error message (e.g., Forbidden call to {node.func.id}: only Depends() is allowed) to make debugging and log analysis easier when a disallowed call is encountered.
  • If you anticipate allowing more safe helpers in future, it may be clearer to define an explicit ALLOWED_CALLS = {"Depends"} whitelist and check membership rather than inlining a single-name check, so the policy is easy to audit and extend.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `ast.Call` restriction, consider including the offending function name in the error message (e.g., `Forbidden call to {node.func.id}: only Depends() is allowed`) to make debugging and log analysis easier when a disallowed call is encountered.
- If you anticipate allowing more safe helpers in future, it may be clearer to define an explicit `ALLOWED_CALLS = {"Depends"}` whitelist and check membership rather than inlining a single-name check, so the policy is easy to audit and extend.

## Individual Comments

### Comment 1
<location path="src/codeweaver/core/di/container.py" line_range="140-141" />
<code_context>
                     raise TypeError(f"Forbidden dunder attribute: {node.attr}")

+                # Security: Restrict calls to prevent arbitrary code execution during type evaluation
+                if isinstance(node, ast.Call) and (
+                    not isinstance(node.func, ast.Name) or node.func.id != "Depends"
+                ):
+                    raise TypeError("Forbidden call type: only Depends() is allowed")
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider allowing attribute-based Depends calls (e.g. fastapi.Depends) if those are expected in annotations.

Right now this only permits calls where `node.func` is a bare `Name` with id `"Depends"`. Any use of `fastapi.Depends(...)` or `some_module.Depends(...)` will now raise a `TypeError`, which is a breaking change despite being semantically equivalent. If you want to keep those working, consider also allowing `ast.Attribute` nodes with `attr == "Depends"` (optionally constraining `value` to approved modules) while still rejecting arbitrary callables.
</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 +140 to +141
if isinstance(node, ast.Call) and (
not isinstance(node.func, ast.Name) or node.func.id != "Depends"
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.

issue (bug_risk): Consider allowing attribute-based Depends calls (e.g. fastapi.Depends) if those are expected in annotations.

Right now this only permits calls where node.func is a bare Name with id "Depends". Any use of fastapi.Depends(...) or some_module.Depends(...) will now raise a TypeError, which is a breaking change despite being semantically equivalent. If you want to keep those working, consider also allowing ast.Attribute nodes with attr == "Depends" (optionally constraining value to approved modules) while still rejecting arbitrary callables.

@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

@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 Arbitrary Code Execution Fix

I've reviewed PR #355 which addresses a critical arbitrary code execution vulnerability in the dependency injection container's type string evaluation logic.

Summary

This PR fixes a severe security vulnerability where the AST validator in _safe_eval_type (src/codeweaver/core/di/container.py:87-171) allowed arbitrary function calls through ast.Call nodes, potentially enabling remote code execution.

Changes Reviewed

File: src/codeweaver/core/di/container.py

  • Lines 139-143: Added critical security check that restricts ast.Call nodes to only the Depends function
  • This prevents arbitrary code execution by blocking calls like os.system(...), eval(...), etc.

File: .jules/sentinel.md

  • Lines 8-11: Documented the vulnerability, impact, and prevention strategies
  • Provides valuable learning for future security reviews

Security Analysis

Vulnerability Assessment:
Correctly Identified: The vulnerability was real - generic ast.Call nodes in eval contexts are extremely dangerous
Appropriate Severity: CRITICAL rating is justified given the arbitrary code execution potential
Targeted Fix: The solution is precise and surgical

Fix Quality:
Defense in Depth: The fix adds explicit whitelisting on top of existing AST node type restrictions
Minimal Attack Surface: Only allows the safe Depends function, which is necessary for the DI framework
Proper Validation: Checks both that the node is an ast.Name and that the id is exactly "Depends"

Potential Attack Vector (now blocked):

# Before the fix, a malicious type string like:
"os.system('rm -rf /')"
# Could have been evaluated if 'os' was in the global namespace

Code Quality

Strengths:

  • Clear, descriptive security comment explaining the rationale
  • Follows existing code style and patterns
  • Minimal change footprint (6 lines added)
  • Well-documented in sentinel.md for future reference

Observations:

  • The fix integrates seamlessly with existing AST validation logic (lines 114-137)
  • Maintains compatibility with legitimate Depends() usage in type annotations
  • The error message "Forbidden call type: only Depends() is allowed" is clear and actionable

Testing Considerations

⚠️ Missing Test Coverage: I did not find any unit tests specifically for this security fix. While the PR description mentions "Ran unit tests and the AST validation is now strict," I recommend adding explicit test cases such as:

def test_safe_eval_type_blocks_arbitrary_calls():
    container = Container()
    # Should reject os.system
    assert container._safe_eval_type("os.system('echo test')", globals()) is None
    # Should reject eval
    assert container._safe_eval_type("eval('1+1')", globals()) is None
    # Should allow Depends
    assert container._safe_eval_type("Depends()", globals()) is not None

CI Status

  • ✅ Lint/Format: Passed
  • ✅ CodeQL: Passed
  • ✅ Socket Security: Passed
  • ❌ Docker Build: Failed (appears to be an infrastructure issue, not related to this PR)
  • ❌ Review workflow: Failed (Gemini CLI configuration issue, not code-related)
  • ⏳ Python Tests (3.12, 3.13, 3.14): Pending

The code changes themselves look correct, but the Docker and review failures should be investigated separately.

Recommendations

  1. Add Security Tests: Create explicit unit tests that verify malicious call attempts are blocked
  2. Consider Logging: Consider adding security logging when forbidden patterns are detected (for monitoring/alerting)
  3. Documentation: Update any developer documentation about the DI system to mention these security constraints

Verdict

The security fix is sound and should be merged once:

  1. The pending Python unit tests pass
  2. Security-specific test coverage is added (recommended but not blocking)

The core vulnerability has been properly addressed with a minimal, targeted fix that follows security best practices. The documentation in sentinel.md is excellent for institutional knowledge.

Great work identifying and fixing this critical vulnerability! 🛡️


Reviewed by Claude Code - Security-focused code analysis

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 CodeWeaver’s DI container against arbitrary code execution when resolving string-based type annotations by tightening the AST validation performed before eval().

Changes:

  • Restricts ast.Call usage during _safe_eval_type() evaluation so only Depends(...) calls are permitted.
  • Documents the vulnerability and prevention guidance in .jules/sentinel.md.

Reviewed changes

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

File Description
src/codeweaver/core/di/container.py Tightens AST validation to block arbitrary function calls during type-string evaluation.
.jules/sentinel.md Adds a security incident entry describing the vulnerability, lessons learned, and prevention steps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +139 to +143
# Security: Restrict calls to prevent arbitrary code execution during type evaluation
if isinstance(node, ast.Call) and (
not isinstance(node.func, ast.Name) or node.func.id != "Depends"
):
raise TypeError("Forbidden call type: only Depends() is allowed")
Comment on lines +139 to +143
# Security: Restrict calls to prevent arbitrary code execution during type evaluation
if isinstance(node, ast.Call) and (
not isinstance(node.func, ast.Name) or node.func.id != "Depends"
):
raise TypeError("Forbidden call type: only Depends() is allowed")
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