{"record":{"id":"7c40c8291f1fb238","repo":"HKUDS/Vibe-Trading","slug":"only-numeric-constants-are-allowed","errorCode":null,"errorMessage":"only numeric constants are allowed","messagePattern":"only numeric constants are allowed","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/tools/financial_rigor_tool.py","lineNumber":99,"sourceCode":"    return f\"{value:,.2f}\"\n\n\ndef _eval_arith_node(node: ast.AST) -> Decimal:\n    \"\"\"Recursively evaluate an arithmetic AST node in the Decimal domain.\n\n    Args:\n        node: An AST node from a parsed expression.\n\n    Returns:\n        The exact Decimal value of the node.\n\n    Raises:\n        ValueError: If the node is not a supported numeric/arithmetic form.\n    \"\"\"\n    if isinstance(node, ast.Constant):\n        # bool is a subclass of int — reject it explicitly.\n        if isinstance(node.value, bool) or not isinstance(node.value, (int, float)):\n            raise ValueError(\"only numeric constants are allowed\")\n        return _exact(node.value)\n    if isinstance(node, ast.BinOp):\n        op_fn = _AST_BINOPS.get(type(node.op))\n        if op_fn is None:\n            raise ValueError(f\"unsupported operator: {type(node.op).__name__}\")\n        return op_fn(_eval_arith_node(node.left), _eval_arith_node(node.right))\n    if isinstance(node, ast.UnaryOp):\n        op_fn = _AST_UNARYOPS.get(type(node.op))\n        if op_fn is None:\n            raise ValueError(f\"unsupported unary operator: {type(node.op).__name__}\")\n        return op_fn(_eval_arith_node(node.operand))\n    raise ValueError(f\"disallowed element in expression: {type(node).__name__}\")\n\n\ndef _safe_arith(expr: str) -> Decimal:\n    \"\"\"Evaluate a numeric arithmetic expression in the exact-Decimal domain.\n\n    The expression is parsed and evaluated recursively with Decimal arithmetic,","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/tools/financial_rigor_tool.py#L81-L117","documentation":"The tool evaluates user-supplied arithmetic expressions by walking the AST and only allows numeric int/float constants (bools explicitly rejected since bool subclasses int) plus a whitelist of binary operators. Any non-numeric constant — strings, None, True/False — raises this ValueError, preventing code injection and type confusion in exact arithmetic.","triggerScenarios":"Passing an expression containing a string literal like 100 * '2', booleans like True + 1, None, or any non-AST-constant numeric form into _safe_arith; unsupported operators raise a sibling error instead.","commonSituations":"LLM-generated formulas quoting numbers as strings; formulas using boolean flags; users pasting full Python expressions (variables, function calls) that reach the constant check via unexpected node types.","solutions":["Ensure the expression contains only bare numeric literals and whitelisted operators","Remove quotes around numbers ('2' -> 2) and replace True/False with 1/0 if arithmetic is intended","Pre-validate the expression string with a regex/ast scan allowing only Constant(int/float) and whitelisted BinOps before evaluation"],"exampleFix":"# before\n_safe_arith(\"100 * '2'\")   # string constant -> ValueError\n_safe_arith(\"price * 1.1\")  # names are not constants\n# after\n_safe_arith(\"100 * 2\")\n_safe_arith(\"110.00000000000001\")","handlingStrategy":"validation","validationCode":"import ast, re\n\nNUM_EXPR = re.compile(r\"^[\\d\\s.()+\\-*/%]+$$\")\n\ndef looks_safe(expr: str) -> bool:\n    if not NUM_EXPR.match(expr):\n        return False\n    try:\n        tree = ast.parse(expr, mode=\"eval\")\n    except SyntaxError:\n        return False\n    return all(\n        isinstance(n, (ast.Expression, ast.BinOp, ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod, ast.USub, ast.UAdd, ast.Load))\n        or (isinstance(n, ast.Constant) and isinstance(n.value, (int, float)) and not isinstance(n.value, bool))\n        for n in ast.walk(tree)\n    )","typeGuard":"def is_numeric_literal(node: ast.AST) -> bool:\n    return (\n        isinstance(node, ast.Constant)\n        and not isinstance(node.value, bool)\n        and isinstance(node.value, (int, float))\n    )","tryCatchPattern":"try:\n    value = _safe_arith(expr)\nexcept ValueError as exc:\n    if \"only numeric constants\" in str(exc):\n        expr = re.sub(r\"'([\\d.]+)'\", r\"\\1\", expr)  # unquote numeric strings\n        value = _safe_arith(expr)\n    else:\n        raise","preventionTips":["Reject expression strings containing quotes, letters, or True/False before evaluation","Pre-validate with an AST walk that only numeric Constant nodes and whitelisted operators appear","Never pass raw user/LLM text into arithmetic evaluators without sanitization"],"tags":["validation","arithmetic","ast","sandboxing","python"],"backgroundTag":"unsafe-expression-rejected","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}