autotel-devtools 8.0.0 → 8.1.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.
package/dist/cli.js CHANGED
@@ -346,6 +346,40 @@ function appendManyWithLimit(items, incoming, limit) {
346
346
  return next.length > limit ? next.slice(next.length - limit) : next;
347
347
  }
348
348
 
349
+ // src/server/origin-guard.ts
350
+ var LOOPBACK_IPV6 = /* @__PURE__ */ new Set(["::1", "0:0:0:0:0:0:0:1"]);
351
+ function isLoopbackHostname(hostname) {
352
+ const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
353
+ return h === "localhost" || /^127\./.test(h) || LOOPBACK_IPV6.has(h);
354
+ }
355
+ function hostnameFromHostHeader(host) {
356
+ const h = host.trim();
357
+ if (h.startsWith("[")) {
358
+ const end = h.indexOf("]");
359
+ return end > 0 ? h.slice(1, end) : h;
360
+ }
361
+ const colon = h.indexOf(":");
362
+ return colon === -1 ? h : h.slice(0, colon);
363
+ }
364
+ function hostHeaderIsLoopback(host) {
365
+ return isLoopbackHostname(hostnameFromHostHeader(host));
366
+ }
367
+ function originIsLoopback(origin) {
368
+ try {
369
+ return isLoopbackHostname(new URL(origin).hostname);
370
+ } catch {
371
+ return false;
372
+ }
373
+ }
374
+ function allowSensitiveRequest(headers, loopbackOnly) {
375
+ const { origin, host } = headers;
376
+ if (origin && origin.length > 0 && !originIsLoopback(origin)) return false;
377
+ if (loopbackOnly && host && host.length > 0 && !hostHeaderIsLoopback(host)) {
378
+ return false;
379
+ }
380
+ return true;
381
+ }
382
+
349
383
  // src/server/server.ts
350
384
  var DevtoolsServer = class {
351
385
  wss;
@@ -365,7 +399,12 @@ var DevtoolsServer = class {
365
399
  this._port = options.port ?? 4318;
366
400
  this.onData = options.onData;
367
401
  this.httpServer = options.server ?? createServer();
368
- this.wss = new WebSocketServer({ server: this.httpServer, path: options.path ?? "/ws" });
402
+ const loopbackOnly = options.host == null || hostHeaderIsLoopback(options.host);
403
+ this.wss = new WebSocketServer({
404
+ server: this.httpServer,
405
+ path: options.path ?? "/ws",
406
+ verifyClient: ({ origin, req }) => allowSensitiveRequest({ origin, host: req.headers.host }, loopbackOnly)
407
+ });
369
408
  this.wss.on("error", (err) => {
370
409
  if (this.httpServer.listening) throw err;
371
410
  });
@@ -1004,7 +1043,8 @@ function getWidgetJs() {
1004
1043
  }
1005
1044
  return cachedWidgetJs;
1006
1045
  }
1007
- function attachDevtoolsRoutes(httpServer, devtools) {
1046
+ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
1047
+ const loopbackOnly = options.loopbackOnly ?? true;
1008
1048
  httpServer.on("request", async (req, res) => {
1009
1049
  res.setHeader("Access-Control-Allow-Origin", "*");
1010
1050
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
@@ -1038,11 +1078,19 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1038
1078
  return;
1039
1079
  }
1040
1080
  if (req.method === "GET" && url === "/v1/traces") {
1081
+ if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
1082
+ sendJson(res, 403, { error: "Forbidden" });
1083
+ return;
1084
+ }
1041
1085
  const data = devtools.getCurrentData();
1042
1086
  sendJson(res, 200, { traces: data.traces, count: data.traces.length });
1043
1087
  return;
1044
1088
  }
1045
1089
  if (req.method === "DELETE" && url === "/v1/traces") {
1090
+ if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
1091
+ sendJson(res, 403, { error: "Forbidden" });
1092
+ return;
1093
+ }
1046
1094
  devtools.clearData();
1047
1095
  sendJson(res, 200, { cleared: true });
1048
1096
  return;
@@ -1283,13 +1331,14 @@ async function main() {
1283
1331
  process.exit(0);
1284
1332
  }
1285
1333
  const httpServer = createServer();
1286
- const wsServer = new DevtoolsServer({ server: httpServer, verbose: true });
1287
- attachDevtoolsRoutes(httpServer, wsServer);
1334
+ const loopbackOnly = hostHeaderIsLoopback(options.host);
1335
+ const wsServer = new DevtoolsServer({ server: httpServer, host: options.host, verbose: true });
1336
+ attachDevtoolsRoutes(httpServer, wsServer, { loopbackOnly });
1288
1337
  const listeners = listenLoopbackDualStack({
1289
1338
  primary: httpServer,
1290
1339
  port: options.port,
1291
1340
  host: options.host,
1292
- attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer)
1341
+ attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer, { loopbackOnly })
1293
1342
  });
1294
1343
  const { addresses, warnings, port: boundPort } = await listeners.ready;
1295
1344
  if (boundPort !== options.port) {