fix: policy_evaluate crash on missing/malformed request (#2894) - #3137
fix: policy_evaluate crash on missing/malformed request (#2894)#3137ruvnet wants to merge 1 commit into
Conversation
…2894) policy_evaluate cast `input.request` straight to `PolicyRequest` with no validation. A missing or malformed `request` argument (including the `policy_evaluate {}` repro from #2894) flowed unchecked into AgenticPolicyEngine.evaluate(), which unconditionally reads `request.requestId` — crashing with a bare "Cannot read properties of undefined (reading 'requestId')" TypeError that surfaced to MCP clients as an opaque JSON-RPC -32603. Add a small validation guard in the policy_evaluate handler that checks `request`/`request.identity`/`request.action` are present before calling evaluatePolicyRequest, so malformed input is now a clear, catchable validation Error instead of an internal crash. Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01KpeUPeLwJtPC5USnJGukv9
|
CI red on this PR is not caused by this diff. Same pre-existing, repo-wide install failure confirmed on #3134, #3135, and #3136 tonight (tracked in #3095/#3101/#3111): Generated by Claude Code |
Fixes #2894 (finding 1 only — see below for findings 2 and 3, which are not addressed here).
What changed
policy_evaluate's handler castinput.requeststraight toPolicyRequestwith no validation:evaluatePolicyRequest→AgenticPolicyEngine.evaluate()(in@claude-flow/security/src/policy/engine.ts) unconditionally readsrequest.requestIdon line 1. Wheninput.requestisundefined— e.g. the issue's exactpolicy_evaluate {}repro, or arguments passed at the top level instead of nested underrequest— this crashes with a bareTypeError: Cannot read properties of undefined (reading 'requestId'), which the MCP dispatcher (src/mcp-server.ts) surfaces to clients as an opaque JSON-RPC-32603— reproduced byte-for-byte against the issue's report.The fix adds a small guard in the
policy_evaluatehandler (v3/@claude-flow/cli/src/mcp-tools/policy-tools.ts) that validatesrequest/request.identity/request.actionare present before callingevaluatePolicyRequest, turning a missing/malformed request into a clear, catchable validationErrorinstead of an internal crash.Testing
New test file:
v3/@claude-flow/cli/__tests__/policy-evaluate-requestId-2894.test.ts, calling the realpolicy_evaluatehandler directly (no mocks).git stash): 2 of 3 tests fail — the handler throws the exact reported message"Cannot read properties of undefined (reading 'requestId')"for bothpolicy_evaluate {}and the issue's "plausible arguments" repro ({action:'read', resource:'file'}, i.e. fields at the top level instead of underrequest).requestnow throws a clear validation error (not the TypeError), and a well-formed request still evaluates normally.Also ran
npx tsc --noEmit: 467 pre-existing errors both before and after this change (confirmed viagit stashcomparison — all pre-existing, unrelated to this file, coming from unbuilt workspace packages in this fresh worktree). No new errors introduced.Diff summary
v3/@claude-flow/cli/src/mcp-tools/policy-tools.ts— addedrequireWellFormedPolicyRequest()guard, called at the top of thepolicy_evaluatehandler (34 insertions / 4 deletions).v3/@claude-flow/cli/__tests__/policy-evaluate-requestId-2894.test.ts— new test file (regression coverage).No CI/workflow, release-tooling, or unrelated files touched.
Findings NOT addressed here (human input needed)
Finding 2 — validation errors returned in-band, outer
isErrornever set. Confirmed real and broader than the issue's framing: the stdio dispatcher (mcp-server.ts'shandleMCPMessage, the actual code path for the reporter'snpx -y @claude-flow/cli@latest+ stdio setup) never setsisErrorat all on itstools/callsuccess path, and there are two different, inconsistent error-signaling conventions already in use across ~50+ tool handlers: some (e.g.security-tools.ts'saidefence_*family,browser-tools.ts,ruvllm-tools.ts,wasm-agent-tools.ts,guidance-tools.ts) return a full MCP-envelope-shaped object ({content, isError:true}) on validation failure, which the dispatcher then re-wraps wholesale intocontent[0].textof a fresh envelope — exactly the "double-wrapped, isError never reaches the outer envelope" bug described in the issue. Others (e.g.memory_searchon missingquery) return a plain data object with anerrorfield and nocontent/isErrorat all, which is a related but structurally different gap. A correct fix requires either normalizing ~50+ handler return shapes to one contract, or teaching the dispatcher a heuristic for "this payload is an error" — both are judgment calls beyond a safe, single-file, unattended nightly patch, so I did not guess at one. Left for a dedicated follow-up.Finding 3 —
swarm_healthvsswarm statusdisagreement. Per the task instructions, not investigated for a fix; the reporter's own caveat (the two commands may be reading different state-store paths) looks plausible from a first read of the two commands, but confirming that needs deliberate product/architecture judgment on what "healthy" should mean withagentCount: 0.Opened by the nightly triage routine — human review required before merge.
Generated by Claude Code