Files
WRNexusJS/packages/rpc
ClintchizandClaude Opus 5 7c4b484d0a fix(rpc): close prototype-chain permission bypass, add server/client/transport tests
C1 CRITICAL: implement() looked up procedures/handlers with plain property
indexing, so any Object.prototype member name (constructor, toString, etc.)
resolved truthy and skipped the permission gate entirely. Fixed with
Object.hasOwn checks in packages/rpc/src/server.ts. Defense-in-depth guard
added in packages/dev-server/src/rpc-dispatch.ts constraining URL path
segments to a safe charset before they reach service/procedure lookups.

Added missing direct test coverage for packages/rpc/src/transport.ts,
server.ts and client.ts (previously untested), including a prototype-name
sweep in both server.test.ts and dev-server's rpc-endpoint.test.ts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 19:46:59 +05:30
..

@wrnexus/rpc

Define a service contract in a shared workspace package, then import that same contract from the caller and callee.

import {
  defineService,
  implement,
  inProcessTransport,
  procedure,
  serviceClient,
} from "@wrnexus/rpc";
import { v } from "@wrnexus/validation";

const greeter = defineService({
  name: "greeter",
  procedures: {
    greet: procedure
      .input(v.object({ name: v.string() }))
      .output<{ message: string }>()
      .build(),
  },
});

const service = implement(
  greeter,
  { greet: async ({ name }) => ({ message: `Hello, ${name}` }) },
  { selfApp: "greeter" },
);

const client = serviceClient(greeter, {
  app: "greeter",
  transport: inProcessTransport({
    "greeter/greet": (input, identity) => service.invoke("greet", input, identity),
  }),
});
await client.greet({ name: "Ada" });

Service files default-export implement(...) from app/services. The development server mounts them under the private /__wrnexus/rpc prefix.

Pass { as: ctx } to serviceClient to propagate the subject. The signed token contains only subject and tenant identifiers; permissions are always checked by the callee. Set WRNEXUS_RPC_SECRET in every app, use at least 32 characters, and never reuse the session secret.

Calls time out by default. Retrying is intentionally deferred; when introduced, only procedures marked .idempotent() may be retried.