diff --git a/examples/eval/reliability_example.py b/examples/eval/reliability_example.py index ee050ff95..e5fb646b6 100644 --- a/examples/eval/reliability_example.py +++ b/examples/eval/reliability_example.py @@ -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)}") + return str(_ev(ast.parse(expression, mode="eval"))) # Check if we have an API key diff --git a/examples/mcp/multi_server.py b/examples/mcp/multi_server.py index 243dfb558..3671d5721 100644 --- a/examples/mcp/multi_server.py +++ b/examples/mcp/multi_server.py @@ -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, + } + + 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)}") + 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)}" diff --git a/examples/mcp_server/custom_tools_server.py b/examples/mcp_server/custom_tools_server.py index 4bac47e58..9731c26f6 100644 --- a/examples/mcp_server/custom_tools_server.py +++ b/examples/mcp_server/custom_tools_server.py @@ -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} + 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'))}" except Exception as e: return f"Error: {e}" diff --git a/examples/python/a2a/a2a-server.py b/examples/python/a2a/a2a-server.py index ec4c40d4a..05453e91e 100644 --- a/examples/python/a2a/a2a-server.py +++ b/examples/python/a2a/a2a-server.py @@ -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, + 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)}" except Exception: return "Invalid expression" diff --git a/examples/python/custom_tools/tools_with_latency.py b/examples/python/custom_tools/tools_with_latency.py index 42f505210..4b55c9f61 100644 --- a/examples/python/custom_tools/tools_with_latency.py +++ b/examples/python/custom_tools/tools_with_latency.py @@ -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} + 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)}") + result = _ev(ast.parse(expression, mode="eval")) return f"Result: {result}" except Exception as e: return f"Error: {str(e)}" diff --git a/examples/python/mcp/mcp-tools-server-sse.py b/examples/python/mcp/mcp-tools-server-sse.py index bde832d22..112b1b608 100644 --- a/examples/python/mcp/mcp-tools-server-sse.py +++ b/examples/python/mcp/mcp-tools-server-sse.py @@ -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} + 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 {"expression": expression, "result": _ev(ast.parse(expression, mode="eval"))} except Exception as e: return {"error": str(e)} diff --git a/examples/python/mcp/mcp-tools-server.py b/examples/python/mcp/mcp-tools-server.py index a20d08259..aa9c86abe 100644 --- a/examples/python/mcp/mcp-tools-server.py +++ b/examples/python/mcp/mcp-tools-server.py @@ -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} + 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)}") + result = _ev(ast.parse(expression, mode="eval")) return {"expression": expression, "result": result} except Exception as e: return {"error": str(e)} diff --git a/examples/python/mcp_server_example.py b/examples/python/mcp_server_example.py index 6c4deb908..06f4a289d 100644 --- a/examples/python/mcp_server_example.py +++ b/examples/python/mcp_server_example.py @@ -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} + 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)}") + result = _ev(ast.parse(expression, mode="eval")) return {"expression": expression, "result": result} except Exception as e: return {"error": str(e)} diff --git a/src/praisonai-agents/benchmarks/real_benchmark.py b/src/praisonai-agents/benchmarks/real_benchmark.py index abb07acd6..0fea65935 100644 --- a/src/praisonai-agents/benchmarks/real_benchmark.py +++ b/src/praisonai-agents/benchmarks/real_benchmark.py @@ -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} + 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'))}" except Exception: return "Error: Invalid expression" diff --git a/src/praisonai-platform/praisonai_platform/services/auth_service.py b/src/praisonai-platform/praisonai_platform/services/auth_service.py index 061846a93..26cde6456 100644 --- a/src/praisonai-platform/praisonai_platform/services/auth_service.py +++ b/src/praisonai-platform/praisonai_platform/services/auth_service.py @@ -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: + # 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, + ) + +# 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,