-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix: harden JWT secret defaults and replace eval() in examples (GHSA-3qg8-5g3r-79v5, GHSA-vg22-4gmj-prxw) #1793
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,8 +16,19 @@ def search_web(query: str) -> str: | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| def calculate(expression: str) -> str: | ||||||||||||||||||||||||||||||||||
| """Calculate a math expression.""" | ||||||||||||||||||||||||||||||||||
| return str(eval(expression)) | ||||||||||||||||||||||||||||||||||
| """Calculate a math expression safely.""" | ||||||||||||||||||||||||||||||||||
| import ast, operator | ||||||||||||||||||||||||||||||||||
| _OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, | ||||||||||||||||||||||||||||||||||
| ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv, | ||||||||||||||||||||||||||||||||||
| ast.Mod: operator.mod, ast.Pow: operator.pow, | ||||||||||||||||||||||||||||||||||
| ast.USub: operator.neg, ast.UAdd: operator.pos} | ||||||||||||||||||||||||||||||||||
| def _ev(n): | ||||||||||||||||||||||||||||||||||
| if isinstance(n, ast.Expression): return _ev(n.body) | ||||||||||||||||||||||||||||||||||
| if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value | ||||||||||||||||||||||||||||||||||
| if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand)) | ||||||||||||||||||||||||||||||||||
| if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right)) | ||||||||||||||||||||||||||||||||||
| raise ValueError(f"Unsupported: {ast.dump(n)}") | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Split the one-line evaluator branches. Ruff is already flagging Lines 26-29 with E701, so this copy will fail the configured lint check until each branch is expanded onto its own lines. Suggested cleanup def _ev(n):
- if isinstance(n, ast.Expression): return _ev(n.body)
- if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value
- if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand))
- if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
+ if isinstance(n, ast.Expression):
+ return _ev(n.body)
+ if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)):
+ return n.value
+ if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.operand))
+ if isinstance(n, ast.BinOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
raise ValueError(f"Unsupported: {ast.dump(n)}")📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.15)[error] 26-26: Multiple statements on one line (colon) (E701) [error] 27-27: Multiple statements on one line (colon) (E701) [error] 28-28: Multiple statements on one line (colon) (E701) [error] 29-29: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| return str(_ev(ast.parse(expression, mode="eval"))) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Check if we have an API key | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,20 +19,43 @@ | |
|
|
||
| def calculate(expression: str) -> str: | ||
| """ | ||
| A simple calculator tool that evaluates mathematical expressions. | ||
| A simple calculator tool that evaluates mathematical expressions safely. | ||
|
|
||
| Args: | ||
| expression: A mathematical expression to evaluate (e.g., "2 + 2") | ||
|
|
||
| Returns: | ||
| The result of the calculation | ||
| """ | ||
| import ast | ||
| import operator | ||
|
|
||
| _OPS = { | ||
| ast.Add: operator.add, | ||
| ast.Sub: operator.sub, | ||
| ast.Mult: operator.mul, | ||
| ast.Div: operator.truediv, | ||
| ast.FloorDiv: operator.floordiv, | ||
| ast.Mod: operator.mod, | ||
| ast.Pow: operator.pow, | ||
| ast.USub: operator.neg, | ||
| ast.UAdd: operator.pos, | ||
| } | ||
|
Comment on lines
+33
to
+43
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allowing the exponentiation operator ( Since this is a simple calculator tool, it is safest to remove _OPS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.FloorDiv: operator.floordiv,
ast.Mod: operator.mod,
ast.USub: operator.neg,
ast.UAdd: operator.pos,
} |
||
|
|
||
| def _safe_eval(node): | ||
| if isinstance(node, ast.Expression): | ||
| return _safe_eval(node.body) | ||
| if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): | ||
| return node.value | ||
| if isinstance(node, ast.UnaryOp) and type(node.op) in _OPS: | ||
| return _OPS[type(node.op)](_safe_eval(node.operand)) | ||
| if isinstance(node, ast.BinOp) and type(node.op) in _OPS: | ||
| return _OPS[type(node.op)](_safe_eval(node.left), _safe_eval(node.right)) | ||
| raise ValueError(f"Unsupported expression: {ast.dump(node)}") | ||
|
Comment on lines
+45
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, in Python, bool is a subclass of int [1][2][3][4]. This relationship exists for backward compatibility, as boolean values were historically represented as 0 and 1 before the formal bool type was introduced [4][5]. Because bool is a subclass of int, the function isinstance(True, (int, float)) returns True [2][5]. This is because isinstance checks if the object is an instance of the specified class or any of its subclasses [6]. Since True is an instance of bool, and bool is a subclass of int, the check against int succeeds [2][4][5]. Citations:
Exclude 🤖 Prompt for AI Agents |
||
|
|
||
| try: | ||
| # Safe evaluation of mathematical expressions | ||
| allowed_chars = set("0123456789+-*/.() ") | ||
| if not all(c in allowed_chars for c in expression): | ||
| return "Error: Invalid characters in expression" | ||
| result = eval(expression) | ||
| tree = ast.parse(expression, mode="eval") | ||
| result = _safe_eval(tree) | ||
| return f"Result: {result}" | ||
| except Exception as e: | ||
| return f"Error: {str(e)}" | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,12 +24,18 @@ def greet(name: str) -> str: | |||||||||||||||||
| def calculate(expression: str) -> str: | ||||||||||||||||||
| """Safely evaluate a mathematical expression.""" | ||||||||||||||||||
| try: | ||||||||||||||||||
| # Only allow safe math operations | ||||||||||||||||||
| allowed = set("0123456789+-*/.(). ") | ||||||||||||||||||
| if not all(c in allowed for c in expression): | ||||||||||||||||||
| return "Error: Invalid characters in expression" | ||||||||||||||||||
| result = eval(expression) # Safe due to character filtering | ||||||||||||||||||
| return f"Result: {result}" | ||||||||||||||||||
| import ast, operator | ||||||||||||||||||
| _OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, | ||||||||||||||||||
| ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv, | ||||||||||||||||||
| ast.Mod: operator.mod, ast.Pow: operator.pow, | ||||||||||||||||||
| ast.USub: operator.neg, ast.UAdd: operator.pos} | ||||||||||||||||||
|
Comment on lines
+28
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allowing the exponentiation operator ( Since this is a simple calculator tool, it is safest to remove
Suggested change
|
||||||||||||||||||
| def _ev(n): | ||||||||||||||||||
| if isinstance(n, ast.Expression): return _ev(n.body) | ||||||||||||||||||
| if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value | ||||||||||||||||||
| if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand)) | ||||||||||||||||||
| if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right)) | ||||||||||||||||||
| raise ValueError(f"Unsupported: {ast.dump(n)}") | ||||||||||||||||||
| return f"Result: {_ev(ast.parse(expression, mode='eval'))}" | ||||||||||||||||||
|
Comment on lines
+32
to
+38
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Expand the one-line Ruff is already reporting E701 on Lines 33-36, so this block will keep failing lint until each branch is split onto its own lines. 🧰 Tools🪛 Ruff (0.15.15)[error] 33-33: Multiple statements on one line (colon) (E701) [error] 34-34: Multiple statements on one line (colon) (E701) [error] 35-35: Multiple statements on one line (colon) (E701) [error] 36-36: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||
| except Exception as e: | ||||||||||||||||||
| return f"Error: {e}" | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,9 +21,36 @@ def search_web(query: str) -> str: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return f"Search results for: {query}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def calculate(expression: str) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Calculate a mathematical expression.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Calculate a mathematical expression safely.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ast | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import operator | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _OPS = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ast.Add: operator.add, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ast.Sub: operator.sub, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ast.Mult: operator.mul, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ast.Div: operator.truediv, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ast.FloorDiv: operator.floordiv, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ast.Mod: operator.mod, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ast.Pow: operator.pow, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ast.USub: operator.neg, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ast.UAdd: operator.pos, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+38
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allowing the exponentiation operator ( Since this is a simple calculator tool, it is safest to remove _OPS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.FloorDiv: operator.floordiv,
ast.Mod: operator.mod,
ast.USub: operator.neg,
ast.UAdd: operator.pos,
} |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _eval(node): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(node, ast.Expression): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _eval(node.body) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return node.value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(node, ast.UnaryOp) and type(node.op) in _OPS: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _OPS[type(node.op)](_eval(node.operand)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(node, ast.BinOp) and type(node.op) in _OPS: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return _OPS[type(node.op)](_eval(node.left), _eval(node.right)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError(f"Unsupported expression: {ast.dump(node)}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return f"Result: {eval(expression)}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tree = ast.parse(expression, mode="eval") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return f"Result: {_eval(tree)}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bound expensive arithmetic before exposing this over A2A. This server is network-reachable, and the unrestricted Minimal hardening if exponentiation is not required _OPS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.FloorDiv: operator.floordiv,
ast.Mod: operator.mod,
- ast.Pow: operator.pow,
ast.USub: operator.neg,
ast.UAdd: operator.pos,
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "Invalid expression" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -49,7 +49,18 @@ def calculate_with_tracking(expression: str) -> str: | |||||||||||||||||||||||||||||||||||||
| time.sleep(0.05) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| result = eval(expression) | ||||||||||||||||||||||||||||||||||||||
| import ast, operator | ||||||||||||||||||||||||||||||||||||||
| _OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, | ||||||||||||||||||||||||||||||||||||||
| ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv, | ||||||||||||||||||||||||||||||||||||||
| ast.Mod: operator.mod, ast.Pow: operator.pow, | ||||||||||||||||||||||||||||||||||||||
| ast.USub: operator.neg, ast.UAdd: operator.pos} | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+56
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allowing the exponentiation operator ( Since this is a simple calculator tool, it is safest to remove
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| def _ev(n): | ||||||||||||||||||||||||||||||||||||||
| if isinstance(n, ast.Expression): return _ev(n.body) | ||||||||||||||||||||||||||||||||||||||
| if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value | ||||||||||||||||||||||||||||||||||||||
| if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand)) | ||||||||||||||||||||||||||||||||||||||
| if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right)) | ||||||||||||||||||||||||||||||||||||||
| raise ValueError(f"Unsupported: {ast.dump(n)}") | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+57
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject booleans explicitly and expand the one-line branches.
Suggested fix def _ev(n):
- if isinstance(n, ast.Expression): return _ev(n.body)
- if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value
- if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand))
- if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
+ if isinstance(n, ast.Expression):
+ return _ev(n.body)
+ if isinstance(n, ast.Constant):
+ if type(n.value) not in (int, float):
+ raise ValueError(f"Unsupported: {ast.dump(n)}")
+ return n.value
+ if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.operand))
+ if isinstance(n, ast.BinOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
raise ValueError(f"Unsupported: {ast.dump(n)}")📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.15)[error] 58-58: Multiple statements on one line (colon) (E701) [error] 59-59: Multiple statements on one line (colon) (E701) [error] 60-60: Multiple statements on one line (colon) (E701) [error] 61-61: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| result = _ev(ast.parse(expression, mode="eval")) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
51
to
+63
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the relevant portion of the file with line numbers
FILE="examples/python/custom_tools/tools_with_latency.py"
nl -ba "$FILE" | sed -n '1,120p' > /tmp/ttls_top.txt
echo "=== $FILE (lines 1-120) ==="
cat /tmp/ttls_top.txt
# Extract calculate_with_tracking and any helper(s) for context
echo
echo "=== Search for calculate_with_tracking definition ==="
rg -n "def calculate_with_tracking|calculate_with_tracking" "$FILE"
# Look for expression validation or any MAX_* constants in the file
echo
echo "=== Search for expression bounds/validation patterns in the file ==="
rg -n "MAX_|len\(expression\)|expression.*(len|depth|pow|exp|bound)" "$FILE" || true
# Find other occurrences of Pow / AST evaluator in repo to see if this helper is reused
echo
echo "=== Search repo for the _ev(ast.parse(... mode=\"eval\")) pattern ==="
rg -n "_ev\\(ast\\.parse\\(expression, mode=\"eval\"\\)\\)|ast\\.Pow|Unsupported: \\{ast\\.dump\\(n\\)\\}" -S .Repository: MervinPraison/PraisonAI Length of output: 110 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="examples/python/custom_tools/tools_with_latency.py"
echo "=== Showing file with line numbers around the referenced range (40-100) ==="
cat -n "$FILE" | sed -n '40,110p'
echo
echo "=== Find calculate_with_tracking definition and usage in this file ==="
rg -n "def calculate_with_tracking|calculate_with_tracking\\(" "$FILE" || true
echo
echo "=== Find the AST evaluator helper(s) inside the tool ==="
rg -n "_ev\\b|ast\\.parse\\(expression, mode=\"eval\"\\)|Unsupported: \\{ast\\.dump\\(n\\)\\}|_OPS\\b|ast\\.Pow" "$FILE" || true
echo
echo "=== Check for any existing complexity/length bounds in this file ==="
rg -n "MAX_|min\\(|max\\(|len\\(expression\\)|depth|recurs|exponent|pow\\b.*(bound|limit)|Expression too|Exponent too" "$FILE" || true
echo
echo "=== Check for isinstance(n.value, (int, float)) specifically ==="
rg -n "isinstance\\(n\\.value, \\(int, float\\)\\)|isinstance\\(.*n\\.value.*int" "$FILE" || true
echo
echo "=== Repo-wide: see if this evaluator pattern exists elsewhere (optional but helps) ==="
rg -n "_ev\\(ast\\.parse\\(expression, mode=\"eval\"\\)\\)|Unsupported: \\{ast\\.dump\\(n\\)\\}" -S . || trueRepository: MervinPraison/PraisonAI Length of output: 5643 Add complexity/length limits to the AST expression evaluator in RCE is mitigated, but the evaluator still has no bounds (no
Suggested guard rails+ MAX_EXPRESSION_LENGTH = 200
+ MAX_DEPTH = 32
+ MAX_EXPONENT = 12
+
+ if len(expression) > MAX_EXPRESSION_LENGTH:
+ raise ValueError("Expression too long")
+
- def _ev(n):
- if isinstance(n, ast.Expression): return _ev(n.body)
+ def _ev(n, depth=0):
+ if depth > MAX_DEPTH:
+ raise ValueError("Expression too complex")
+ if isinstance(n, ast.Expression):
+ return _ev(n.body, depth + 1)
...
- if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
+ if isinstance(n, ast.BinOp) and type(n.op) in _OPS:
+ left = _ev(n.left, depth + 1)
+ right = _ev(n.right, depth + 1)
+ if isinstance(n.op, ast.Pow) and abs(right) > MAX_EXPONENT:
+ raise ValueError("Exponent too large")
+ return _OPS[type(n.op)](left, right)🧰 Tools🪛 Ruff (0.15.15)[error] 58-58: Multiple statements on one line (colon) (E701) [error] 59-59: Multiple statements on one line (colon) (E701) [error] 60-60: Multiple statements on one line (colon) (E701) [error] 61-61: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| return f"Result: {result}" | ||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||
| return f"Error: {str(e)}" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -36,10 +36,18 @@ def calculate(expression: str) -> dict: | |||||||||||||||||
| expression: Math expression (e.g., "2 + 2 * 3") | ||||||||||||||||||
| """ | ||||||||||||||||||
| try: | ||||||||||||||||||
| allowed = set("0123456789+-*/.() ") | ||||||||||||||||||
| if not all(c in allowed for c in expression): | ||||||||||||||||||
| return {"error": "Invalid characters"} | ||||||||||||||||||
| return {"expression": expression, "result": eval(expression)} | ||||||||||||||||||
| import ast, operator | ||||||||||||||||||
| _OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, | ||||||||||||||||||
| ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv, | ||||||||||||||||||
| ast.Mod: operator.mod, ast.Pow: operator.pow, | ||||||||||||||||||
| ast.USub: operator.neg, ast.UAdd: operator.pos} | ||||||||||||||||||
|
Comment on lines
+40
to
+43
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allowing the exponentiation operator ( Since this is a simple calculator tool, it is safest to remove
Suggested change
|
||||||||||||||||||
| def _ev(n): | ||||||||||||||||||
| if isinstance(n, ast.Expression): return _ev(n.body) | ||||||||||||||||||
| if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value | ||||||||||||||||||
| if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand)) | ||||||||||||||||||
| if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right)) | ||||||||||||||||||
| raise ValueError(f"Unsupported: {ast.dump(n)}") | ||||||||||||||||||
|
Comment on lines
+44
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Split the one-line evaluator branches. Ruff is already flagging Lines 45-48 with E701, so this block will keep failing lint until each branch is expanded. 🧰 Tools🪛 Ruff (0.15.15)[error] 45-45: Multiple statements on one line (colon) (E701) [error] 46-46: Multiple statements on one line (colon) (E701) [error] 47-47: Multiple statements on one line (colon) (E701) [error] 48-48: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||
| return {"expression": expression, "result": _ev(ast.parse(expression, mode="eval"))} | ||||||||||||||||||
|
Comment on lines
+39
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject or cap exponentiation on the public SSE tool. Because this server is exposed on 🧰 Tools🪛 Ruff (0.15.15)[error] 45-45: Multiple statements on one line (colon) (E701) [error] 46-46: Multiple statements on one line (colon) (E701) [error] 47-47: Multiple statements on one line (colon) (E701) [error] 48-48: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||
| except Exception as e: | ||||||||||||||||||
| return {"error": str(e)} | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -47,12 +47,18 @@ def calculate(expression: str) -> Dict[str, Any]: | |||||||||||||||||
| The result of the calculation | ||||||||||||||||||
| """ | ||||||||||||||||||
| try: | ||||||||||||||||||
| # Safe evaluation of mathematical expressions | ||||||||||||||||||
| allowed_chars = set("0123456789+-*/.() ") | ||||||||||||||||||
| if not all(c in allowed_chars for c in expression): | ||||||||||||||||||
| return {"error": "Invalid characters in expression"} | ||||||||||||||||||
|
|
||||||||||||||||||
| result = eval(expression) # Use safer alternative in production | ||||||||||||||||||
| import ast, operator | ||||||||||||||||||
| _OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, | ||||||||||||||||||
| ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv, | ||||||||||||||||||
| ast.Mod: operator.mod, ast.Pow: operator.pow, | ||||||||||||||||||
| ast.USub: operator.neg, ast.UAdd: operator.pos} | ||||||||||||||||||
|
Comment on lines
+51
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allowing the exponentiation operator ( Since this is a simple calculator tool, it is safest to remove
Suggested change
|
||||||||||||||||||
| def _ev(n): | ||||||||||||||||||
| if isinstance(n, ast.Expression): return _ev(n.body) | ||||||||||||||||||
| if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value | ||||||||||||||||||
| if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand)) | ||||||||||||||||||
| if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right)) | ||||||||||||||||||
| raise ValueError(f"Unsupported: {ast.dump(n)}") | ||||||||||||||||||
|
Comment on lines
+55
to
+60
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tighten numeric literal validation and address Ruff E701
Suggested fix def _ev(n):
- if isinstance(n, ast.Expression): return _ev(n.body)
- if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value
- if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand))
- if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
+ if isinstance(n, ast.Expression):
+ return _ev(n.body)
+ if isinstance(n, ast.Constant):
+ if type(n.value) not in (int, float):
+ raise ValueError(f"Unsupported: {ast.dump(n)}")
+ return n.value
+ if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.operand))
+ if isinstance(n, ast.BinOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
raise ValueError(f"Unsupported: {ast.dump(n)}")🧰 Tools🪛 Ruff (0.15.15)[error] 56-56: Multiple statements on one line (colon) (E701) [error] 57-57: Multiple statements on one line (colon) (E701) [error] 58-58: Multiple statements on one line (colon) (E701) [error] 59-59: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||
| result = _ev(ast.parse(expression, mode="eval")) | ||||||||||||||||||
| return {"expression": expression, "result": result} | ||||||||||||||||||
| except Exception as e: | ||||||||||||||||||
| return {"error": str(e)} | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -49,13 +49,18 @@ def calculate(expression: str) -> Dict[str, Any]: | |||||||||||||||||
| The result of the calculation | ||||||||||||||||||
| """ | ||||||||||||||||||
| try: | ||||||||||||||||||
| # Safe evaluation of mathematical expressions | ||||||||||||||||||
| # In production, use a proper math parser | ||||||||||||||||||
| allowed_chars = set("0123456789+-*/.() ") | ||||||||||||||||||
| if not all(c in allowed_chars for c in expression): | ||||||||||||||||||
| return {"error": "Invalid characters in expression"} | ||||||||||||||||||
|
|
||||||||||||||||||
| result = eval(expression) # Note: Use safer alternative in production | ||||||||||||||||||
| import ast, operator | ||||||||||||||||||
| _OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, | ||||||||||||||||||
| ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv, | ||||||||||||||||||
| ast.Mod: operator.mod, ast.Pow: operator.pow, | ||||||||||||||||||
| ast.USub: operator.neg, ast.UAdd: operator.pos} | ||||||||||||||||||
|
Comment on lines
+53
to
+56
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allowing the exponentiation operator ( Since this is a simple calculator tool, it is safest to remove
Suggested change
|
||||||||||||||||||
| def _ev(n): | ||||||||||||||||||
| if isinstance(n, ast.Expression): return _ev(n.body) | ||||||||||||||||||
| if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value | ||||||||||||||||||
| if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand)) | ||||||||||||||||||
| if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right)) | ||||||||||||||||||
| raise ValueError(f"Unsupported: {ast.dump(n)}") | ||||||||||||||||||
|
Comment on lines
+57
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the relevant portion of the file with line numbers
sed -n '1,120p' examples/python/mcp_server_example.py | nl -ba | sed -n '40,90p'
# Show if there's a Ruff config and whether E701 is enabled
ls -la | head
fd -a "ruff.*" . || true
fd -a "pyproject.toml" . || true
if [ -f pyproject.toml ]; then
echo "---- pyproject.toml ----"
rg -n "ruff|E701" pyproject.toml || true
fi
# Search for E701 mentions in any ruff config files
fd -a -g"*ruff*" . | while read -r f; do
echo "---- $f ----"
rg -n "E701" "$f" || true
doneRepository: MervinPraison/PraisonAI Length of output: 110 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the relevant portion of the file with line numbers
nl -ba examples/python/mcp_server_example.py | sed -n '40,90p'
# Locate Ruff config files
echo "---- Ruff config candidates ----"
fd -a "ruff.*\\.toml|\\.ruff\\.toml|ruff\\.ini|\\.ruff\\.ini" . || true
fd -a "pyproject\\.toml" . || true
fd -a -g"*.toml" "ruff" . || true
# Search for Ruff settings in pyproject.toml and any .ruff*.toml/.ini
for f in $(fd -a "pyproject\\.toml|ruff\\.ini|\\.ruff\\.toml|ruff\\.toml|\\.ruff\\.ini" . || true); do
echo "---- $f ----"
rg -n "ruff|E701|ignore|select" "$f" || true
done
# Additionally, just search repository for the specific line pattern / function name
rg -n "def _ev\\(n\\)" -S . || true
rg -n "Unsupported: \\{ast\\.dump" -S examples/python/mcp_server_example.py || trueRepository: MervinPraison/PraisonAI Length of output: 110 🏁 Script executed: #!/bin/bash
set -euo pipefail
nl -ba examples/python/mcp_server_example.py | sed -n '45,90p'
# Try to find ruff config and E701 enablement
if [ -f pyproject.toml ]; then
rg -n "ruff|E701" pyproject.toml || true
fi
# Search common files quickly
for f in $(fd -a "ruff\\.toml|ruff\\.ini|\\.ruff\\.toml|\\.ruff\\.ini|pyproject\\.toml" . || true); do
echo "---- $f ----"
rg -n "E701" "$f" || true
rg -n "select\\s*=|ignore\\s*=|lint\\.select|lint\\.ignore" "$f" || true
done
# Confirm AST evaluation code exists as described
rg -n "ast\\.Constant" examples/python/mcp_server_example.py || true
rg -n "type\\(n\\.value\\)" examples/python/mcp_server_example.py || trueRepository: MervinPraison/PraisonAI Length of output: 110 🏁 Script executed: #!/bin/bash
set -euo pipefail
nl -ba examples/python/mcp_server_example.py | sed -n '45,90p'
echo "---- Ruff config checks ----"
for f in pyproject.toml ruff.toml .ruff.toml ruff.ini .ruff.ini; do
if [ -f "$f" ]; then
echo "---- $f ----"
rg -n "E701" "$f" || true
rg -n "select\\s*=|ignore\\s*=|lint\\.select|lint\\.ignore" "$f" || true
fi
done
# Search within repository for ruff config
fd -a "ruff\\.toml|ruff\\.ini|\\.ruff\\.toml|\\.ruff\\.ini|pyproject\\.toml" . | while read -r f; do
echo "---- $f ----"
rg -n "E701" "$f" || true
done
# Verify boolean handling in _ev
sed -n '55,70p' examples/python/mcp_server_example.pyRepository: MervinPraison/PraisonAI Length of output: 110 🏁 Script executed: #!/bin/bash
set -euo pipefail
nl -ba examples/python/mcp_server_example.py | sed -n '50,75p'
echo "---- Ruff config (E701) ----"
if [ -f pyproject.toml ]; then
rg -n "E701" pyproject.toml || true
rg -n "lint\\.(select|ignore)" pyproject.toml || true
rg -n "ruff" pyproject.toml || true
fi
# Search for any ruff config files in repo
fd -a "ruff\\.toml|ruff\\.ini|\\.ruff\\.toml|\\.ruff\\.ini|pyproject\\.toml" . | while read -r f; do
echo "---- $f ----"
rg -n "E701" "$f" || true
rg -n "lint\\.(select|ignore)" "$f" || true
done
# Confirm current _ev constant guard
rg -n "if isinstance\\(n, ast\\.Constant\\)" -n examples/python/mcp_server_example.py || trueRepository: MervinPraison/PraisonAI Length of output: 110 🏁 Script executed: trueRepository: MervinPraison/PraisonAI Length of output: 49 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "---- File snippet (lines 45-80) ----"
sed -n '45,80p' examples/python/mcp_server_example.py | cat -n
echo "---- Search for _ev function ----"
rg -n "def _ev\\(n\\)" -n examples/python/mcp_server_example.py || true
rg -n "ast\\.Constant" examples/python/mcp_server_example.py || true
echo "---- Locate Ruff config ----"
# list likely config files (avoid huge walks)
for f in pyproject.toml ruff.toml .ruff.toml ruff.ini .ruff.ini setup.cfg tox.ini .flake8 .flake8.ini; do
[ -f "$f" ] && echo "FOUND: $f"
done
# search common config names anywhere in repo (small output)
fd -a "pyproject\\.toml|ruff\\.toml|\\.ruff\\.toml|ruff\\.ini|\\.ruff\\.ini" . 2>/dev/null || true
echo "---- Ruff config: check E701 / lint.select / lint.ignore ----"
# check top-level and any found config files
configs=(pyproject.toml ruff.toml .ruff.toml ruff.ini .ruff.ini)
for cfg in "${configs[@]}"; do
if [ -f "$cfg" ]; then
echo "---- $cfg ----"
rg -n "E701" "$cfg" || true
rg -n "lint\\.(select|ignore)" "$cfg" || true
rg -n "select\\s*=|ignore\\s*=" "$cfg" || true
rg -n "\\[tool\\.ruff\\]" "$cfg" || true
fi
done
# also search any .ruff* and pyproject.toml across repo for E701
fd -a "pyproject\\.toml|ruff\\.toml|\\.ruff\\.toml|ruff\\.ini|\\.ruff\\.ini" . 2>/dev/null | while read -r f; do
[ -f "$f" ] || continue
rg -n "E701" "$f" && echo "---- $f (E701 match) ----" || true
doneRepository: MervinPraison/PraisonAI Length of output: 2617 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "---- Show ruff.toml select block (first 120 lines) ----"
sed -n '1,120p' ruff.toml
echo "---- Search ruff.toml for E701/E70/E (select block context) ----"
rg -n "E701|E70\\b|\\bE\\b|select\\s*=\\s*\\[" ruff.toml || true
echo "---- Search all repo ruff/pyproject configs for E701/E70 ----"
for f in \
ruff.toml \
src/praisonai/pyproject.toml \
src/praisonai-agents/pyproject.toml \
src/praisonai-platform/pyproject.toml \
examples/python/plugin_template/pyproject.toml \
pyproject.toml; do
if [ -f "$f" ]; then
echo "==== $f ===="
rg -n "E701|E70\\b" "$f" || true
rg -n "\\[tool\\.ruff\\]|select\\s*=|ignore\\s*=" "$f" || true
fi
done
echo "---- Print tool.ruff select from any pyproject.toml found (only relevant lines) ----"
for f in $(fd -a "pyproject\\.toml" . 2>/dev/null); do
rg -n "\\[tool\\.ruff\\]|^select\\s*=|^ignore\\s*=" "$f" >/dev/null 2>&1 && echo "MATCH: $f"
doneRepository: MervinPraison/PraisonAI Length of output: 1899 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Try to run ruff on the file (if ruff is available in the sandbox)
ruff --version || true
ruff check examples/python/mcp_server_example.py || trueRepository: MervinPraison/PraisonAI Length of output: 5343 Exclude booleans from numeric evaluation and fix E701 single-line
Suggested fix def _ev(n):
- if isinstance(n, ast.Expression): return _ev(n.body)
- if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value
- if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand))
- if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
+ if isinstance(n, ast.Expression):
+ return _ev(n.body)
+ if isinstance(n, ast.Constant):
+ if type(n.value) not in (int, float):
+ raise ValueError(f"Unsupported: {ast.dump(n)}")
+ return n.value
+ if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.operand))
+ if isinstance(n, ast.BinOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
raise ValueError(f"Unsupported: {ast.dump(n)}")🧰 Tools🪛 Ruff (0.15.15)[error] 58-58: Multiple statements on one line (colon) (E701) [error] 59-59: Multiple statements on one line (colon) (E701) [error] 60-60: Multiple statements on one line (colon) (E701) [error] 61-61: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||
| result = _ev(ast.parse(expression, mode="eval")) | ||||||||||||||||||
|
Comment on lines
51
to
+63
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate and print the relevant section around the evaluator
FILE="examples/python/mcp_server_example.py"
if [ ! -f "$FILE" ]; then
echo "Missing $FILE"; exit 1
fi
echo "== Snippet context =="
nl -ba "$FILE" | sed -n '1,120p' | sed -n '40,95p'
echo "== Search for calculate / evaluator / expression limits =="
rg -n "def calculate|calculate\\(|_ev\\(|expression\\b|ast\\.Pow|MAX_|depth|depth\\b|SSE|ServerSentEvents|sse|ToolsMCPServer" "$FILE"
echo "== Search for server tool registration =="
rg -n "ToolsMCPServer|register|tool|calculate" "$FILE"
# Check for other similar evaluator implementations in nearby example files for consistency
echo "== Related mcp-tools-server evaluator =="
TARGETS=(examples/python/mcp/mcp-tools-server.py examples/python/mcp_server_example.py)
for t in "${TARGETS[@]}"; do
if [ -f "$t" ]; then
echo "--- $t ---"
rg -n "def calculate|_ev\\(|ast\\.Pow|MAX_|depth|expression\\b" "$t" || true
fi
doneRepository: MervinPraison/PraisonAI Length of output: 151 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="examples/python/mcp_server_example.py"
if [ ! -f "$FILE" ]; then
echo "Missing $FILE"; exit 1
fi
echo "== Snippet context (lines 40-95) =="
# Use sed to print with line numbers via awk (nl may not exist)
awk 'NR>=40 && NR<=95 {printf "%4d:%s\n", NR, $0}' "$FILE"
echo "== Search for calculate / evaluator / expression limits =="
rg -n "def calculate|calculate\\(|_ev\\(|expression\\b|ast\\.Pow|MAX_|depth\\b|SSE|ServerSentEvents|sse|ToolsMCPServer" "$FILE" || true
echo "== Search for server tool registration =="
rg -n "ToolsMCPServer|register|tool|calculate" "$FILE" || true
echo "== Related mcp-tools-server evaluator =="
for t in examples/python/mcp/mcp-tools-server.py examples/python/mcp_server_example.py; do
if [ -f "$t" ]; then
echo "--- $t ---"
rg -n "def calculate|_ev\\(|ast\\.Pow|MAX_|depth\\b|expression\\b" "$t" || true
fi
doneRepository: MervinPraison/PraisonAI Length of output: 5841 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Print relevant context for SSE/tool registration
FILE="examples/python/mcp_server_example.py"
echo "== Registration/SSE wiring (lines 100-170) =="
awk 'NR>=95 && NR<=170 {printf "%4d:%s\n", NR, $0}' "$FILE"
echo "== Check for any existing MAX_/limit/depth logic =="
rg -n "MAX_|limit|depth|exponent|expression.*len|ast\\.walk" "$FILE" || true
# Inspect related file for same evaluator
FILE2="examples/python/mcp/mcp-tools-server.py"
echo "== Related evaluator wiring (mcp-tools-server.py lines 1-120) =="
awk 'NR>=1 && NR<=120 {printf "%4d:%s\n", NR, $0}' "$FILE2"
echo "== Related file any existing limits =="
rg -n "MAX_|limit|depth|exponent|expression.*len" "$FILE2" || true
# Confirm whether boolean constants are allowed by the predicate
echo "== Sanity check: bool is instance of int? (Python runtime) =="
python3 - <<'PY'
print(isinstance(True,(int,float)))
PY
# Confirm ruff E701 possibility by checking for single-line if statements
echo "== Lines with 'if ...: return' patterns (potential E701) =="
rg -n "if .+:\s*return" "$FILE" "$FILE2" || trueRepository: MervinPraison/PraisonAI Length of output: 7985 Put resource limits around this evaluator before serving it.
Suggested guard rails+ MAX_EXPRESSION_LENGTH = 200
+ MAX_DEPTH = 32
+ MAX_EXPONENT = 12
+
+ if len(expression) > MAX_EXPRESSION_LENGTH:
+ raise ValueError("Expression too long")
+
- def _ev(n):
- if isinstance(n, ast.Expression): return _ev(n.body)
+ def _ev(n, depth=0):
+ if depth > MAX_DEPTH:
+ raise ValueError("Expression too complex")
+ if isinstance(n, ast.Expression):
+ return _ev(n.body, depth + 1)
...
- if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
+ if isinstance(n, ast.BinOp) and type(n.op) in _OPS:
+ left = _ev(n.left, depth + 1)
+ right = _ev(n.right, depth + 1)
+ if isinstance(n.op, ast.Pow) and abs(right) > MAX_EXPONENT:
+ raise ValueError("Exponent too large")
+ return _OPS[type(n.op)](left, right)🧰 Tools🪛 Ruff (0.15.15)[error] 58-58: Multiple statements on one line (colon) (E701) [error] 59-59: Multiple statements on one line (colon) (E701) [error] 60-60: Multiple statements on one line (colon) (E701) [error] 61-61: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||
| return {"expression": expression, "result": result} | ||||||||||||||||||
| except Exception as e: | ||||||||||||||||||
| return {"error": str(e)} | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -79,10 +79,20 @@ def get_time(timezone: Literal["est", "pst"]) -> str: | |||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def calculate(expression: str) -> str: | ||||||||||||||||||
| """Calculate a math expression.""" | ||||||||||||||||||
| """Calculate a math expression safely.""" | ||||||||||||||||||
| try: | ||||||||||||||||||
| result = eval(expression) | ||||||||||||||||||
| return f"Result: {result}" | ||||||||||||||||||
| import ast, operator | ||||||||||||||||||
| _OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, | ||||||||||||||||||
| ast.Div: operator.truediv, ast.FloorDiv: operator.floordiv, | ||||||||||||||||||
| ast.Mod: operator.mod, ast.Pow: operator.pow, | ||||||||||||||||||
| ast.USub: operator.neg, ast.UAdd: operator.pos} | ||||||||||||||||||
|
Comment on lines
+85
to
+88
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allowing the exponentiation operator ( Since this is a simple calculator tool, it is safest to remove
Suggested change
|
||||||||||||||||||
| def _ev(n): | ||||||||||||||||||
| if isinstance(n, ast.Expression): return _ev(n.body) | ||||||||||||||||||
| if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value | ||||||||||||||||||
| if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand)) | ||||||||||||||||||
| if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right)) | ||||||||||||||||||
| raise ValueError(f"Unsupported: {ast.dump(n)}") | ||||||||||||||||||
|
Comment on lines
+89
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="src/praisonai-agents/benchmarks/real_benchmark.py"
echo "== File stats =="
wc -l "$FILE" | cat
echo "== Snippet around lines 70-110 =="
nl -ba "$FILE" | sed -n '70,110p'Repository: MervinPraison/PraisonAI Length of output: 234 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Try to locate any other _ev occurrences and any Ruff/flake8 configs mentioning E701
FILE="src/praisonai-agents/benchmarks/real_benchmark.py"
rg -n "def _ev\\b" -S "$FILE" || true
rg -n "E701" -S . || true
rg -n "ruff" -S pyproject.toml setup.cfg .ruff.toml . || trueRepository: MervinPraison/PraisonAI Length of output: 10209 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="src/praisonai-agents/benchmarks/real_benchmark.py"
echo "== _ev() implementation (around line 80-105) =="
awk 'NR>=75 && NR<=105 {printf "%d:%s\n", NR, $0}' "$FILE"
echo
echo "== ruff.toml (top) =="
if [ -f "ruff.toml" ]; then
awk 'NR<=120 {print NR ":" $0}' ruff.toml
else
echo "ruff.toml not found"
fiRepository: MervinPraison/PraisonAI Length of output: 2901 Keep Suggested fix def _ev(n):
- if isinstance(n, ast.Expression): return _ev(n.body)
- if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)): return n.value
- if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.operand))
- if isinstance(n, ast.BinOp) and type(n.op) in _OPS: return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
+ if isinstance(n, ast.Expression):
+ return _ev(n.body)
+ if isinstance(n, ast.Constant):
+ if type(n.value) not in (int, float):
+ raise ValueError(f"Unsupported: {ast.dump(n)}")
+ return n.value
+ if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.operand))
+ if isinstance(n, ast.BinOp) and type(n.op) in _OPS:
+ return _OPS[type(n.op)](_ev(n.left), _ev(n.right))
raise ValueError(f"Unsupported: {ast.dump(n)}")🧰 Tools🪛 Ruff (0.15.15)[error] 90-90: Multiple statements on one line (colon) (E701) [error] 91-91: Multiple statements on one line (colon) (E701) [error] 92-92: Multiple statements on one line (colon) (E701) [error] 93-93: Multiple statements on one line (colon) (E701) 🤖 Prompt for AI Agents |
||||||||||||||||||
| return f"Result: {_ev(ast.parse(expression, mode='eval'))}" | ||||||||||||||||||
| except Exception: | ||||||||||||||||||
| return "Error: Invalid expression" | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,15 +23,37 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _DEFAULT_SECRET = "dev-secret-change-me" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JWT_SECRET = os.environ.get("PLATFORM_JWT_SECRET", _DEFAULT_SECRET) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JWT_SECRET = os.environ.get("PLATFORM_JWT_SECRET") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JWT_ALGORITHM = "HS256" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JWT_TTL_SECONDS = int(os.environ.get("PLATFORM_JWT_TTL", str(30 * 24 * 3600))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if JWT_SECRET == _DEFAULT_SECRET and os.environ.get("PLATFORM_ENV", "dev") != "dev": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise RuntimeError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "PLATFORM_JWT_SECRET must be set to a strong random value in production. " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Set PLATFORM_ENV=dev to suppress this check during development." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if JWT_SECRET is None or JWT_SECRET == _DEFAULT_SECRET: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+26
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Determine if we are in an explicit dev environment. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Only trust PLATFORM_ENV when it is explicitly set — an unset value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # must NOT silently fall back to "dev" because production deployments | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # that forget the variable would run with a well-known signing key. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _explicit_env = os.environ.get("PLATFORM_ENV") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if _explicit_env == "dev": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Developer explicitly opted in to insecure defaults — allow it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JWT_SECRET = _DEFAULT_SECRET | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Either production or environment not specified. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Auto-generate an ephemeral secret so the service can start, but | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # warn loudly because tokens will not survive a restart. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import secrets as _secrets | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import warnings as _warnings | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| JWT_SECRET = _secrets.token_urlsafe(32) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _warnings.warn( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "PLATFORM_JWT_SECRET is not set. Auto-generated an ephemeral " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "signing key — JWT tokens will be invalidated on restart. " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Set PLATFORM_JWT_SECRET to a stable random value for production " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use, or set PLATFORM_ENV=dev to use the development default.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stacklevel=2, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Auto-generating an ephemeral random secret in production when It is highly recommended to fail fast by raising a
Suggested change
Comment on lines
+46
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Comment on lines
+30
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restore fail-fast behavior for explicit non-dev environments. This Suggested fix if JWT_SECRET is None or JWT_SECRET == _DEFAULT_SECRET:
_explicit_env = os.environ.get("PLATFORM_ENV")
if _explicit_env == "dev":
JWT_SECRET = _DEFAULT_SECRET
+ elif _explicit_env is not None:
+ raise RuntimeError(
+ "PLATFORM_JWT_SECRET must be set when PLATFORM_ENV is not 'dev'."
+ )
else:
import secrets as _secrets
import warnings as _warnings
JWT_SECRET = _secrets.token_urlsafe(32)
_warnings.warn(🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Type-narrow: at this point JWT_SECRET is always a non-None str. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert JWT_SECRET is not None # noqa: S105 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class AuthService: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -113,10 +135,6 @@ async def login(self, email: str, password: str) -> Optional[tuple[User, str]]: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _issue_token(self, user: User) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Issue a JWT for a user.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if JWT_SECRET == _DEFAULT_SECRET and os.environ.get("PLATFORM_ENV", "dev") != "dev": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise RuntimeError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Refusing to issue JWT with default PLATFORM_JWT_SECRET outside dev" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| now = datetime.now(timezone.utc) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "sub": user.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allowing the exponentiation operator (
ast.Pow/**) in the safe evaluator introduces a Denial of Service (DoS) vulnerability. An attacker can supply an expression with nested exponentiation (e.g.,9**9**9**9or10**1000000000) that consumes excessive CPU and memory, potentially hanging or crashing the server.Since this is a simple calculator tool, it is safest to remove
ast.Powfrom the allowed operators.