🔒 Fix dangerous eval() in DI container resolution#229
🔒 Fix dangerous eval() in DI container resolution#229bashandbone wants to merge 3 commits intomainfrom
Conversation
Introduces _safe_eval_type to the Container class, which uses AST parsing and validation to ensure type strings only contain safe constructs. It blocks dunder access and evaluates in a restricted environment without builtins, preventing arbitrary code execution while preserving functionality for complex type hints. Fixes a security vulnerability in src/codeweaver/core/di/container.py. Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
|
👋 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 New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
Reviewer's GuideReplaces insecure use of eval() for DI string type resolution with a restricted AST-validated evaluator and adds security-focused tests to ensure both safe behavior and compatibility with valid type expressions. Class diagram for updated DI container type resolutionclassDiagram
class Container {
- _shutdown_hooks: list[Callable]
- _cleanup_stack: AsyncExitStack | None
- _request_cache: dict[Any, Any]
- _providers_loaded: bool
+ _safe_eval_type(type_str: str, globalns: dict[str, Any]) Any
+ _unwrap_annotated(annotation: Any) Any
+ _resolve_string_type(type_str: str, globalns: dict[str, Any]) Any
}
Container ..> Container : uses _safe_eval_type in _resolve_string_type
Flow diagram for safe AST-based type string evaluationflowchart TD
A["Start _safe_eval_type"] --> B["Parse type_str with ast.parse(mode=eval)"]
B -->|SyntaxError| C["Return None"]
B -->|Parsed successfully| D["Create TypeValidator"]
D --> E["Visit AST nodes"]
E --> F{"Node type allowed?"}
F -->|No| G["Raise ValueError Forbidden AST node"]
F -->|Yes| H{"Is ast.Name or ast.Attribute?"}
H -->|Yes and dunder| I["Raise ValueError Forbidden dunder"]
H -->|No or non-dunder| J["Continue visiting children"]
J --> E
I --> K["Exception propagates to caller"]
G --> K
E -->|All nodes validated| L["Compile AST to code object"]
L --> M["Eval code with globals {__builtins__: {}} and local globalns"]
M --> N["Return evaluated type"]
C --> O["End _safe_eval_type"]
N --> O["End _safe_eval_type"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- The current AST whitelist still allows arbitrary
ast.Call+ast.Attributecombinations (e.g.,os.system('...')) whenever such names are present inglobalns; consider either disallowingCallentirely or restricting it to a small set of explicitly allowed callables (e.g.,Depends) to actually prevent remote code execution. - Because
evalruns withglobalnsas locals, any powerful objects exposed there can be invoked via allowed nodes (e.g., plain functions or modules); it may be safer to construct a minimal, curated namespace specifically for type resolution rather than passing through the full globals. - Right now all failures in
_safe_eval_type(syntax errors, forbidden nodes, NameError, etc.) collapse intoNoneat the call site; if distinguishing between 'syntactically invalid' and 'forbidden/unsafe' cases would be useful, you might propagate a specific exception or at least log/annotate the reason before suppressing it.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The current AST whitelist still allows arbitrary `ast.Call` + `ast.Attribute` combinations (e.g., `os.system('...')`) whenever such names are present in `globalns`; consider either disallowing `Call` entirely or restricting it to a small set of explicitly allowed callables (e.g., `Depends`) to actually prevent remote code execution.
- Because `eval` runs with `globalns` as locals, any powerful objects exposed there can be invoked via allowed nodes (e.g., plain functions or modules); it may be safer to construct a minimal, curated namespace specifically for type resolution rather than passing through the full globals.
- Right now all failures in `_safe_eval_type` (syntax errors, forbidden nodes, NameError, etc.) collapse into `None` at the call site; if distinguishing between 'syntactically invalid' and 'forbidden/unsafe' cases would be useful, you might propagate a specific exception or at least log/annotate the reason before suppressing it.
## Individual Comments
### Comment 1
<location path="src/codeweaver/core/di/container.py" line_range="105-114" />
<code_context>
+ class TypeValidator(ast.NodeVisitor):
</code_context>
<issue_to_address>
**🚨 issue (security):** Current validation still allows arbitrary function calls and attribute access from `globalns`, which may undercut the "safe" aspect of `_safe_eval_type` if `type_str` is user-influenced.
Because `ast.Call` and `ast.Attribute` are allowed on anything in `globalns`, a user-controlled `type_str` could call or inspect any object exposed there (e.g., `os.system` or DI helpers), despite `__builtins__` being removed.
To harden this:
- Only allow calls to a small, explicit whitelist (e.g. `Annotated`, `Literal`, `Depends`, `Optional`, `Union`, etc.).
- Restrict attribute access to explicitly approved modules/types (e.g. `typing`, `collections.abc`) or a registry you control.
- Consider banning `ast.Call` entirely unless there is a clearly bounded, vetted set of call targets.
This would better match the “safe eval” intent and avoid arbitrary code paths via type strings.
</issue_to_address>
### Comment 2
<location path="src/codeweaver/core/di/container.py" line_range="112-129" />
<code_context>
+ ast.Attribute,
+ ast.Subscript,
+ ast.Constant,
+ ast.BinOp,
+ ast.BitOr,
+ ast.Load,
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Allowing generic `ast.BinOp` enables arbitrary binary operations in type strings, not just `|` unions.
The validator currently accepts any `ast.BinOp` without checking that the operator is `BitOr`, so expressions like `Foo + Bar` or `Foo * 2` would also be validated and evaluated, which goes beyond what’s needed for type strings.
Consider either:
- Dropping `ast.BinOp` and relying solely on `ast.BitOr`, or
- Keeping `ast.BinOp` but enforcing `isinstance(node.op, ast.BitOr)` and rejecting all other operators.
```suggestion
if not isinstance(
node,
(
ast.Expression,
ast.Name,
ast.Attribute,
ast.Subscript,
ast.Constant,
ast.BinOp,
ast.BitOr,
ast.Load,
ast.Tuple,
ast.List,
ast.Call,
ast.keyword,
),
):
raise ValueError(f"Forbidden AST node in type string: {type(node).__name__}")
# Restrict binary operations to unions using `|` only
if isinstance(node, ast.BinOp) and not isinstance(node.op, ast.BitOr):
raise ValueError(
f"Forbidden binary operator in type string: {type(node.op).__name__}"
)
```
</issue_to_address>
### Comment 3
<location path="tests/di/test_container_security.py" line_range="44-46" />
<code_context>
+ assert get_args(resolved_annotated)[0] is int
+ assert isinstance(get_args(resolved_annotated)[1], Depends)
+
+def test_malicious_type_resolution():
+ container = Container()
+ globalns = {"__name__": "__main__"}
+
+ # Malicious strings that should be blocked
+ malicious_strings = [
+ "__import__('os').system('echo VULNERABLE')",
+ "eval('1+1')",
+ "getattr(int, '__name__')",
+ "int.__class__",
+ "(lambda x: x)(1)",
+ ]
+
+ for s in malicious_strings:
+ result = container._resolve_string_type(s, globalns)
+ assert result is None, f"String '{s}' should have been blocked"
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for syntactically invalid or unknown-name type strings to assert they safely return None.
`test_malicious_type_resolution` currently covers semantically dangerous expressions but not syntactically invalid ones (e.g. `"int["`) or unknown names (e.g. `"UnknownType"`). Since `_safe_eval_type` and `_resolve_string_type` both fall back to `None` in those cases, please add a couple of assertions for invalid/unknown strings here or in a separate test to verify we don’t leak exceptions or accidentally resolve them successfully.
```suggestion
for s in malicious_strings:
result = container._resolve_string_type(s, globalns)
assert result is None, f"String '{s}' should have been blocked"
# Syntactically invalid or unknown type strings should also be safely rejected
invalid_or_unknown_strings = [
"int[", # invalid syntax
"UnknownType", # unknown name
]
for s in invalid_or_unknown_strings:
result = container._resolve_string_type(s, globalns)
assert result is None, f"Invalid or unknown string '{s}' should resolve to None"
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| class TypeValidator(ast.NodeVisitor): | ||
| def generic_visit(self, node: ast.AST) -> None: | ||
| # Allowed nodes for type annotations, including support for: | ||
| # - Generics: List[int], dict[str, Any] (Subscript, Name, Attribute) | ||
| # - Unions: int | str (BinOp, BitOr) | ||
| # - Annotated: Annotated[int, Depends(...)] (Call, keyword, Tuple, List) | ||
| # - Literals: Literal["foo"] (Constant) | ||
| if not isinstance( | ||
| node, | ||
| ( |
There was a problem hiding this comment.
🚨 issue (security): Current validation still allows arbitrary function calls and attribute access from globalns, which may undercut the "safe" aspect of _safe_eval_type if type_str is user-influenced.
Because ast.Call and ast.Attribute are allowed on anything in globalns, a user-controlled type_str could call or inspect any object exposed there (e.g., os.system or DI helpers), despite __builtins__ being removed.
To harden this:
- Only allow calls to a small, explicit whitelist (e.g.
Annotated,Literal,Depends,Optional,Union, etc.). - Restrict attribute access to explicitly approved modules/types (e.g.
typing,collections.abc) or a registry you control. - Consider banning
ast.Callentirely unless there is a clearly bounded, vetted set of call targets.
This would better match the “safe eval” intent and avoid arbitrary code paths via type strings.
| if not isinstance( | ||
| node, | ||
| ( | ||
| ast.Expression, | ||
| ast.Name, | ||
| ast.Attribute, | ||
| ast.Subscript, | ||
| ast.Constant, | ||
| ast.BinOp, | ||
| ast.BitOr, | ||
| ast.Load, | ||
| ast.Tuple, | ||
| ast.List, | ||
| ast.Call, | ||
| ast.keyword, | ||
| ), | ||
| ): | ||
| raise ValueError(f"Forbidden AST node in type string: {type(node).__name__}") |
There was a problem hiding this comment.
suggestion (bug_risk): Allowing generic ast.BinOp enables arbitrary binary operations in type strings, not just | unions.
The validator currently accepts any ast.BinOp without checking that the operator is BitOr, so expressions like Foo + Bar or Foo * 2 would also be validated and evaluated, which goes beyond what’s needed for type strings.
Consider either:
- Dropping
ast.BinOpand relying solely onast.BitOr, or - Keeping
ast.BinOpbut enforcingisinstance(node.op, ast.BitOr)and rejecting all other operators.
| if not isinstance( | |
| node, | |
| ( | |
| ast.Expression, | |
| ast.Name, | |
| ast.Attribute, | |
| ast.Subscript, | |
| ast.Constant, | |
| ast.BinOp, | |
| ast.BitOr, | |
| ast.Load, | |
| ast.Tuple, | |
| ast.List, | |
| ast.Call, | |
| ast.keyword, | |
| ), | |
| ): | |
| raise ValueError(f"Forbidden AST node in type string: {type(node).__name__}") | |
| if not isinstance( | |
| node, | |
| ( | |
| ast.Expression, | |
| ast.Name, | |
| ast.Attribute, | |
| ast.Subscript, | |
| ast.Constant, | |
| ast.BinOp, | |
| ast.BitOr, | |
| ast.Load, | |
| ast.Tuple, | |
| ast.List, | |
| ast.Call, | |
| ast.keyword, | |
| ), | |
| ): | |
| raise ValueError(f"Forbidden AST node in type string: {type(node).__name__}") | |
| # Restrict binary operations to unions using `|` only | |
| if isinstance(node, ast.BinOp) and not isinstance(node.op, ast.BitOr): | |
| raise ValueError( | |
| f"Forbidden binary operator in type string: {type(node.op).__name__}" | |
| ) |
| for s in malicious_strings: | ||
| result = container._resolve_string_type(s, globalns) | ||
| assert result is None, f"String '{s}' should have been blocked" |
There was a problem hiding this comment.
suggestion (testing): Add tests for syntactically invalid or unknown-name type strings to assert they safely return None.
test_malicious_type_resolution currently covers semantically dangerous expressions but not syntactically invalid ones (e.g. "int[") or unknown names (e.g. "UnknownType"). Since _safe_eval_type and _resolve_string_type both fall back to None in those cases, please add a couple of assertions for invalid/unknown strings here or in a separate test to verify we don’t leak exceptions or accidentally resolve them successfully.
| for s in malicious_strings: | |
| result = container._resolve_string_type(s, globalns) | |
| assert result is None, f"String '{s}' should have been blocked" | |
| for s in malicious_strings: | |
| result = container._resolve_string_type(s, globalns) | |
| assert result is None, f"String '{s}' should have been blocked" | |
| # Syntactically invalid or unknown type strings should also be safely rejected | |
| invalid_or_unknown_strings = [ | |
| "int[", # invalid syntax | |
| "UnknownType", # unknown name | |
| ] | |
| for s in invalid_or_unknown_strings: | |
| result = container._resolve_string_type(s, globalns) | |
| assert result is None, f"Invalid or unknown string '{s}' should resolve to None" |
There was a problem hiding this comment.
Pull request overview
Replaces unsafe eval() with AST-validated evaluation in the DI container's string type resolution to prevent code injection.
Changes:
- Added
_safe_eval_typemethod with AST whitelist validation and dunder blocking - Replaced both
eval()call sites in_resolve_string_typewith_safe_eval_type - Added security tests for safe type resolution, malicious inputs, and dunder blocking
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/codeweaver/core/di/container.py | New _safe_eval_type method replacing raw eval() calls |
| tests/di/test_container_security.py | Security tests for the new safe evaluation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tests/di/test_container_security.py
Outdated
|
|
||
| # Annotated with Depends | ||
| resolved_annotated = container._resolve_string_type("Annotated[int, Depends()]", globalns) | ||
| assert get_origin(resolved_annotated) is Annotated |
src/codeweaver/core/di/container.py
Outdated
| The evaluated type object. | ||
|
|
||
| Raises: | ||
| ValueError: If the type string contains forbidden constructs. | ||
| """ | ||
| try: | ||
| tree = ast.parse(type_str, mode="eval") | ||
| except SyntaxError: | ||
| return None | ||
|
|
||
| class TypeValidator(ast.NodeVisitor): | ||
| def generic_visit(self, node: ast.AST) -> None: | ||
| # Allowed nodes for type annotations, including support for: | ||
| # - Generics: List[int], dict[str, Any] (Subscript, Name, Attribute) | ||
| # - Unions: int | str (BinOp, BitOr) | ||
| # - Annotated: Annotated[int, Depends(...)] (Call, keyword, Tuple, List) | ||
| # - Literals: Literal["foo"] (Constant) | ||
| if not isinstance( | ||
| node, | ||
| ( | ||
| ast.Expression, | ||
| ast.Name, | ||
| ast.Attribute, | ||
| ast.Subscript, | ||
| ast.Constant, | ||
| ast.BinOp, | ||
| ast.BitOr, | ||
| ast.Load, | ||
| ast.Tuple, | ||
| ast.List, | ||
| ast.Call, | ||
| ast.keyword, | ||
| ), | ||
| ): | ||
| raise ValueError(f"Forbidden AST node in type string: {type(node).__name__}") | ||
|
|
||
| # Block dunder access to prevent escaping the restricted environment | ||
| if isinstance(node, ast.Name) and node.id.startswith("__"): | ||
| raise ValueError(f"Forbidden dunder name: {node.id}") | ||
| if isinstance(node, ast.Attribute) and node.attr.startswith("__"): | ||
| raise ValueError(f"Forbidden dunder attribute: {node.attr}") | ||
|
|
||
| super().generic_visit(node) | ||
|
|
||
| TypeValidator().visit(tree) | ||
|
|
||
| # Restricted eval: no builtins allowed | ||
| code = compile(tree, "<string>", "eval") | ||
| return eval(code, {"__builtins__": {}}, globalns) |
src/codeweaver/core/di/container.py
Outdated
| TypeValidator().visit(tree) | ||
|
|
||
| # Restricted eval: no builtins allowed | ||
| code = compile(tree, "<string>", "eval") | ||
| return eval(code, {"__builtins__": {}}, globalns) |
Code ReviewI've reviewed this PR which addresses a security vulnerability by replacing Critical Security Issues1. Arbitrary Function Calls Still Allowed 🚨The current AST whitelist permits # If globalns contains os module, this would still work:
"os.system('malicious command')"Recommendation: Either:
See: 2. Unrestricted Binary OperationsThe validator allows all "Foo + Bar" # Addition
"Foo * 2" # Multiplication
"Foo << 8" # Bit shiftsRecommendation: Add a check to ensure only if isinstance(node, ast.BinOp) and not isinstance(node.op, ast.BitOr):
raise ValueError(f"Forbidden binary operator: {type(node.op).__name__}")See: 3. globalns Namespace ExposureThe implementation passes the entire Recommendation: Consider curating a minimal, safe namespace specifically for type resolution rather than passing through arbitrary globals. Code Quality Issues4. Missing Test:
|
There was a problem hiding this comment.
This PR addresses an important security vulnerability, but the current implementation has critical security gaps that allow arbitrary code execution through the AST whitelist. Please address the security issues outlined in my detailed review comment before merging.
Key concerns:
- Arbitrary function calls still permitted via ast.Call
- All binary operations allowed (not just unions)
- Full globalns exposure creates attack surface
- Missing test coverage for edge cases
The approach is sound, but needs hardening to actually prevent code injection attacks.
Security Fix ReviewThank you for addressing this critical security vulnerability! The replacement of ✅ Strengths
|
There was a problem hiding this comment.
Security Fix Review
I've reviewed PR #229 which addresses a critical security vulnerability in the DI container's type resolution. Here's my comprehensive analysis:
✅ Security Fix Quality
The replacement of eval() with AST-validated type resolution is excellent and follows security best practices:
- AST Validation: The
_safe_eval_typemethod properly validates the AST before evaluation - Allowlist Approach: Only permits specific AST node types needed for type hints (Name, Attribute, Subscript, BinOp, etc.)
- Dunder Blocking: Explicitly blocks dunder names/attributes to prevent
__import__,__builtins__, etc. - Restricted Environment: Evaluates with
{"__builtins__": {}}to disable built-in functions - Graceful Failure: Returns
Noneon syntax errors or validation failures
✅ Test Coverage
The security tests in test_container_security.py are well-designed:
- ✅ Tests valid type expressions (generics, unions, Annotated)
- ✅ Tests malicious injection attempts
- ✅ Tests dunder attribute/name blocking
❌ CI Failure - Missing License Header
The tests are failing due to a missing REUSE license header in the new test file. The file needs this header at the top:
# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0This is required for REUSE compliance (all project files need copyright/license info).
🔍 Code Quality Observations
Strengths:
- Clear, comprehensive documentation in docstrings
- Proper error handling with try/except for SyntaxError
- Maintains backward compatibility with existing type resolution
- Uses
contextlib.suppressappropriately for fallback behavior
Minor Suggestions:
-
Potential Issue with
eval()Exception Handling: In_safe_eval_typeline 143, theeval()call could raise exceptions beyondValueError. Consider wrapping it:try: return eval(code, {"__builtins__": {}}, globalns) except Exception: return None
-
AST Node Coverage: Consider if you need to support:
ast.Slicefor slice notation in genericsast.Index(deprecated in 3.9+ but may exist in AST for compatibility)
-
Test Enhancement: Add a test case for deeply nested generics like
Dict[str, List[Optional[int]]]to ensure the validator handles complex nesting.
📝 Recommendations
Required for merge:
- Add the REUSE license header to
tests/di/test_container_security.py
Suggested improvements (optional):
- Add broader exception handling around the
eval()call - Consider adding more edge case tests for complex nested types
- Document the security rationale in the module docstring
🎯 Overall Assessment
This is a high-quality security fix that properly addresses the eval() vulnerability. The AST-based approach is industry-standard for safe dynamic type evaluation. Once the license header is added, this should be ready to merge.
The fix appropriately balances security with functionality - it blocks malicious code while still supporting legitimate type hint patterns including generics, unions, and Annotated types with Depends().
Verdict: Approve pending license header fix ✅
There was a problem hiding this comment.
Security Review of PR #229
I've completed a thorough security review of this PR that fixes a critical security vulnerability in the DI container. Here's my assessment:
✅ Excellent Security Fix
The PR successfully replaces the dangerous eval() call with a robust AST-based validation approach. The implementation is well-designed and comprehensive.
Strengths
-
Strong AST Validation (container.py:87-143)
- Uses
ast.parse()to create an Abstract Syntax Tree before evaluation - Explicitly whitelists only safe node types needed for type annotations
- Supports complex typing constructs: Generics, Unions, Annotated, Literals, etc.
- Clean implementation with clear comments explaining what each node type is for
- Uses
-
Dunder Protection (container.py:132-135)
- Blocks
__import__,__builtins__, and other dunder access - Prevents escaping the restricted environment
- Covers both
ast.Nameandast.Attributenode types
- Blocks
-
Restricted Execution Environment (container.py:141-143)
- Sets
__builtins__: {}to prevent access to built-in functions - Limits scope to only the provided
globalns - Multiple layers of defense
- Sets
-
Comprehensive Test Coverage (tests/di/test_container_security.py)
- Tests valid type resolution including complex generics
- Tests malicious string blocking (eval, import, getattr, lambda, etc.)
- Tests dunder blocking specifically
- Good variety of attack vectors covered
-
Graceful Error Handling (container.py:100-103)
- Returns
Noneon syntax errors instead of crashing - Maintains backward compatibility
- Returns
Issues Identified
🔴 Critical: Missing License Headers
The new test file tests/di/test_container_security.py is missing the required SPDX headers, causing CI failures. The file needs these headers at the top:
# SPDX-FileCopyrightText: 2025 Knitli Inc.
# SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
#
# SPDX-License-Identifier: MIT OR Apache-2.0This is the root cause of all test failures.
⚠️ Minor: Import Organization
In the test file (test_container_security.py:2-5), imports should be organized:
- Standard library imports first
- Third-party imports (pytest) next
- Local imports last
Should be:
from typing import Annotated, List, Optional, Union
import pytest
from codeweaver.core.di.container import Container
from codeweaver.core.di.dependency import Depends📝 Minor: Test Documentation
The test file would benefit from a module docstring explaining the security concerns being tested, similar to other test files in the project (see test_provider_api.py:5-17).
Edge Cases to Consider
The implementation handles these well, but worth noting:
- Complex Nested Types: The code correctly handles deeply nested generics like
Dict[str, List[Tuple[int, str]]] - Union Syntax: Both old-style
Union[X, Y]and new-styleX | Yare supported viaast.BinOpandast.BitOr - Syntax Errors: Gracefully returns
Nonerather than crashing - Performance: AST parsing adds minimal overhead compared to the security benefit
Recommendations
- Fix the license headers - This will make all tests pass
- Consider adding a test for deeply nested complex types to document this works:
assert container._resolve_string_type("Dict[str, List[Tuple[int, Optional[str]]]]", globalns)
- Consider documenting in the code comment that this approach is resistant to:
- Code injection via
__import__ - Attribute access exploits via
__builtins__ - Lambda/function definition attacks
- Class instantiation attacks
- Code injection via
Security Assessment
✅ The security fix is sound and ready to merge once the license header issue is resolved.
The multi-layered defense approach (AST validation + dunder blocking + restricted environment) provides excellent protection against arbitrary code execution while maintaining full functionality for legitimate type resolution.
Approval Status
I'm marking this as requesting changes solely due to the missing license headers causing CI failures. Once those are added, this is ready to merge.
Great work on identifying and fixing this critical vulnerability! 🔒
|
@jules please rebase and address the following PR comments: Issues Identified 🔴 Critical: Missing License Headers This is the root cause of all test failures.
Standard library imports first from typing import Annotated, List, Optional, Union
import pytest
from codeweaver.core.di.container import Container
from codeweaver.core.di.dependency import Depends📝 Minor: Test Documentation Edge Cases to Consider Complex Nested Types: The code correctly handles deeply nested generics like Dict[str, List[Tuple[int, str]]] |
I've addressed the feedback by adding the missing SPDX license headers, organizing the imports according to the project style (stdlib, third-party, then local), and adding a module docstring to |
- Replaces `eval()` in `src/codeweaver/core/di/container.py` with `_safe_eval_type`. - `_safe_eval_type` uses AST validation to allow only type-safe constructs. - Blocks access to dunder attributes and names to prevent sandbox escapes. - Evaluates in a restricted environment with a whitelist of safe built-ins (int, str, list, etc.). - Includes comprehensive security tests in `tests/di/test_container_security.py` with proper license headers and documentation. Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Security Review: Safe Type Resolution in DI ContainerI've reviewed PR #229 and the security fix looks excellent. This is a critical security improvement that properly addresses the eval() vulnerability while maintaining full functionality. ✅ Security AnalysisVulnerability Fixed: Security Measures Implemented:
✅ Code QualityStrengths:
Implementation Details:
|
There was a problem hiding this comment.
Additional Code Quality Note
Regarding the unused import comment on tests/di/test_container_security.py:14:
Confirmed: The get_origin import is indeed unused. While this is a minor linting issue that doesn't affect the security fix or functionality, it would be good to clean up.
Suggested fix for line 14:
from typing import Annotated, List, Optional, Union, get_argsThe import became vestigial because the test (lines 42-48) uses __metadata__ attribute checking instead of get_origin() for cross-version compatibility.
This can be addressed in a follow-up commit if desired, but should not block merging this important security fix.
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> Signed-off-by: Adam Poulemanos <89049923+bashandbone@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Replaces an unsafe eval() call in the DI container's string type resolution with an AST-validated evaluation that restricts allowed node types and blocks dunder access.
Changes:
- Added
_safe_eval_typemethod with AST validation and restricted builtins to replace raweval() - Updated both call sites in
_resolve_string_typeto use the new safe method - Added security tests verifying malicious strings are blocked and valid type expressions still resolve
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/codeweaver/core/di/container.py | Adds _safe_eval_type with AST validation and restricted eval; replaces two eval() calls |
| tests/di/test_container_security.py | New test file covering safe resolution, malicious strings, dunder blocking, and builtin types |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| # SPDX-FileCopyrightText: 2025 Knitli Inc. | ||
| # SPDX-FileContributor: Adam Poulemanos <adam@knit.li> | ||
| # | ||
| # SPDX-License-Identifier: MIT OR Apache-2.0 | ||
|
|
||
| """Security tests for the dependency injection container. | ||
|
|
||
| This module verifies that the DI container safely resolves string type | ||
| annotations, preventing arbitrary code execution while supporting | ||
| complex Python type hints including generics, unions, and Annotated types. | ||
| """ | ||
|
|
||
| from typing import Annotated, List, Optional, Union, get_args | ||
|
|
||
| import pytest | ||
|
|
||
| from codeweaver.core.di.container import Container | ||
| from codeweaver.core.di.dependency import Depends | ||
|
|
||
| def test_safe_type_resolution(): |
| "dict": dict, | ||
| "set": set, | ||
| "frozenset": frozenset, | ||
| "type": type, |
| Raises: | ||
| ValueError: If the type string contains forbidden constructs. | ||
| """ | ||
| try: | ||
| tree = ast.parse(type_str, mode="eval") | ||
| except SyntaxError: | ||
| return None |
Fixed a security vulnerability in the Dependency Injection container where
eval()was used for string type resolution. The fix replaceseval()with a more secure implementation that:__import__).__builtins__disabled.Added security tests in
tests/di/test_container_security.pyto verify the fix and ensure that legitimate type expressions (including generics and unions) still resolve correctly.PR created automatically by Jules for task 15906456217921985240 started by @bashandbone
Summary by Sourcery
Harden DI container string type resolution by replacing unsafe eval usage with AST-validated evaluation and add regression tests for secure and valid type handling.
Bug Fixes:
Enhancements:
Tests: