capnweb 0.9.0 → 0.10.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.
@@ -198,6 +198,13 @@ declare class RpcPayload {
198
198
  * ```
199
199
  */
200
200
  type EncodingLevel = "string" | "jsonCompatible" | "jsonCompatibleWithBytes" | "structuredClonable";
201
+ interface RpcLimits {
202
+ maxBigIntDigits: number;
203
+ maxDepth: number;
204
+ maxMessageSize: number;
205
+ }
206
+ declare const DEFAULT_MAX_DEPTH = 256;
207
+ declare const DEFAULT_LIMITS: RpcLimits;
201
208
  /**
202
209
  * Serialize a value, using Cap'n Web's underlying serialization. This won't be able to serialize
203
210
  * RPC stubs, but it will support basic data types.
@@ -299,6 +306,16 @@ type RpcSessionOptions = {
299
306
  * to serialize the error with the stack omitted.
300
307
  */
301
308
  onSendError?: (error: Error) => Error | void;
309
+ /**
310
+ * Overrides for the resource limits enforced while deserializing messages from the peer. Any
311
+ * field left unset falls back to `DEFAULT_LIMITS`. These guard against resource-exhaustion
312
+ * attacks from untrusted peers; see `RpcLimits` for the meaning and defaults of each field.
313
+ *
314
+ * Limits are a purely local, receiver-side decision -- the protocol has no negotiation step, so
315
+ * the peer never learns these values. A message that exceeds a limit is rejected, aborting the
316
+ * session.
317
+ */
318
+ limits?: Partial<RpcLimits>;
302
319
  };
303
320
  //#endregion
304
321
  //#region src/websocket.d.ts
@@ -445,7 +462,7 @@ declare let newMessagePortRpcSession: <T extends RpcCompatible<T> = Empty$1>(por
445
462
  * credentials as parameters and returns the authorized API), then cross-origin requests should
446
463
  * be safe.
447
464
  */
448
- declare function newWorkersRpcResponse(request: Request, localMain: any): Promise<Response>;
465
+ declare function newWorkersRpcResponse(request: Request, localMain: any, options?: RpcSessionOptions): Promise<Response>;
449
466
  //#endregion
450
467
  //#region src/bun.d.ts
451
468
  type WsData = {
@@ -496,5 +513,5 @@ declare let newBunWebSocketRpcSession: <T extends RpcCompatible<T> = Empty, D =
496
513
  transport: BunWebSocketTransport<D>;
497
514
  };
498
515
  //#endregion
499
- export { type AnyRpcTransport, BunWebSocketTransport, type EncodingLevel, type RpcCompatible, RpcPromise, RpcSession, type RpcSessionOptions, RpcStub, RpcTarget, type RpcTransport, type RpcTransportWithCustomEncoding, WebSocketTransport, deserialize, newBunWebSocketRpcHandler, newBunWebSocketRpcSession, newHttpBatchRpcResponse, newHttpBatchRpcSession, newMessagePortRpcSession, newWebSocketRpcSession, newWorkersRpcResponse, newWorkersWebSocketRpcResponse, nodeHttpBatchRpcResponse, serialize };
516
+ export { type AnyRpcTransport, BunWebSocketTransport, DEFAULT_LIMITS, DEFAULT_MAX_DEPTH, type EncodingLevel, type RpcCompatible, type RpcLimits, RpcPromise, RpcSession, type RpcSessionOptions, RpcStub, RpcTarget, type RpcTransport, type RpcTransportWithCustomEncoding, WebSocketTransport, deserialize, newBunWebSocketRpcHandler, newBunWebSocketRpcSession, newHttpBatchRpcResponse, newHttpBatchRpcSession, newMessagePortRpcSession, newWebSocketRpcSession, newWorkersRpcResponse, newWorkersWebSocketRpcResponse, nodeHttpBatchRpcResponse, serialize };
500
517
  //# sourceMappingURL=index-bun.d.cts.map
@@ -198,6 +198,13 @@ declare class RpcPayload {
198
198
  * ```
199
199
  */
200
200
  type EncodingLevel = "string" | "jsonCompatible" | "jsonCompatibleWithBytes" | "structuredClonable";
201
+ interface RpcLimits {
202
+ maxBigIntDigits: number;
203
+ maxDepth: number;
204
+ maxMessageSize: number;
205
+ }
206
+ declare const DEFAULT_MAX_DEPTH = 256;
207
+ declare const DEFAULT_LIMITS: RpcLimits;
201
208
  /**
202
209
  * Serialize a value, using Cap'n Web's underlying serialization. This won't be able to serialize
203
210
  * RPC stubs, but it will support basic data types.
@@ -299,6 +306,16 @@ type RpcSessionOptions = {
299
306
  * to serialize the error with the stack omitted.
300
307
  */
301
308
  onSendError?: (error: Error) => Error | void;
309
+ /**
310
+ * Overrides for the resource limits enforced while deserializing messages from the peer. Any
311
+ * field left unset falls back to `DEFAULT_LIMITS`. These guard against resource-exhaustion
312
+ * attacks from untrusted peers; see `RpcLimits` for the meaning and defaults of each field.
313
+ *
314
+ * Limits are a purely local, receiver-side decision -- the protocol has no negotiation step, so
315
+ * the peer never learns these values. A message that exceeds a limit is rejected, aborting the
316
+ * session.
317
+ */
318
+ limits?: Partial<RpcLimits>;
302
319
  };
303
320
  //#endregion
304
321
  //#region src/websocket.d.ts
@@ -445,7 +462,7 @@ declare let newMessagePortRpcSession: <T extends RpcCompatible<T> = Empty$1>(por
445
462
  * credentials as parameters and returns the authorized API), then cross-origin requests should
446
463
  * be safe.
447
464
  */
448
- declare function newWorkersRpcResponse(request: Request, localMain: any): Promise<Response>;
465
+ declare function newWorkersRpcResponse(request: Request, localMain: any, options?: RpcSessionOptions): Promise<Response>;
449
466
  //#endregion
450
467
  //#region src/bun.d.ts
451
468
  type WsData = {
@@ -496,5 +513,5 @@ declare let newBunWebSocketRpcSession: <T extends RpcCompatible<T> = Empty, D =
496
513
  transport: BunWebSocketTransport<D>;
497
514
  };
498
515
  //#endregion
499
- export { type AnyRpcTransport, BunWebSocketTransport, type EncodingLevel, type RpcCompatible, RpcPromise, RpcSession, type RpcSessionOptions, RpcStub, RpcTarget, type RpcTransport, type RpcTransportWithCustomEncoding, WebSocketTransport, deserialize, newBunWebSocketRpcHandler, newBunWebSocketRpcSession, newHttpBatchRpcResponse, newHttpBatchRpcSession, newMessagePortRpcSession, newWebSocketRpcSession, newWorkersRpcResponse, newWorkersWebSocketRpcResponse, nodeHttpBatchRpcResponse, serialize };
516
+ export { type AnyRpcTransport, BunWebSocketTransport, DEFAULT_LIMITS, DEFAULT_MAX_DEPTH, type EncodingLevel, type RpcCompatible, type RpcLimits, RpcPromise, RpcSession, type RpcSessionOptions, RpcStub, RpcTarget, type RpcTransport, type RpcTransportWithCustomEncoding, WebSocketTransport, deserialize, newBunWebSocketRpcHandler, newBunWebSocketRpcSession, newHttpBatchRpcResponse, newHttpBatchRpcSession, newMessagePortRpcSession, newWebSocketRpcSession, newWorkersRpcResponse, newWorkersWebSocketRpcResponse, nodeHttpBatchRpcResponse, serialize };
500
517
  //# sourceMappingURL=index-bun.d.ts.map
package/dist/index-bun.js CHANGED
@@ -929,6 +929,12 @@ var PromiseStubHook = class PromiseStubHook extends StubHook {
929
929
 
930
930
  //#endregion
931
931
  //#region src/serialize.ts
932
+ const DEFAULT_MAX_DEPTH = 256;
933
+ const DEFAULT_LIMITS = {
934
+ maxBigIntDigits: 16384,
935
+ maxDepth: 256,
936
+ maxMessageSize: 32 * 1024 * 1024
937
+ };
932
938
  var NullExporter = class {
933
939
  exportStub(stub) {
934
940
  throw new Error("Cannot serialize RPC stubs without an RPC session.");
@@ -948,7 +954,7 @@ async function streamToBlob(stream, type) {
948
954
  let b = await new Response(stream).blob();
949
955
  return b.type === type ? b : b.slice(0, b.size, type);
950
956
  }
951
- const ERROR_TYPES = {
957
+ const ERROR_TYPES = Object.assign(Object.create(null), {
952
958
  Error,
953
959
  EvalError,
954
960
  RangeError,
@@ -957,7 +963,7 @@ const ERROR_TYPES = {
957
963
  TypeError,
958
964
  URIError,
959
965
  AggregateError
960
- };
966
+ });
961
967
  var Devaluator = class Devaluator {
962
968
  exporter;
963
969
  source;
@@ -980,7 +986,7 @@ var Devaluator = class Devaluator {
980
986
  }
981
987
  exports;
982
988
  devaluateImpl(value, parent, depth) {
983
- if (depth >= 64) throw new Error("Serialization exceeded maximum allowed depth. (Does the message contain cycles?)");
989
+ if (depth >= 256) throw new Error("Serialization exceeded maximum allowed depth. (Does the message contain cycles?)");
984
990
  switch (typeForRpc(value)) {
985
991
  case "unsupported": {
986
992
  let msg;
@@ -1214,6 +1220,9 @@ var NullImporter = class {
1214
1220
  getPipeReadable(exportId) {
1215
1221
  throw new Error("Cannot retrieve pipe readable without an RPC session.");
1216
1222
  }
1223
+ getLimits() {
1224
+ return DEFAULT_LIMITS;
1225
+ }
1217
1226
  };
1218
1227
  const NULL_IMPORTER = new NullImporter();
1219
1228
  function fixBrokenRequestBody(request, body) {
@@ -1231,16 +1240,21 @@ function streamToBlobPromise(stream, type) {
1231
1240
  var Evaluator = class Evaluator {
1232
1241
  importer;
1233
1242
  encodingLevel;
1243
+ limits;
1234
1244
  constructor(importer, encodingLevel = "string") {
1235
1245
  this.importer = importer;
1236
1246
  this.encodingLevel = encodingLevel;
1247
+ this.limits = importer.getLimits();
1237
1248
  }
1238
1249
  hooks = [];
1239
1250
  promises = [];
1240
1251
  evaluate(value) {
1252
+ return this.evaluateWithDepth(value, 0);
1253
+ }
1254
+ evaluateWithDepth(value, depth) {
1241
1255
  let payload = RpcPayload.forEvaluate(this.hooks, this.promises);
1242
1256
  try {
1243
- payload.value = this.evaluateImpl(value, payload, "value");
1257
+ payload.value = this.evaluateImpl(value, payload, "value", depth);
1244
1258
  return payload;
1245
1259
  } catch (err) {
1246
1260
  payload.dispose();
@@ -1250,18 +1264,25 @@ var Evaluator = class Evaluator {
1250
1264
  evaluateCopy(value) {
1251
1265
  return this.evaluate(structuredClone(value));
1252
1266
  }
1253
- evaluateImpl(value, parent, property) {
1267
+ evaluateImpl(value, parent, property, depth) {
1268
+ let maxDepth = this.limits.maxDepth;
1269
+ if (depth >= maxDepth) throw new TypeError(`Deserialization exceeded maximum allowed message depth of ${maxDepth}.`);
1254
1270
  if (this.encodingLevel === "structuredClonable") {
1255
1271
  if (value instanceof Date || typeof value === "bigint") return value;
1256
1272
  }
1257
1273
  if (value instanceof Array) {
1258
1274
  if (value.length == 1 && value[0] instanceof Array) {
1259
1275
  let result = value[0];
1260
- for (let i = 0; i < result.length; i++) result[i] = this.evaluateImpl(result[i], result, i);
1276
+ for (let i = 0; i < result.length; i++) result[i] = this.evaluateImpl(result[i], result, i, depth + 1);
1261
1277
  return result;
1262
1278
  } else switch (value[0]) {
1263
1279
  case "bigint":
1264
- if (typeof value[1] == "string") return BigInt(value[1]);
1280
+ if (typeof value[1] == "string") {
1281
+ let digits = value[1];
1282
+ let maxBigIntDigits = this.limits.maxBigIntDigits;
1283
+ if (digits.length > maxBigIntDigits) throw new TypeError(`Deserialized bigint exceeds maximum length of ${maxBigIntDigits} digits.`);
1284
+ return BigInt(digits);
1285
+ }
1265
1286
  break;
1266
1287
  case "date":
1267
1288
  if (value[1] === null) return /* @__PURE__ */ new Date(NaN);
@@ -1291,7 +1312,11 @@ var Evaluator = class Evaluator {
1291
1312
  let propsObj = props;
1292
1313
  for (let key of Object.keys(propsObj)) {
1293
1314
  if (key === "name" || key === "message" || key === "stack") continue;
1294
- anyResult[key] = this.evaluateImpl(propsObj[key], result, key);
1315
+ if (key in Object.prototype || key === "toJSON") {
1316
+ this.evaluateImpl(propsObj[key], result, key, depth + 1);
1317
+ continue;
1318
+ }
1319
+ anyResult[key] = this.evaluateImpl(propsObj[key], result, key, depth + 1);
1295
1320
  }
1296
1321
  }
1297
1322
  return result;
@@ -1312,11 +1337,11 @@ var Evaluator = class Evaluator {
1312
1337
  let init = value[2];
1313
1338
  if (typeof init !== "object" || init === null) break;
1314
1339
  if (init.body) {
1315
- init.body = this.evaluateImpl(init.body, init, "body");
1340
+ init.body = this.evaluateImpl(init.body, init, "body", depth + 1);
1316
1341
  if (init.body === null || typeof init.body === "string" || init.body instanceof Uint8Array || init.body instanceof ReadableStream) {} else throw new TypeError("Request body must be of type ReadableStream.");
1317
1342
  }
1318
1343
  if (init.signal) {
1319
- init.signal = this.evaluateImpl(init.signal, init, "signal");
1344
+ init.signal = this.evaluateImpl(init.signal, init, "signal", depth + 1);
1320
1345
  if (!(init.signal instanceof AbortSignal)) throw new TypeError("Request siganl must be of type AbortSignal.");
1321
1346
  }
1322
1347
  if (init.headers && !(init.headers instanceof Array)) throw new TypeError("Request headers must be serialized as an array of pairs.");
@@ -1333,7 +1358,7 @@ var Evaluator = class Evaluator {
1333
1358
  }
1334
1359
  case "response": {
1335
1360
  if (value.length !== 3) break;
1336
- let body = this.evaluateImpl(value[1], parent, property);
1361
+ let body = this.evaluateImpl(value[1], parent, property, depth + 1);
1337
1362
  if (body === null || typeof body === "string" || body instanceof Uint8Array || body instanceof ReadableStream) {} else throw new TypeError("Response body must be of type ReadableStream.");
1338
1363
  let init = value[2];
1339
1364
  if (typeof init !== "object" || init === null) break;
@@ -1344,7 +1369,7 @@ var Evaluator = class Evaluator {
1344
1369
  case "blob": {
1345
1370
  if (value.length !== 3 || typeof value[1] !== "string") break;
1346
1371
  let contentType = value[1];
1347
- let content = this.evaluateImpl(value[2], parent, property);
1372
+ let content = this.evaluateImpl(value[2], parent, property, depth + 1);
1348
1373
  if (!(content instanceof ReadableStream)) throw new TypeError("Blob content must be serialized as a ReadableStream.");
1349
1374
  let promise = streamToBlobPromise(content, contentType);
1350
1375
  this.promises.push({
@@ -1385,7 +1410,7 @@ var Evaluator = class Evaluator {
1385
1410
  if (value.length == 3) return addStub(hook.get(path));
1386
1411
  let args = value[3];
1387
1412
  if (!(args instanceof Array)) break;
1388
- args = new Evaluator(this.importer).evaluate([args]);
1413
+ args = new Evaluator(this.importer).evaluateWithDepth([args], depth);
1389
1414
  return addStub(hook.call(path, args));
1390
1415
  }
1391
1416
  case "remap": {
@@ -1451,9 +1476,9 @@ var Evaluator = class Evaluator {
1451
1476
  } else if (value instanceof Object) {
1452
1477
  let result = value;
1453
1478
  for (let key in result) if (key in Object.prototype || key === "toJSON") {
1454
- this.evaluateImpl(result[key], result, key);
1479
+ this.evaluateImpl(result[key], result, key, depth + 1);
1455
1480
  delete result[key];
1456
- } else result[key] = this.evaluateImpl(result[key], result, key);
1481
+ } else result[key] = this.evaluateImpl(result[key], result, key, depth + 1);
1457
1482
  return result;
1458
1483
  } else return value;
1459
1484
  }
@@ -1524,6 +1549,10 @@ var ImportTableEntry = class {
1524
1549
  resolution;
1525
1550
  onBrokenRegistrations;
1526
1551
  resolve(resolution) {
1552
+ if (this.resolution) {
1553
+ resolution.dispose();
1554
+ return;
1555
+ }
1527
1556
  if (this.localRefcount == 0) {
1528
1557
  resolution.dispose();
1529
1558
  return;
@@ -1676,6 +1705,7 @@ var RpcSessionImpl = class {
1676
1705
  pullCount = 0;
1677
1706
  onBrokenCallbacks = [];
1678
1707
  encodingLevel;
1708
+ limits;
1679
1709
  constructor(transport, mainHook, options) {
1680
1710
  this.transport = transport;
1681
1711
  this.options = options;
@@ -1688,6 +1718,10 @@ var RpcSessionImpl = class {
1688
1718
  }
1689
1719
  }
1690
1720
  this.encodingLevel = level;
1721
+ this.limits = {
1722
+ ...DEFAULT_LIMITS,
1723
+ ...options.limits
1724
+ };
1691
1725
  this.exports.push({
1692
1726
  hook: mainHook,
1693
1727
  refcount: 1
@@ -1830,6 +1864,9 @@ var RpcSessionImpl = class {
1830
1864
  entry.pipeReadable = void 0;
1831
1865
  return readable;
1832
1866
  }
1867
+ getLimits() {
1868
+ return this.limits;
1869
+ }
1833
1870
  createPipe(readable, readableHook) {
1834
1871
  if (this.abortReason) throw this.abortReason;
1835
1872
  this.send(["pipe"]);
@@ -1989,6 +2026,7 @@ var RpcSessionImpl = class {
1989
2026
  } finally {
1990
2027
  if (this.cancelReadLoop === readCanceled.reject) this.cancelReadLoop = void 0;
1991
2028
  }
2029
+ if (this.encodingLevel === "string" && raw.length > this.limits.maxMessageSize) throw new TypeError(`Incoming message exceeds maximum size of ${this.limits.maxMessageSize} UTF-16 code units.`);
1992
2030
  if (this.abortReason) break;
1993
2031
  let msg = this.encodingLevel === "string" ? JSON.parse(raw) : raw;
1994
2032
  if (msg instanceof Array) switch (msg[0]) {
@@ -2063,7 +2101,7 @@ var RpcSessionImpl = class {
2063
2101
  case "abort": {
2064
2102
  let payload = new Evaluator(this, this.encodingLevel).evaluate(msg[1]);
2065
2103
  payload.dispose();
2066
- this.abort(payload, false);
2104
+ this.abort(payload.value, false);
2067
2105
  break;
2068
2106
  }
2069
2107
  }
@@ -2189,6 +2227,8 @@ var WebSocketTransport = class {
2189
2227
  let message;
2190
2228
  if (reason instanceof Error) message = reason.message;
2191
2229
  else message = `${reason}`;
2230
+ let reasonBytes = new TextEncoder().encode(message);
2231
+ if (reasonBytes.length > 123) message = new TextDecoder().decode(reasonBytes.subarray(0, 123), { stream: true });
2192
2232
  this.#webSocket.close(3e3, message);
2193
2233
  if (!this.#error) this.#error = reason;
2194
2234
  }
@@ -2305,7 +2345,11 @@ async function newHttpBatchRpcResponse(request, localMain, options) {
2305
2345
  * @param options Optional RPC session options. You can also pass headers to set on the response.
2306
2346
  */
2307
2347
  async function nodeHttpBatchRpcResponse(request, response, localMain, options) {
2308
- if (request.method !== "POST") response.writeHead(405, "This endpoint only accepts POST requests.");
2348
+ if (request.method !== "POST") {
2349
+ response.writeHead(405, "This endpoint only accepts POST requests.", options?.headers);
2350
+ response.end();
2351
+ return;
2352
+ }
2309
2353
  let body = await new Promise((resolve, reject) => {
2310
2354
  let chunks = [];
2311
2355
  request.on("data", (chunk) => {
@@ -2570,6 +2614,9 @@ var MapApplicator = class {
2570
2614
  getPipeReadable(exportId) {
2571
2615
  throw new Error("A mapper function cannot use pipe readables.");
2572
2616
  }
2617
+ getLimits() {
2618
+ return DEFAULT_LIMITS;
2619
+ }
2573
2620
  };
2574
2621
  function applyMapToElement(input, parent, owner, captures, instructions) {
2575
2622
  let mapper = new MapApplicator(captures, new PayloadStubHook(RpcPayload.deepCopyFrom(input, parent, owner)));
@@ -2908,12 +2955,12 @@ let newMessagePortRpcSession = newMessagePortRpcSession$1;
2908
2955
  * credentials as parameters and returns the authorized API), then cross-origin requests should
2909
2956
  * be safe.
2910
2957
  */
2911
- async function newWorkersRpcResponse(request, localMain) {
2958
+ async function newWorkersRpcResponse(request, localMain, options) {
2912
2959
  if (request.method === "POST") {
2913
- let response = await newHttpBatchRpcResponse(request, localMain);
2960
+ let response = await newHttpBatchRpcResponse(request, localMain, options);
2914
2961
  response.headers.set("Access-Control-Allow-Origin", "*");
2915
2962
  return response;
2916
- } else if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") return newWorkersWebSocketRpcResponse(request, localMain);
2963
+ } else if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") return newWorkersWebSocketRpcResponse(request, localMain, options);
2917
2964
  else return new Response("This endpoint only accepts POST or WebSocket requests.", { status: 400 });
2918
2965
  }
2919
2966
 
@@ -3034,5 +3081,5 @@ var BunWebSocketTransport = class {
3034
3081
  let newBunWebSocketRpcSession = newBunWebSocketRpcSession$1;
3035
3082
 
3036
3083
  //#endregion
3037
- export { BunWebSocketTransport, RpcPromise, RpcSession, RpcStub, RpcTarget, WebSocketTransport, deserialize, newBunWebSocketRpcHandler, newBunWebSocketRpcSession, newHttpBatchRpcResponse, newHttpBatchRpcSession, newMessagePortRpcSession, newWebSocketRpcSession, newWorkersRpcResponse, newWorkersWebSocketRpcResponse, nodeHttpBatchRpcResponse, serialize };
3084
+ export { BunWebSocketTransport, DEFAULT_LIMITS, DEFAULT_MAX_DEPTH, RpcPromise, RpcSession, RpcStub, RpcTarget, WebSocketTransport, deserialize, newBunWebSocketRpcHandler, newBunWebSocketRpcSession, newHttpBatchRpcResponse, newHttpBatchRpcSession, newMessagePortRpcSession, newWebSocketRpcSession, newWorkersRpcResponse, newWorkersWebSocketRpcResponse, nodeHttpBatchRpcResponse, serialize };
3038
3085
  //# sourceMappingURL=index-bun.js.map