3 minor findings · 2 suggestions
Earlier concerns remain unresolved; see the Run archive for their assessments.
Findings
🟠 Minor · CLI batch handlers can hide the denied method behind -39
When the first batch request is permitted but a later one is denied, bitcoin-cli -getinfo and -netinfo display the generic -39 error without naming the denied method.
Location: src/httprpc.cpp:162 (head).
Details and suggested fix
For example, whitelist getnetworkinfo,getblockchaininfo but not the wallet methods, then run bitcoin-cli -getinfo. Whole-batch refusal gives the first entry -39, and GetinfoRequestHandler::ProcessReply returns that entry before examining the named -38 errors for getwalletinfo and getbalances. The CLI therefore prints the fixed “Not executed because another request in the batch was not permitted” message and exits with status 39. This happens even without wallet support because authorization precedes dispatch. -netinfo has the same limitation when getpeerinfo is permitted and getnetworkinfo is denied. Base returned an empty 403, so this is an incomplete reporting improvement rather than a regression. Have the CLI batch handlers prefer an RPC_METHOD_NOT_PERMITTED entry over -39, and add a -getinfo case whose first requests are permitted.
🟠 Minor · Non-object batch members inherit the previous request's reply metadata
Whitelisted users' non-object batch members now reach a loop that reuses the previous request's id and version, causing duplicate reply ids or suppressed errors.
Location: src/httprpc.cpp:219 (head).
Details and suggested fix
For a user permitted to call getblockcount, [{"jsonrpc":"2.0","method":"getblockcount","id":0},7] now passes the whitelist preflight. The execution loop reuses jreq, and parsing 7 throws before resetting its id or version. The invalid-request error therefore also carries id 0. If the preceding member is a 2.0 notification, the stale IsNotification() state suppresses the error entirely. Base rejected non-object members during whitelist preflight; the shared-loop bug already affected users without a whitelist. No current client sending non-object batch members was identified, so production impact is unconfirmed. Parse each member into a fresh copy of the connection request and add cases after both an identified request and a notification. The existing malformed-object test does not exercise this because parsing that object resets its id.
🟠 Minor · Default-denied malformed requests can now abort Debug builds
Parsing before default denial exposes another authenticated caller class to the existing V2 assumption failure, which aborts Debug builds but returns HTTP 400 in ordinary release builds.
Location: src/httprpc.cpp:189 (head).
Details and suggested fix
With -rpcwhitelistdefault=1 and no whitelist for an authenticated user, send {"jsonrpc":"2.0","method":"getblockcount","params":"bad","id":1}. The singleton path now parses before checking the denial. Parsing records V2 and then throws for the invalid params, so the outer catch calls JSONErrorReply with V2 and violates its Assume. Debug builds enable ABORT_ON_FAILED_ASSUME and abort; ordinary release builds return HTTP 400 with the error body. Base denied this user before parsing, although the same failure was already reachable by other authenticated HTTP users and IPC clients, which supply their own username. Handle validation failures after version detection without entering a helper that assumes the version is not V2, for example by returning the error with HTTP 200 once V2 has been identified.
Optional improvements
🔵 Suggestion · Refused-batch tests do not observe non-execution or default denial
The read-only batch assertions would still pass if permitted members ran and their results were discarded, and default-denied batches have no coverage.
Location: test/functional/rpc_whitelist.py:140 (head).
Details and suggested fix
The permitted members in the “runs none of them” case are both getblockcount, and every assertion inspects only the response. Production correctly returns before executing any member, but the test would not detect executing those reads before constructing the same refusal array. Whitelist setnetworkactive for a test user, send it with [false] before a denied method using rpcpost or a params-capable batch helper, and verify through the unrestricted connection that a previously true getnetworkinfo()["networkactive"] remains true. Keep the reply-shape assertions. Also exercise a batch from strangedude6 under rpcwhitelistdefault=1 and check -38 for each request; its current helper sends only singletons despite the changed default-denied batch path.
🔵 Suggestion · Whitelist response changes lack a release note
Clients that recognize whitelist refusals through HTTP 403 need migration guidance for the new JSON-RPC errors and version-dependent statuses.
Location: src/rpc/protocol.h:79 (head).
Details and suggested fix
Add a PR-specific doc/release-notes-<PR number>.md describing RPC_METHOD_NOT_PERMITTED (-38) and RPC_REQUEST_NOT_EXECUTED (-39). Refused 2.0 calls now return HTTP 200 with an error, and refused batches use HTTP 200 with per-request errors for either protocol version; 2.0 notifications receive no response body and HTTP 204 when no batch replies remain. Legacy singleton denials retain 403 but gain an error body. Also mention that default-denied users now reach request parsing and receive [] for an empty batch. Tell clients that previously branched on whitelist 403 responses to inspect JSON-RPC error codes instead. The general interface documentation already describes the V2 status convention, but it does not document this upgrade transition, and the patch contains no release note.