BUG-HUNT: [correctness] ToolRunner host-route accepts NaN/Infinity in tool inputs violating JSON spec #7775

Open
opened 2026-04-12 03:31:49 +00:00 by HAL9000 · 3 comments
Owner

Bug Report: Correctness — ToolRunner.execute() Host-Route Allows NaN/Infinity in Tool Inputs

Severity Assessment

  • Impact: Host-routed tool inputs containing float('nan'), float('inf'), or float('-inf') pass the JSON-serialisability check (because Python's json.dumps allows these by default), then get forwarded to tool handlers. These values then cause silent failures or exceptions when handlers attempt to re-serialize or compare them, or when outputs containing them are returned downstream (e.g., to LLM providers that require strict RFC 7159 JSON).
  • Likelihood: Medium — NaN/Infinity can appear in numeric tool outputs that feed into other tools, or via malformed LLM responses.
  • Priority: Medium

Location

  • File: src/cleveragents/tool/runner.py
  • Function/Class: ToolRunner.execute
  • Lines: 459–470 (host-route JSON validation)

Description

The ToolRunner.execute() method validates tool inputs for JSON-serialisability before calling the handler. For container-routed tools, it explicitly uses json.dumps(inputs, allow_nan=False) (line 413), which rejects NaN/Infinity per RFC 7159. However for host-routed tools, it uses json.dumps(inputs) with the default allow_nan=True (line 463).

Python's json module with allow_nan=True serialises float('nan') as the bare token NaN, which is not valid JSON. Any downstream consumer expecting RFC 7159 compliant JSON (LLM APIs, logging systems, checkpoint serialization) will fail or silently corrupt data.

The code comment at line 461 explicitly acknowledges this as a deliberate inconsistency: # Host-routed tools use the default allow_nan=True for backward compatibility. This "backward compatibility" concern preserves a broken behaviour that violates the documented contract.

Evidence

# src/cleveragents/tool/runner.py lines 459-470
# Container path (lines 413-414) — correct:
try:
    json.dumps(inputs, allow_nan=False)   # <-- RFC-7159-strict
except (TypeError, ValueError) as exc:
    return ToolResult(success=False, error=...)

# Host path (lines 459-470) — inconsistent:
try:
    json.dumps(inputs)    # <-- allow_nan=True (default), accepts NaN/Infinity
except (TypeError, ValueError) as exc:
    return ToolResult(success=False, error=...)

Additionally, the output validation at line 508 also uses the default allow_nan=True:

try:
    json.dumps(raw_output)   # <-- also allow_nan=True for outputs
except (TypeError, ValueError) as exc:
    return ToolResult(success=False, error=...)

Demonstration:

import json, math
output = {"value": float('nan')}
json.dumps(output)            # returns '{"value": NaN}' -- not valid JSON!
json.dumps(output, allow_nan=False)  # raises ValueError -- correct

Expected Behavior

Both host-route and container-route paths should apply the same RFC 7159-compliant JSON validation (allow_nan=False). NaN/Infinity values should be rejected at input validation time with a clear error.

Actual Behavior

Host-route tool inputs (and outputs) containing NaN/Infinity values pass validation and are forwarded to handlers and callers, producing invalid JSON tokens that cause failures downstream.

Suggested Fix

Change both host-route validation calls to use allow_nan=False:

# Line 463 — input validation
try:
    json.dumps(inputs, allow_nan=False)  # was: json.dumps(inputs)
except (TypeError, ValueError) as exc:
    return ToolResult(success=False, output={},
                      error=f"Inputs are not JSON-serialisable: {exc}",
                      duration_ms=0.0)

# Line 508 — output validation  
try:
    json.dumps(raw_output, allow_nan=False)  # was: json.dumps(raw_output)
except (TypeError, ValueError) as exc:
    return ToolResult(success=False, output={},
                      error=f"Output is not JSON-serialisable: {exc}",
                      duration_ms=elapsed)

If true backward compatibility is needed, add a deprecation warning and document the migration path.

Category

correctness

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: Correctness — `ToolRunner.execute()` Host-Route Allows NaN/Infinity in Tool Inputs ### Severity Assessment - **Impact**: Host-routed tool inputs containing `float('nan')`, `float('inf')`, or `float('-inf')` pass the JSON-serialisability check (because Python's `json.dumps` allows these by default), then get forwarded to tool handlers. These values then cause silent failures or exceptions when handlers attempt to re-serialize or compare them, or when outputs containing them are returned downstream (e.g., to LLM providers that require strict RFC 7159 JSON). - **Likelihood**: Medium — NaN/Infinity can appear in numeric tool outputs that feed into other tools, or via malformed LLM responses. - **Priority**: Medium ### Location - **File**: `src/cleveragents/tool/runner.py` - **Function/Class**: `ToolRunner.execute` - **Lines**: 459–470 (host-route JSON validation) ### Description The `ToolRunner.execute()` method validates tool inputs for JSON-serialisability before calling the handler. For **container-routed** tools, it explicitly uses `json.dumps(inputs, allow_nan=False)` (line 413), which rejects NaN/Infinity per RFC 7159. However for **host-routed** tools, it uses `json.dumps(inputs)` with the default `allow_nan=True` (line 463). Python's `json` module with `allow_nan=True` serialises `float('nan')` as the bare token `NaN`, which is not valid JSON. Any downstream consumer expecting RFC 7159 compliant JSON (LLM APIs, logging systems, checkpoint serialization) will fail or silently corrupt data. The code comment at line 461 explicitly acknowledges this as a deliberate inconsistency: `# Host-routed tools use the default allow_nan=True for backward compatibility`. This "backward compatibility" concern preserves a broken behaviour that violates the documented contract. ### Evidence ```python # src/cleveragents/tool/runner.py lines 459-470 # Container path (lines 413-414) — correct: try: json.dumps(inputs, allow_nan=False) # <-- RFC-7159-strict except (TypeError, ValueError) as exc: return ToolResult(success=False, error=...) # Host path (lines 459-470) — inconsistent: try: json.dumps(inputs) # <-- allow_nan=True (default), accepts NaN/Infinity except (TypeError, ValueError) as exc: return ToolResult(success=False, error=...) ``` Additionally, the output validation at line 508 also uses the default `allow_nan=True`: ```python try: json.dumps(raw_output) # <-- also allow_nan=True for outputs except (TypeError, ValueError) as exc: return ToolResult(success=False, error=...) ``` **Demonstration:** ```python import json, math output = {"value": float('nan')} json.dumps(output) # returns '{"value": NaN}' -- not valid JSON! json.dumps(output, allow_nan=False) # raises ValueError -- correct ``` ### Expected Behavior Both host-route and container-route paths should apply the same RFC 7159-compliant JSON validation (`allow_nan=False`). NaN/Infinity values should be rejected at input validation time with a clear error. ### Actual Behavior Host-route tool inputs (and outputs) containing NaN/Infinity values pass validation and are forwarded to handlers and callers, producing invalid JSON tokens that cause failures downstream. ### Suggested Fix Change both host-route validation calls to use `allow_nan=False`: ```python # Line 463 — input validation try: json.dumps(inputs, allow_nan=False) # was: json.dumps(inputs) except (TypeError, ValueError) as exc: return ToolResult(success=False, output={}, error=f"Inputs are not JSON-serialisable: {exc}", duration_ms=0.0) # Line 508 — output validation try: json.dumps(raw_output, allow_nan=False) # was: json.dumps(raw_output) except (TypeError, ValueError) as exc: return ToolResult(success=False, output={}, error=f"Output is not JSON-serialisable: {exc}", duration_ms=elapsed) ``` If true backward compatibility is needed, add a deprecation warning and document the migration path. ### Category correctness ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-12 03:43:51 +00:00
Author
Owner

Verified — Bug: ToolRunner accepts NaN/Infinity in tool inputs, violating JSON spec. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Bug: ToolRunner accepts NaN/Infinity in tool inputs, violating JSON spec. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Bug: ToolRunner accepts NaN/Infinity in tool inputs, violating JSON spec. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Bug: ToolRunner accepts NaN/Infinity in tool inputs, violating JSON spec. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Bug: ToolRunner accepts NaN/Infinity in tool inputs, violating JSON spec. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Bug: ToolRunner accepts NaN/Infinity in tool inputs, violating JSON spec. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#7775
No description provided.