@zudojs/rpc 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +120 -17
  2. package/dist/index.d.ts +12 -8
  3. package/dist/index.js +14 -5
  4. package/dist/rpc/client/index.d.ts +5 -0
  5. package/dist/rpc/client/index.js +5 -0
  6. package/dist/rpc/client/rpcClient.core.d.ts +6 -7
  7. package/dist/rpc/client/rpcClient.core.js +32 -46
  8. package/dist/rpc/client/rpcWireError.helper.d.ts +13 -0
  9. package/dist/rpc/client/rpcWireError.helper.js +71 -0
  10. package/dist/rpc/constants/index.d.ts +6 -1
  11. package/dist/rpc/constants/index.js +6 -1
  12. package/dist/rpc/constants/rpcConstants.core.d.ts +11 -0
  13. package/dist/rpc/constants/rpcConstants.core.js +11 -0
  14. package/dist/rpc/context/rpcContext.type.d.ts +7 -0
  15. package/dist/rpc/dispatcher/rpcDispatcher.core.js +14 -1
  16. package/dist/rpc/reliability/cancellation/rpcAbort.helper.d.ts +17 -0
  17. package/dist/rpc/reliability/cancellation/rpcAbort.helper.js +40 -0
  18. package/dist/rpc/reliability/retry/rpcRetry.helper.js +3 -1
  19. package/dist/rpc/reliability/timeout/rpcTimeout.helper.d.ts +5 -3
  20. package/dist/rpc/reliability/timeout/rpcTimeout.helper.js +5 -4
  21. package/dist/rpc/server/index.d.ts +6 -0
  22. package/dist/rpc/server/index.js +5 -0
  23. package/dist/rpc/server/rpcBaseErrorMapping.helper.d.ts +16 -0
  24. package/dist/rpc/server/rpcBaseErrorMapping.helper.js +57 -0
  25. package/dist/rpc/server/rpcErrorMapping.helper.d.ts +35 -0
  26. package/dist/rpc/server/rpcErrorMapping.helper.js +101 -0
  27. package/dist/rpc/server/rpcServer.core.d.ts +0 -7
  28. package/dist/rpc/server/rpcServer.core.js +7 -91
  29. package/dist/rpc/transport/codec/index.d.ts +10 -0
  30. package/dist/rpc/transport/codec/index.js +8 -0
  31. package/dist/rpc/transport/codec/rpcBody.helper.d.ts +36 -0
  32. package/dist/rpc/transport/codec/rpcBody.helper.js +64 -0
  33. package/dist/rpc/transport/codec/rpcCodec.helper.d.ts +41 -0
  34. package/dist/rpc/transport/codec/rpcCodec.helper.js +40 -0
  35. package/dist/rpc/transport/http/index.d.ts +12 -0
  36. package/dist/rpc/transport/http/index.js +10 -0
  37. package/dist/rpc/transport/http/rpcFetchHandler.core.d.ts +36 -0
  38. package/dist/rpc/transport/http/rpcFetchHandler.core.js +97 -0
  39. package/dist/rpc/transport/http/rpcHttpExchange.helper.d.ts +27 -0
  40. package/dist/rpc/transport/http/rpcHttpExchange.helper.js +78 -0
  41. package/dist/rpc/transport/http/rpcHttpStatus.helper.d.ts +16 -0
  42. package/dist/rpc/transport/http/rpcHttpStatus.helper.js +37 -0
  43. package/dist/rpc/transport/http/rpcHttpTransport.core.d.ts +36 -0
  44. package/dist/rpc/transport/http/rpcHttpTransport.core.js +45 -0
  45. package/dist/rpc/transport/index.d.ts +12 -0
  46. package/dist/rpc/transport/index.js +9 -1
  47. package/dist/rpc/transport/memory/index.d.ts +7 -0
  48. package/dist/rpc/transport/memory/index.js +6 -0
  49. package/dist/rpc/transport/memory/rpcMemoryTransport.core.d.ts +42 -0
  50. package/dist/rpc/transport/memory/rpcMemoryTransport.core.js +73 -0
  51. package/dist/rpc/validation/rpcUnsafeKey.helper.d.ts +18 -0
  52. package/dist/rpc/validation/rpcUnsafeKey.helper.js +60 -0
  53. package/dist/rpc/validation/rpcValidation.core.d.ts +11 -2
  54. package/dist/rpc/validation/rpcValidation.core.js +11 -2
  55. package/package.json +9 -7
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Detection of prototype-polluting keys in decoded, untrusted data.
3
+ */
4
+ import { SCHEMA_FORBIDDEN_KEYS } from "@zudojs/constants";
5
+ function isTraversable(value) {
6
+ if (typeof value !== "object" || value === null) {
7
+ return false;
8
+ }
9
+ if (Array.isArray(value)) {
10
+ return true;
11
+ }
12
+ const prototype = Object.getPrototypeOf(value);
13
+ return prototype === Object.prototype || prototype === null;
14
+ }
15
+ /**
16
+ * Returns the first `__proto__`, `constructor` or `prototype` key found
17
+ * anywhere in `value`, or `undefined` when there is none.
18
+ *
19
+ * `JSON.parse` keeps such a key as an ordinary own property, so it reaches
20
+ * handlers intact; the moment one is copied with `Object.assign`, a
21
+ * `for…in` merge or a bracket assignment, it replaces the target's
22
+ * prototype. Transports refuse a frame that carries one rather than
23
+ * silently dropping it.
24
+ *
25
+ * Walks plain objects and arrays only, iteratively (no recursion limit)
26
+ * and cycle-safe, so it is safe on any decoded or in-process value.
27
+ */
28
+ export function findUnsafeKey(value) {
29
+ if (!isTraversable(value)) {
30
+ return undefined;
31
+ }
32
+ const seen = new WeakSet();
33
+ const pending = [value];
34
+ while (pending.length > 0) {
35
+ const node = pending.pop();
36
+ if (seen.has(node)) {
37
+ continue;
38
+ }
39
+ seen.add(node);
40
+ if (Array.isArray(node)) {
41
+ for (const child of node) {
42
+ if (isTraversable(child)) {
43
+ pending.push(child);
44
+ }
45
+ }
46
+ continue;
47
+ }
48
+ for (const key of Object.keys(node)) {
49
+ if (SCHEMA_FORBIDDEN_KEYS.has(key)) {
50
+ return key;
51
+ }
52
+ const child = node[key];
53
+ if (isTraversable(child)) {
54
+ pending.push(child);
55
+ }
56
+ }
57
+ }
58
+ return undefined;
59
+ }
60
+ //# sourceMappingURL=rpcUnsafeKey.helper.js.map
@@ -33,6 +33,13 @@ export interface RPCRequestLimits {
33
33
  * Defaults to `true`.
34
34
  */
35
35
  readonly enforceProcedureNamePattern?: boolean;
36
+ /**
37
+ * Accept `__proto__`, `constructor` and `prototype` as keys inside
38
+ * `payload` and `metadata`. Defaults to `false`: a frame carrying one is
39
+ * refused, because a handler that merges its input into another object
40
+ * would have that object's prototype replaced.
41
+ */
42
+ readonly allowUnsafeKeys?: boolean;
36
43
  }
37
44
  /**
38
45
  * Validates a procedure name.
@@ -53,10 +60,12 @@ export declare function measurePayloadBytes(payload: unknown): number | undefine
53
60
  *
54
61
  * Every caller-controlled part of the frame is bounded: the id by
55
62
  * length, the procedure name by length and pattern, and `payload` plus
56
- * `metadata` by their combined encoded size.
63
+ * `metadata` by their combined encoded size. Neither may carry a
64
+ * prototype-polluting key unless `limits.allowUnsafeKeys` is set.
57
65
  *
58
66
  * @throws {RPCInvalidRequestError} when the frame is malformed, the id is
59
- * over-long, or payload and metadata together exceed the configured limit.
67
+ * over-long, payload and metadata together exceed the configured limit, or
68
+ * either carries a `__proto__`, `constructor` or `prototype` key.
60
69
  */
61
70
  export declare function assertValidRequest(request: unknown, limits?: RPCRequestLimits): asserts request is RPCRequest;
62
71
  /**
@@ -6,6 +6,7 @@
6
6
  * Requests arrive from an untrusted peer over a transport, so these are
7
7
  * the first checks the server runs, not the last.
8
8
  */
9
+ import { findUnsafeKey } from "@zudojs/security";
9
10
  import { RPCInternalError, RPCInvalidRequestError, RPCValidationError, } from "../errors/rpc.errors.js";
10
11
  import { MAX_PROCEDURE_NAME_LENGTH, MAX_RPC_PAYLOAD_SIZE, MAX_RPC_REQUEST_ID_LENGTH, PROCEDURE_NAME_PATTERN, } from "../constants/rpcConstants.core.js";
11
12
  /**
@@ -51,10 +52,12 @@ export function measurePayloadBytes(payload) {
51
52
  *
52
53
  * Every caller-controlled part of the frame is bounded: the id by
53
54
  * length, the procedure name by length and pattern, and `payload` plus
54
- * `metadata` by their combined encoded size.
55
+ * `metadata` by their combined encoded size. Neither may carry a
56
+ * prototype-polluting key unless `limits.allowUnsafeKeys` is set.
55
57
  *
56
58
  * @throws {RPCInvalidRequestError} when the frame is malformed, the id is
57
- * over-long, or payload and metadata together exceed the configured limit.
59
+ * over-long, payload and metadata together exceed the configured limit, or
60
+ * either carries a `__proto__`, `constructor` or `prototype` key.
58
61
  */
59
62
  export function assertValidRequest(request, limits = {}) {
60
63
  if (typeof request !== "object" || request === null) {
@@ -94,6 +97,12 @@ export function assertValidRequest(request, limits = {}) {
94
97
  throw new RPCInvalidRequestError(`Request payload of ${size} bytes exceeds the ${maxBytes} byte limit.`, candidate.procedure);
95
98
  }
96
99
  }
100
+ if (limits.allowUnsafeKeys !== true) {
101
+ const unsafe = findUnsafeKey(candidate.payload) ?? findUnsafeKey(candidate.metadata);
102
+ if (unsafe !== undefined) {
103
+ throw new RPCInvalidRequestError(`Request contains the forbidden key "${unsafe}".`, candidate.procedure);
104
+ }
105
+ }
97
106
  }
98
107
  /**
99
108
  * Converts schema issues into metadata safe to return to a caller.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/rpc",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Remote procedure call infrastructure for Zudojs applications.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -27,14 +27,16 @@
27
27
  "node": ">=24.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@zudojs/errors": "1.2.0",
31
- "@zudojs/constants": "1.1.1",
32
- "@zudojs/types": "1.1.1",
33
- "@zudojs/schema": "1.1.1"
30
+ "@zudojs/constants": "1.1.2",
31
+ "@zudojs/errors": "1.3.0",
32
+ "@zudojs/schema": "1.2.0",
33
+ "@zudojs/security": "1.3.0",
34
+ "@zudojs/serialization": "1.2.0",
35
+ "@zudojs/types": "1.2.0"
34
36
  },
35
37
  "devDependencies": {
36
38
  "typescript": "7.0.2",
37
- "vitest": "^4.1.11"
39
+ "vitest": "^5.0.1"
38
40
  },
39
41
  "publishConfig": {
40
42
  "access": "public"
@@ -45,7 +47,7 @@
45
47
  "remote",
46
48
  "procedure"
47
49
  ],
48
- "homepage": "https://github.com/oyinlola-tech/zudo#readme",
50
+ "homepage": "https://zudojs.oyinlola.site/docs/packages-rpc",
49
51
  "bugs": {
50
52
  "url": "https://github.com/oyinlola-tech/zudo/issues"
51
53
  },