autotel-devtools 8.0.0 → 8.1.1

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.
@@ -1,4 +1,4 @@
1
1
  import '@opentelemetry/sdk-trace-base';
2
2
  import '@opentelemetry/core';
3
- export { a as DevtoolsSpanExporter } from '../exporter-DjLkU621.cjs';
3
+ export { a as DevtoolsSpanExporter } from '../exporter-De6p4iAD.cjs';
4
4
  import 'node:http';
@@ -1,4 +1,4 @@
1
1
  import '@opentelemetry/sdk-trace-base';
2
2
  import '@opentelemetry/core';
3
- export { a as DevtoolsSpanExporter } from '../exporter-DjLkU621.js';
3
+ export { a as DevtoolsSpanExporter } from '../exporter-De6p4iAD.js';
4
4
  import 'node:http';
@@ -363,6 +363,40 @@ function applyTelemetryLimits(data, limits) {
363
363
  };
364
364
  }
365
365
 
366
+ // src/server/origin-guard.ts
367
+ var LOOPBACK_IPV6 = /* @__PURE__ */ new Set(["::1", "0:0:0:0:0:0:0:1"]);
368
+ function isLoopbackHostname(hostname) {
369
+ const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
370
+ return h === "localhost" || /^127\./.test(h) || LOOPBACK_IPV6.has(h);
371
+ }
372
+ function hostnameFromHostHeader(host) {
373
+ const h = host.trim();
374
+ if (h.startsWith("[")) {
375
+ const end = h.indexOf("]");
376
+ return end > 0 ? h.slice(1, end) : h;
377
+ }
378
+ const colon = h.indexOf(":");
379
+ return colon === -1 ? h : h.slice(0, colon);
380
+ }
381
+ function hostHeaderIsLoopback(host) {
382
+ return isLoopbackHostname(hostnameFromHostHeader(host));
383
+ }
384
+ function originIsLoopback(origin) {
385
+ try {
386
+ return isLoopbackHostname(new URL(origin).hostname);
387
+ } catch {
388
+ return false;
389
+ }
390
+ }
391
+ function allowSensitiveRequest(headers, loopbackOnly) {
392
+ const { origin, host } = headers;
393
+ if (origin && origin.length > 0 && !originIsLoopback(origin)) return false;
394
+ if (loopbackOnly && host && host.length > 0 && !hostHeaderIsLoopback(host)) {
395
+ return false;
396
+ }
397
+ return true;
398
+ }
399
+
366
400
  // src/server/server.ts
367
401
  var DevtoolsServer = class {
368
402
  wss;
@@ -382,7 +416,12 @@ var DevtoolsServer = class {
382
416
  this._port = options.port ?? 4318;
383
417
  this.onData = options.onData;
384
418
  this.httpServer = options.server ?? http.createServer();
385
- this.wss = new ws.WebSocketServer({ server: this.httpServer, path: options.path ?? "/ws" });
419
+ const loopbackOnly = options.host == null || hostHeaderIsLoopback(options.host);
420
+ this.wss = new ws.WebSocketServer({
421
+ server: this.httpServer,
422
+ path: options.path ?? "/ws",
423
+ verifyClient: ({ origin, req }) => allowSensitiveRequest({ origin, host: req.headers.host }, loopbackOnly)
424
+ });
386
425
  this.wss.on("error", (err) => {
387
426
  if (this.httpServer.listening) throw err;
388
427
  });
@@ -416,6 +455,7 @@ var DevtoolsServer = class {
416
455
  }
417
456
  addTrace(trace) {
418
457
  const existing = this.traces.find((t) => t.traceId === trace.traceId);
458
+ const merged = existing ?? trace;
419
459
  if (existing) {
420
460
  const existingSpanIds = new Set(existing.spans.map((s) => s.spanId));
421
461
  for (const span of trace.spans) {
@@ -427,6 +467,14 @@ var DevtoolsServer = class {
427
467
  existing.endTime = Math.max(existing.endTime, trace.endTime);
428
468
  existing.duration = existing.endTime - existing.startTime;
429
469
  if (trace.status === "ERROR") existing.status = "ERROR";
470
+ const root = existing.spans.find((s) => !s.parentSpanId);
471
+ if (root) {
472
+ existing.rootSpan = root;
473
+ const rootService = root.attributes?.["service.name"];
474
+ if (typeof rootService === "string" && rootService.length > 0) {
475
+ existing.service = rootService;
476
+ }
477
+ }
430
478
  } else {
431
479
  this.traces = appendWithLimit(
432
480
  this.traces,
@@ -435,7 +483,7 @@ var DevtoolsServer = class {
435
483
  );
436
484
  }
437
485
  this.errorAggregator.addErrorsFromTrace(trace);
438
- this.broadcast({ traces: [trace], metrics: [], logs: [], errors: this.errorAggregator.getErrorGroups() });
486
+ this.broadcast({ traces: [merged], metrics: [], logs: [], errors: this.errorAggregator.getErrorGroups() });
439
487
  }
440
488
  addTraces(traces) {
441
489
  for (const trace of traces) this.addTrace(trace);
@@ -1447,7 +1495,8 @@ function findPackageRoot() {
1447
1495
  }
1448
1496
  return dir;
1449
1497
  }
1450
- var FULLPAGE_HTML = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>autotel-devtools</title><style>*{margin:0;padding:0;box-sizing:border-box}html,body{height:100%;width:100%;overflow:hidden}</style></head><body><script src="/widget.js?mode=fullpage"></script></body></html>`;
1498
+ var DEVTOOLS_FAVICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#0f172a"/><text x="32" y="41" text-anchor="middle" font-size="32">\u{1F6F0}\uFE0F</text></svg>';
1499
+ var FULLPAGE_HTML = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>autotel-devtools</title><link rel="icon" href="/favicon.svg" type="image/svg+xml"><style>*{margin:0;padding:0;box-sizing:border-box}html,body{height:100%;width:100%;overflow:hidden}</style></head><body><script src="/widget.js?mode=fullpage"></script></body></html>`;
1451
1500
  var cachedVersion = null;
1452
1501
  function getVersion() {
1453
1502
  if (cachedVersion !== null) return cachedVersion;
@@ -1481,8 +1530,10 @@ function getWidgetJs() {
1481
1530
  }
1482
1531
  return cachedWidgetJs;
1483
1532
  }
1484
- function attachDevtoolsRoutes(httpServer, devtools) {
1533
+ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
1534
+ const loopbackOnly = options.loopbackOnly ?? true;
1485
1535
  httpServer.on("request", async (req, res) => {
1536
+ if (req.headers.upgrade?.toLowerCase() === "websocket") return;
1486
1537
  res.setHeader("Access-Control-Allow-Origin", "*");
1487
1538
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
1488
1539
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
@@ -1505,6 +1556,15 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1505
1556
  res.end(js);
1506
1557
  return;
1507
1558
  }
1559
+ if (req.method === "GET" && (url === "/favicon.svg" || url === "/favicon.ico")) {
1560
+ res.writeHead(200, {
1561
+ "Content-Type": "image/svg+xml; charset=utf-8",
1562
+ "Cache-Control": "public, max-age=86400",
1563
+ "Content-Length": Buffer.byteLength(DEVTOOLS_FAVICON_SVG)
1564
+ });
1565
+ res.end(DEVTOOLS_FAVICON_SVG);
1566
+ return;
1567
+ }
1508
1568
  if (req.method === "GET" && url === "/healthz") {
1509
1569
  sendJson(res, 200, {
1510
1570
  ok: true,
@@ -1515,11 +1575,19 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1515
1575
  return;
1516
1576
  }
1517
1577
  if (req.method === "GET" && url === "/v1/traces") {
1578
+ if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
1579
+ sendJson(res, 403, { error: "Forbidden" });
1580
+ return;
1581
+ }
1518
1582
  const data = devtools.getCurrentData();
1519
1583
  sendJson(res, 200, { traces: data.traces, count: data.traces.length });
1520
1584
  return;
1521
1585
  }
1522
1586
  if (req.method === "DELETE" && url === "/v1/traces") {
1587
+ if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
1588
+ sendJson(res, 403, { error: "Forbidden" });
1589
+ return;
1590
+ }
1523
1591
  devtools.clearData();
1524
1592
  sendJson(res, 200, { cleared: true });
1525
1593
  return;
@@ -1571,6 +1639,7 @@ exports.DevtoolsRemoteExporter = DevtoolsRemoteExporter;
1571
1639
  exports.DevtoolsServer = DevtoolsServer;
1572
1640
  exports.DevtoolsSpanExporter = DevtoolsSpanExporter;
1573
1641
  exports.ErrorAggregator = ErrorAggregator;
1642
+ exports.allowSensitiveRequest = allowSensitiveRequest;
1574
1643
  exports.appendManyWithLimit = appendManyWithLimit;
1575
1644
  exports.appendWithLimit = appendWithLimit;
1576
1645
  exports.applyTelemetryLimits = applyTelemetryLimits;
@@ -1579,7 +1648,10 @@ exports.createDevtoolsHttpServer = createDevtoolsHttpServer;
1579
1648
  exports.decodeOtlpLogsRequest = decodeOtlpLogsRequest;
1580
1649
  exports.decodeOtlpMetricsRequest = decodeOtlpMetricsRequest;
1581
1650
  exports.decodeOtlpTraceRequest = decodeOtlpTraceRequest;
1651
+ exports.hostHeaderIsLoopback = hostHeaderIsLoopback;
1652
+ exports.isLoopbackHostname = isLoopbackHostname;
1582
1653
  exports.isProtobufContentType = isProtobufContentType;
1654
+ exports.originIsLoopback = originIsLoopback;
1583
1655
  exports.parseOtlpLogs = parseOtlpLogs;
1584
1656
  exports.parseOtlpTraces = parseOtlpTraces;
1585
1657
  exports.probePortHolder = probePortHolder;