mirror of
https://github.com/crewAIInc/crewAI-examples.git
synced 2026-01-10 22:38:00 -05:00
fix: replace eval() with safe AST parsing to prevent code injection
- Replace dangerous eval() usage in calculator tools with safe AST-based mathematical expression parser - Add input validation with regex to prevent injection of non-mathematical content - Implement proper error handling for malformed expressions - Update ExaSearchTool to use JSON parsing + ast.literal_eval() instead of eval() - Maintain backward compatibility while eliminating critical security vulnerability Addresses CVE-level code injection risk that could lead to arbitrary code execution
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import os
|
||||
import ast
|
||||
import json
|
||||
from exa_py import Exa
|
||||
from langchain.agents import tool
|
||||
|
||||
@@ -20,12 +22,34 @@ class ExaSearchTool:
|
||||
"""Get the contents of a webpage.
|
||||
The ids must be passed in as a list, a list of ids returned from `search`.
|
||||
"""
|
||||
ids = eval(ids)
|
||||
contents = str(ExaSearchTool._exa().get_contents(ids))
|
||||
print(contents)
|
||||
contents = contents.split("URL:")
|
||||
contents = [content[:1000] for content in contents]
|
||||
return "\n\n".join(contents)
|
||||
try:
|
||||
# Try to parse as JSON first (safer)
|
||||
try:
|
||||
ids = json.loads(ids)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
# If JSON parsing fails, try ast.literal_eval as a fallback
|
||||
try:
|
||||
ids = ast.literal_eval(ids)
|
||||
except (ValueError, SyntaxError):
|
||||
# If both fail, assume it's a single ID string
|
||||
ids = [ids.strip()] if isinstance(ids, str) else []
|
||||
|
||||
# Validate that ids is a list
|
||||
if not isinstance(ids, list):
|
||||
return "Error: IDs must be provided as a list"
|
||||
|
||||
# Validate each ID is a string
|
||||
if not all(isinstance(id_val, str) for id_val in ids):
|
||||
return "Error: All IDs must be strings"
|
||||
|
||||
contents = str(ExaSearchTool._exa().get_contents(ids))
|
||||
print(contents)
|
||||
contents = contents.split("URL:")
|
||||
contents = [content[:1000] for content in contents]
|
||||
return "\n\n".join(contents)
|
||||
|
||||
except Exception as e:
|
||||
return f"Error processing IDs: {str(e)}"
|
||||
|
||||
def tools():
|
||||
return [ExaSearchTool.search, ExaSearchTool.find_similar, ExaSearchTool.get_contents]
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from crewai_tools import BaseTool
|
||||
import ast
|
||||
import operator
|
||||
import re
|
||||
|
||||
|
||||
class CalculatorTool(BaseTool):
|
||||
@@ -7,6 +10,54 @@ class CalculatorTool(BaseTool):
|
||||
"Useful to perform any mathematical calculations, like sum, minus, multiplication, division, etc. The input to this tool should be a mathematical expression, a couple examples are `200*7` or `5000/2*10."
|
||||
)
|
||||
|
||||
def _run(self, operation: str) -> int:
|
||||
# Implementation goes here
|
||||
return eval(operation)
|
||||
def _run(self, operation: str) -> float:
|
||||
try:
|
||||
# Define allowed operators for safe evaluation
|
||||
allowed_operators = {
|
||||
ast.Add: operator.add,
|
||||
ast.Sub: operator.sub,
|
||||
ast.Mult: operator.mul,
|
||||
ast.Div: operator.truediv,
|
||||
ast.Pow: operator.pow,
|
||||
ast.Mod: operator.mod,
|
||||
ast.USub: operator.neg,
|
||||
ast.UAdd: operator.pos,
|
||||
}
|
||||
|
||||
# Parse and validate the expression
|
||||
if not re.match(r'^[0-9+\-*/().% ]+$', operation):
|
||||
raise ValueError("Invalid characters in mathematical expression")
|
||||
|
||||
# Parse the expression
|
||||
tree = ast.parse(operation, mode='eval')
|
||||
|
||||
def _eval_node(node):
|
||||
if isinstance(node, ast.Expression):
|
||||
return _eval_node(node.body)
|
||||
elif isinstance(node, ast.Constant): # Python 3.8+
|
||||
return node.value
|
||||
elif isinstance(node, ast.Num): # Python < 3.8
|
||||
return node.n
|
||||
elif isinstance(node, ast.BinOp):
|
||||
left = _eval_node(node.left)
|
||||
right = _eval_node(node.right)
|
||||
op = allowed_operators.get(type(node.op))
|
||||
if op is None:
|
||||
raise ValueError(f"Unsupported operator: {type(node.op).__name__}")
|
||||
return op(left, right)
|
||||
elif isinstance(node, ast.UnaryOp):
|
||||
operand = _eval_node(node.operand)
|
||||
op = allowed_operators.get(type(node.op))
|
||||
if op is None:
|
||||
raise ValueError(f"Unsupported operator: {type(node.op).__name__}")
|
||||
return op(operand)
|
||||
else:
|
||||
raise ValueError(f"Unsupported node type: {type(node).__name__}")
|
||||
|
||||
result = _eval_node(tree)
|
||||
return result
|
||||
|
||||
except (SyntaxError, ValueError, ZeroDivisionError, TypeError) as e:
|
||||
raise ValueError(f"Calculation error: {str(e)}")
|
||||
except Exception:
|
||||
raise ValueError("Invalid mathematical expression")
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from langchain.tools import tool
|
||||
import ast
|
||||
import operator
|
||||
import re
|
||||
|
||||
class CalculatorTools():
|
||||
|
||||
@@ -10,6 +13,52 @@ class CalculatorTools():
|
||||
expression, a couple examples are `200*7` or `5000/2*10`
|
||||
"""
|
||||
try:
|
||||
return eval(operation)
|
||||
except SyntaxError:
|
||||
return "Error: Invalid syntax in mathematical expression"
|
||||
# Define allowed operators for safe evaluation
|
||||
allowed_operators = {
|
||||
ast.Add: operator.add,
|
||||
ast.Sub: operator.sub,
|
||||
ast.Mult: operator.mul,
|
||||
ast.Div: operator.truediv,
|
||||
ast.Pow: operator.pow,
|
||||
ast.Mod: operator.mod,
|
||||
ast.USub: operator.neg,
|
||||
ast.UAdd: operator.pos,
|
||||
}
|
||||
|
||||
# Parse and validate the expression
|
||||
if not re.match(r'^[0-9+\-*/().% ]+$', operation):
|
||||
return "Error: Invalid characters in mathematical expression"
|
||||
|
||||
# Parse the expression
|
||||
tree = ast.parse(operation, mode='eval')
|
||||
|
||||
def _eval_node(node):
|
||||
if isinstance(node, ast.Expression):
|
||||
return _eval_node(node.body)
|
||||
elif isinstance(node, ast.Constant): # Python 3.8+
|
||||
return node.value
|
||||
elif isinstance(node, ast.Num): # Python < 3.8
|
||||
return node.n
|
||||
elif isinstance(node, ast.BinOp):
|
||||
left = _eval_node(node.left)
|
||||
right = _eval_node(node.right)
|
||||
op = allowed_operators.get(type(node.op))
|
||||
if op is None:
|
||||
raise ValueError(f"Unsupported operator: {type(node.op).__name__}")
|
||||
return op(left, right)
|
||||
elif isinstance(node, ast.UnaryOp):
|
||||
operand = _eval_node(node.operand)
|
||||
op = allowed_operators.get(type(node.op))
|
||||
if op is None:
|
||||
raise ValueError(f"Unsupported operator: {type(node.op).__name__}")
|
||||
return op(operand)
|
||||
else:
|
||||
raise ValueError(f"Unsupported node type: {type(node).__name__}")
|
||||
|
||||
result = _eval_node(tree)
|
||||
return result
|
||||
|
||||
except (SyntaxError, ValueError, ZeroDivisionError, TypeError) as e:
|
||||
return f"Error: {str(e)}"
|
||||
except Exception as e:
|
||||
return "Error: Invalid mathematical expression"
|
||||
|
||||
Reference in New Issue
Block a user