Skip to content

fix(cua-driver): reject malformed tool arguments found by fuzzing - #3545

Open
r33drichards wants to merge 2 commits into
test/cua-driver-tool-boundary-fuzzfrom
fix/cua-driver-tool-boundary-fuzz-findings
Open

fix(cua-driver): reject malformed tool arguments found by fuzzing#3545
r33drichards wants to merge 2 commits into
test/cua-driver-tool-boundary-fuzzfrom
fix/cua-driver-tool-boundary-fuzz-findings

Conversation

@r33drichards

Copy link
Copy Markdown
Collaborator

Summary

Stacked on #3544. Fixes the two defects the new tool-call boundary fuzzer found on its first runs, keeps the crash inputs as regression seeds, and narrows the schema-agreement invariant to what the contract parser actually enforces so the fuzz workflow is green.

Fix 1: non-object arguments panicked the dispatcher

MCP types arguments as an object and Request::tool_call substitutes {} when it is absent, but a client can still send any JSON value. A value like true for a registered tool reached the session-stamping index assignment in ToolRegistry::invoke_authorized and panicked. The stdio proxy dispatches inline, so one malformed request from an MCP client terminated the driver process.

invoke_authorized now refuses non-object arguments with an invalid_arguments refusal (same structured shape parse_typed_input uses). Unknown tool names still win over malformed arguments.

  • Regression test: crates/cua-driver-core/tests/tool_arguments_boundary.rs
  • Seed: fuzz/corpus/mcp_request/bool_arguments.json

Fix 2: hotkey.keys parser ignored the advertised two-key minimum

The published schema says minItems: 2 and every platform runtime refuses fewer than two keys, but HotkeyInput accepted {"keys": []} and {"keys": ["ctrl"]}. The parser now enforces the minimum through a deserialize_with check, so a typed input can never carry a combination the runtimes would refuse.

  • Unit test: cua-driver-contract hotkey_parser_enforces_the_published_two_key_minimum
  • Seeds: fuzz/corpus/tool_arguments/hotkey_empty_keys.bin, fuzz/corpus/typed_input_json/hotkey_empty_keys_sequence.json

Triage: advertised range/length bounds are runtime-enforced

After the hotkey fix the fuzzer immediately hit the same class on set_agent_cursor_theme.theme_id (schema minLength: 1, parser accepts ""). This is systemic: the contract schemas advertise numeric range and length bounds on nine fields (pids, click count, drag steps, scroll amount, theme id and detail lengths, menu path shape) that the typed parser does not enforce and the platform runtimes check later. The fuzz invariant now checks structural agreement only (types, required keys, enums, unknown properties) and ignores those keywords; the design note lists every affected field. Enforcing them in the parser the way hotkey.keys now is, then dropping the filter in boundary_fuzz::is_value_bound_error, is a follow-up.

Validation

  • cargo test -p cua-driver-core -p cua-driver-contract -p cua-driver-testkit --all-targets passes on nixpkgs Rust 1.91 (core: 587 tests, contract: 33, smoke: 6).
  • cargo test -p cua-driver --all-targets --no-run compiles.
  • Each fuzz target ran 90 s clean on stable after the fixes: mcp_request 1.27M execs, registry_invoke 0.94M, tool_arguments 1.51M, typed_input_json 1.76M.
  • cargo fmt --all -- --check clean.

Releasing as a patch: the dispatcher panic is user-visible (a malformed MCP request could kill the driver).

🤖 Generated with Claude Code

https://claude.ai/code/session_01UcJkf8QAvMxw7YoPZ1SvFv

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes directly address confirmed crash/contract defects with focused boundary checks plus regression tests and fuzz seeds, without introducing risky behavioral changes outside malformed-input handling.

Pull request overview

Hardens the Cua Driver MCP tool-call dispatch boundary based on new fuzzing results, preventing malformed client requests from crashing the driver and aligning typed parsing behavior with the published contract where the runtimes already enforce stricter rules.

Changes:

  • Reject non-object arguments at the ToolRegistry::invoke_authorized boundary with a structured invalid_arguments refusal (instead of panicking).
  • Enforce the published minItems: 2 requirement for hotkey.keys during typed parsing via a custom deserialize_with validator.
  • Keep discovered crash inputs as fuzz regression seeds, and narrow the fuzz schema invariant to structural agreement by filtering out range/length keyword failures until parser enforcement is completed.
File summaries
File Description
libs/cua-driver/rust/crates/cua-driver-core/src/tool.rs Adds a top-level guard rejecting non-object arguments before any indexing/normalization work occurs.
libs/cua-driver/rust/crates/cua-driver-core/tests/tool_arguments_boundary.rs Adds regression tests ensuring malformed arguments are rejected (and unknown tools still error as unknown).
libs/cua-driver/rust/crates/cua-driver-contract/src/inputs.rs Enforces hotkey.keys minimum length during deserialization and adds a unit test for the schema/parser agreement.
libs/cua-driver/rust/crates/cua-driver-testkit/src/boundary_fuzz.rs Adjusts the fuzz invariant to ignore range/length keyword mismatches while keeping structural validation.
libs/cua-driver/docs/2026-09-04-tool-call-boundary-fuzzing-design.md Updates the design note with first results and documents the temporary invariant relaxation and follow-up.
libs/cua-driver/rust/fuzz/corpus/mcp_request/bool_arguments.json Adds a seed that previously triggered the dispatcher panic via non-object arguments.
libs/cua-driver/rust/fuzz/corpus/tool_arguments/hotkey_empty_keys.bin Adds a seed covering the hotkey.keys too-short case.
libs/cua-driver/rust/fuzz/corpus/typed_input_json/hotkey_empty_keys_sequence.json Adds a typed-input JSON seed covering the hotkey.keys too-short case.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@injaneity injaneity left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

notice that ignoring every range/length validation error makes ci green, but stops detecting regressions in bounds already enforced, including hotkey’s minimum. maybe we should create a follow up issue / pr for it? otherwise looks good

r33drichards and others added 2 commits September 5, 2026 11:38
Fix the two defects the new tool-call boundary fuzzer found on its first
runs and keep the crash inputs as regression seeds.

A non-object `arguments` value (for example `true`) for any registered tool
reached the session-stamping index assignment in
`ToolRegistry::invoke_authorized` and panicked. The stdio proxy dispatches
inline, so one malformed request from an MCP client terminated the driver
process. Dispatch now refuses non-object arguments with an
`invalid_arguments` refusal; unknown tool names still win.

`HotkeyInput` advertised `minItems: 2` for `keys` and every platform runtime
refuses fewer, but the contract parser accepted `{"keys": []}`. The parser
now enforces the minimum.

The fuzzer then hit the same class on other advertised range and length
bounds (nine fields) that the parser does not enforce and the runtimes check
later. The schema-agreement invariant now checks structural agreement only
and the design note lists every affected field as a follow-up.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UcJkf8QAvMxw7YoPZ1SvFv
The doc comment added with the `deserialize_with` check became a schema
description and made the checked-in contract manifest stale. Use a plain
comment so the parser-only fix leaves the published contract unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UcJkf8QAvMxw7YoPZ1SvFv
@r33drichards
r33drichards force-pushed the fix/cua-driver-tool-boundary-fuzz-findings branch from 2680f62 to a1823c9 Compare September 5, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants