refactor(wallet-cli): enforce RPC param validation at dispatch time via superstruct#8846
Open
sirtimid wants to merge 12 commits into
Open
refactor(wallet-cli): enforce RPC param validation at dispatch time via superstruct#8846sirtimid wants to merge 12 commits into
sirtimid wants to merge 12 commits into
Conversation
17d10e8 to
c591c6e
Compare
9b0489c to
56d3cf9
Compare
Add an end-to-end suite that spawns the built `mm` CLI as real child processes and drives the full daemon lifecycle over the Unix socket — the gap left by the in-process suites (`socket-integration.test.ts` exercises the transport in-realm; `wallet-factory.e2e.test.ts` constructs a `Wallet` in-process). Covers: `start` (and the already-running guard on a second `start`), `call` returning the SRP-derived account, `status`, `stop`, persistence across a restart (the resume path: the wallet comes back locked rather than re-importing the SRP), `purge`, and the owner-only socket (0600) / data dir (0700). Because it needs the built `dist/` and the native better-sqlite3 addon, it runs as its own jest project (`jest.config.e2e.js`) via a new `test:e2e` script and is excluded from the fast unit `test` run and its 100%-coverage gate. A dedicated `test-wallet-cli-e2e` CI job (Node 20.x and 24.x) builds the dependency subtree and runs it; the per-package `test-*` matrix can't host it because it runs against source with no build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename `fd` to `file descriptor` throughout the daemon spawn code and tests
- Narrow `stopDaemon` stale-cleanup to `absent`/`refused` sockets only: a
`permission`/`timeout`/`protocol` socket may be a wedged or foreign-user
daemon, so it is no longer deleted or reported as a successful stop
- Add a compile-time exhaustiveness guard to `ensureDaemon`'s ping handling so
spawning is reachable only for a positive `absent` result
- Replace the two mutable `{ value: T | null }` boxes with a single
`StartupOutcome` discriminated union
- Simplify/de-duplicate daemon comments; use `as unknown as ChildProcess` in
the spawn mocks; add tests for the new branches
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalize the e2e jest config to discriminate by folder instead of the feature-specific `daemon-e2e` filename suffix, so future subprocess e2e tests just drop into `tests/` with no config change. - jest.config.e2e.js: match `roots: ['<rootDir>/tests']` instead of `**/*.daemon-e2e.test.ts` - jest.config.js: ignore `<rootDir>/tests/`; drop the redundant daemon-e2e coverage exclusion (coverage is collected from `./src/**` only) - Rename lifecycle.daemon-e2e.test.ts -> lifecycle.e2e.test.ts - README: describe the suite by folder The in-process `wallet-factory.e2e.test.ts` stays in `src/` (unit suite): it needs the Web-Crypto polyfill env and is coverage-visible, unlike the subprocess suite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ispatch Adds a `defineHandler(paramsStruct, run)` helper plus a generic `RpcHandler<TParams, TResult>` / `RpcHandlerDefinition` type so each daemon RPC method owns its params struct. `rpc-socket-server` now validates params via `superstruct.validate` once per request and returns `-32602 invalidParams` on shape mismatch, so handler bodies can trust their input. Rewrites `getStatus` and `call` against the new shape and replaces the `wallet.messenger.call as any` cast with a single labelled `RpcDispatcher` bind. Tests updated; coverage stays at 100%. Closes #8777. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove duplicate `test-wallet-cli-e2e` CI job (stale v2 entry merged in), duplicate `testPathIgnorePatterns` key in jest.config.js, and stale eslint-suppressions entry removed by lint:fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
c591c6e to
6cf1fde
Compare
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Guard activeDispose() in shutdown closure with try-catch + log; a throwing dispose during SIGTERM/SIGINT previously skipped PID/socket cleanup silently - Change callParamsStruct type annotation from Json[] to unknown[] for the tail elements, matching what the runtime validator actually checks - Simplify RpcDispatcher return type to Promise<Json> (always awaited) - Fix "narrow" → "consolidate the unsafe cast to" in RpcDispatcher JSDoc - Fix missed handlerDefinition() migration in socket-integration test - Reduce asHandler/handlerDefinition JSDoc blocks to single inline comments - Add tests: getStatus/listActions struct rejection, params-absent → -32602, log-on-handler-throw, dispose-error-during-shutdown Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves #8777.
RpcHandler<TParams, TResult>,RpcHandlerDefinition<TParams, TResult>({ paramsStruct, run }), and adefineHandler(paramsStruct, run)helper indaemon/types.ts.RpcHandlerMapnow maps method names to definitions.rpc-socket-server.tsvalidatesparamsagainst the handler's struct viasuperstruct.validatebefore dispatch and returns-32602 invalidParamson shape mismatch. Each handler body can now trust the shape of its argument.daemon-entry.ts:getStatususesliteral(null).calluses adefined struct that enforces a non-empty[string, ...Json[]]tuple, dropping the ad-hocArray.isArray / typeofchecks from the handler body.wallet.messenger.call as anycast with a single typedRpcDispatcher = messenger.call.bind(messenger)escape hatch.@metamask/superstruct ^3.1.0towallet-cli(already used across the monorepo at this version).Test plan
yarn workspace @metamask/wallet-cli run test— 248 tests pass, 100% line/branch/function/statement coverage on the touched files.yarn lint:eslint packages/wallet-cli/src— clean.yarn constraints— clean.yarn workspace @metamask/wallet-cli run changelog:validate— clean.-32602 invalidParamspath and verifiesrunis not invoked when the struct rejects.callhandler cover both the rejection cases (null, empty, non-string head, non-array) and the accepting case.🤖 Generated with Claude Code
Note
Medium Risk
Changes how the Unix-socket daemon validates and dispatches RPC params (including the full messenger
callsurface); behavior shifts from handler-thrown errors to-32602for bad shapes, with filesystem permissions still the main access control.Overview
Daemon JSON-RPC handlers are now
{ paramsStruct, run }definitions (viadefineHandler) instead of plain async functions.rpc-socket-servervalidates incomingparamswith@metamask/superstructbefore callingrun, returning JSON-RPC-32602 invalidParamswhen validation fails and skipping the handler.getStatusandlistActionsexpectnullparams;calluses a custom struct for a non-empty[action, ...args]tuple, removing in-handler array checks. Messenger dispatch is centralized through a singleRpcDispatcherbind/cast onwallet.messenger.call.Shutdown now logs and continues if
dispose()throws (same pattern ashandle.close()). Tests were updated for the new handler shape and cover struct rejection,-32602, and dispose errors during shutdown.Reviewed by Cursor Bugbot for commit 6dcb9c9. Bugbot is set up for automated code reviews on this repo. Configure here.