@neat.is/core 0.3.7 → 0.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.
package/dist/index.cjs CHANGED
@@ -40,6 +40,91 @@ var init_cjs_shims = __esm({
40
40
  }
41
41
  });
42
42
 
43
+ // src/auth.ts
44
+ function isLoopbackHost(host) {
45
+ if (!host) return false;
46
+ return LOOPBACK_HOSTS.has(host);
47
+ }
48
+ function assertBindAuthority(host, token) {
49
+ if (token && token.length > 0) return;
50
+ if (isLoopbackHost(host)) return;
51
+ throw new BindAuthorityError(host);
52
+ }
53
+ function mountBearerAuth(app, opts) {
54
+ if (!opts.token || opts.token.length === 0) return;
55
+ if (opts.trustProxy) return;
56
+ const expected = Buffer.from(opts.token, "utf8");
57
+ const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
58
+ const publicRead = opts.publicRead === true;
59
+ app.addHook("preHandler", (req, reply, done) => {
60
+ const path37 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ for (const suffix of suffixes) {
62
+ if (path37 === suffix || path37.endsWith(suffix)) {
63
+ done();
64
+ return;
65
+ }
66
+ }
67
+ if (publicRead && PUBLIC_READ_METHODS.has(req.method)) {
68
+ done();
69
+ return;
70
+ }
71
+ const header = req.headers.authorization;
72
+ if (typeof header !== "string" || !header.startsWith("Bearer ")) {
73
+ void reply.code(401).send({ error: "unauthorized" });
74
+ return;
75
+ }
76
+ const provided = Buffer.from(header.slice("Bearer ".length).trim(), "utf8");
77
+ if (provided.length !== expected.length || !(0, import_node_crypto.timingSafeEqual)(provided, expected)) {
78
+ void reply.code(401).send({ error: "unauthorized" });
79
+ return;
80
+ }
81
+ done();
82
+ });
83
+ }
84
+ function parseBoolEnv(v) {
85
+ if (!v) return false;
86
+ return v === "true" || v === "1";
87
+ }
88
+ function readAuthEnv(env = process.env) {
89
+ const t = env.NEAT_AUTH_TOKEN;
90
+ const ot = env.NEAT_OTEL_TOKEN;
91
+ return {
92
+ authToken: t && t.length > 0 ? t : void 0,
93
+ otelToken: ot && ot.length > 0 ? ot : t && t.length > 0 ? t : void 0,
94
+ trustProxy: env.NEAT_AUTH_PROXY === "true",
95
+ publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
96
+ };
97
+ }
98
+ var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_SUFFIXES;
99
+ var init_auth = __esm({
100
+ "src/auth.ts"() {
101
+ "use strict";
102
+ init_cjs_shims();
103
+ import_node_crypto = require("crypto");
104
+ LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
105
+ "127.0.0.1",
106
+ "localhost",
107
+ "::1",
108
+ "::ffff:127.0.0.1"
109
+ ]);
110
+ BindAuthorityError = class extends Error {
111
+ constructor(host) {
112
+ super(
113
+ `NEAT refuses to bind on a public interface without \`NEAT_AUTH_TOKEN\` set (host="${host}"). Set the token or bind to loopback only.`
114
+ );
115
+ this.name = "BindAuthorityError";
116
+ }
117
+ };
118
+ PUBLIC_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
119
+ DEFAULT_UNAUTH_SUFFIXES = [
120
+ "/health",
121
+ "/healthz",
122
+ "/readyz",
123
+ "/api/config"
124
+ ];
125
+ }
126
+ });
127
+
43
128
  // src/otel-grpc.ts
44
129
  var otel_grpc_exports = {};
45
130
  __export(otel_grpc_exports, {
@@ -122,8 +207,21 @@ function loadTraceService() {
122
207
  async function startOtelGrpcReceiver(opts) {
123
208
  const server = new grpc.Server();
124
209
  const service = loadTraceService();
210
+ const requiresAuth = !opts.trustProxy && !!opts.authToken && opts.authToken.length > 0;
211
+ const expectedHeader = requiresAuth ? `Bearer ${opts.authToken}` : "";
125
212
  server.addService(service, {
126
213
  Export: (call, callback) => {
214
+ if (requiresAuth) {
215
+ const meta = call.metadata.get("authorization");
216
+ const got = meta.length > 0 ? String(meta[0]) : "";
217
+ const a = Buffer.from(got, "utf8");
218
+ const b = Buffer.from(expectedHeader, "utf8");
219
+ const ok = a.length === b.length && (0, import_node_crypto2.timingSafeEqual)(a, b);
220
+ if (!ok) {
221
+ callback({ code: grpc.status.UNAUTHENTICATED, message: "unauthorized" });
222
+ return;
223
+ }
224
+ }
127
225
  void (async () => {
128
226
  try {
129
227
  const reshaped = reshapeGrpcRequest(call.request ?? {});
@@ -156,13 +254,14 @@ async function startOtelGrpcReceiver(opts) {
156
254
  })
157
255
  };
158
256
  }
159
- var import_node_url, import_node_path34, grpc, protoLoader;
257
+ var import_node_url, import_node_path34, import_node_crypto2, grpc, protoLoader;
160
258
  var init_otel_grpc = __esm({
161
259
  "src/otel-grpc.ts"() {
162
260
  "use strict";
163
261
  init_cjs_shims();
164
262
  import_node_url = require("url");
165
263
  import_node_path34 = __toESM(require("path"), 1);
264
+ import_node_crypto2 = require("crypto");
166
265
  grpc = __toESM(require("@grpc/grpc-js"), 1);
167
266
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
168
267
  init_otel();
@@ -225,6 +324,15 @@ function isoFromUnixNano(nanos) {
225
324
  return void 0;
226
325
  }
227
326
  }
327
+ function pickEnv(spanAttrs, resourceAttrs) {
328
+ for (const attrs of [spanAttrs, resourceAttrs]) {
329
+ for (const key of [ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT]) {
330
+ const v = attrs[key];
331
+ if (typeof v === "string" && v.length > 0) return v;
332
+ }
333
+ }
334
+ return ENV_FALLBACK;
335
+ }
228
336
  function parseOtlpRequest(body) {
229
337
  const out = [];
230
338
  for (const rs of body.resourceSpans ?? []) {
@@ -244,6 +352,7 @@ function parseOtlpRequest(body) {
244
352
  endTimeUnixNano: span.endTimeUnixNano ?? "0",
245
353
  startTimeIso: isoFromUnixNano(span.startTimeUnixNano),
246
354
  durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
355
+ env: pickEnv(attrs, resourceAttrs),
247
356
  attributes: attrs,
248
357
  dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
249
358
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
@@ -303,6 +412,7 @@ async function buildOtelReceiver(opts) {
303
412
  logger: false,
304
413
  bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024
305
414
  });
415
+ mountBearerAuth(app, { token: opts.authToken, trustProxy: opts.trustProxy });
306
416
  const queue = [];
307
417
  let draining = false;
308
418
  let drainPromise = Promise.resolve();
@@ -389,7 +499,7 @@ function logSpanHandler(span) {
389
499
  `otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
390
500
  );
391
501
  }
392
- var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
502
+ var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
393
503
  var init_otel = __esm({
394
504
  "src/otel.ts"() {
395
505
  "use strict";
@@ -398,6 +508,10 @@ var init_otel = __esm({
398
508
  import_node_url2 = require("url");
399
509
  import_fastify2 = __toESM(require("fastify"), 1);
400
510
  import_protobufjs = __toESM(require("protobufjs"), 1);
511
+ init_auth();
512
+ ENV_ATTR_CANONICAL = "deployment.environment.name";
513
+ ENV_ATTR_COMPAT = "deployment.environment";
514
+ ENV_FALLBACK = "unknown";
401
515
  exportTraceServiceRequestType = null;
402
516
  exportTraceServiceResponseType = null;
403
517
  cachedProtobufResponseBody = null;
@@ -1494,52 +1608,64 @@ function cacheSpanService(span, now) {
1494
1608
  if (!span.traceId || !span.spanId) return;
1495
1609
  const key = parentSpanKey(span.traceId, span.spanId);
1496
1610
  parentSpanCache.delete(key);
1497
- parentSpanCache.set(key, { service: span.service, expiresAt: now + PARENT_SPAN_CACHE_TTL_MS });
1611
+ parentSpanCache.set(key, {
1612
+ service: span.service,
1613
+ env: span.env ?? "unknown",
1614
+ expiresAt: now + PARENT_SPAN_CACHE_TTL_MS
1615
+ });
1498
1616
  while (parentSpanCache.size > PARENT_SPAN_CACHE_SIZE) {
1499
1617
  const oldest = parentSpanCache.keys().next().value;
1500
1618
  if (!oldest) break;
1501
1619
  parentSpanCache.delete(oldest);
1502
1620
  }
1503
1621
  }
1504
- function lookupParentSpanService(traceId, parentSpanId, now) {
1622
+ function lookupParentSpan(traceId, parentSpanId, now) {
1505
1623
  const entry = parentSpanCache.get(parentSpanKey(traceId, parentSpanId));
1506
1624
  if (!entry) return null;
1507
1625
  if (entry.expiresAt <= now) {
1508
1626
  parentSpanCache.delete(parentSpanKey(traceId, parentSpanId));
1509
1627
  return null;
1510
1628
  }
1511
- return entry.service;
1629
+ return { service: entry.service, env: entry.env };
1512
1630
  }
1513
- function resolveServiceId(graph, host) {
1514
- const direct = (0, import_types3.serviceId)(host);
1515
- if (graph.hasNode(direct)) return direct;
1516
- let found = null;
1631
+ function resolveServiceId(graph, host, env) {
1632
+ const envTagged = (0, import_types3.serviceId)(host, env);
1633
+ if (graph.hasNode(envTagged)) return envTagged;
1634
+ const envLess = (0, import_types3.serviceId)(host);
1635
+ if (envLess !== envTagged && graph.hasNode(envLess)) return envLess;
1636
+ let sameEnv = null;
1637
+ let envLessMatch = null;
1638
+ let anyMatch = null;
1517
1639
  graph.forEachNode((id, attrs) => {
1518
- if (found) return;
1640
+ if (sameEnv) return;
1519
1641
  const a = attrs;
1520
1642
  if (a.type !== import_types3.NodeType.ServiceNode) return;
1521
- if (a.name === host) {
1522
- found = id;
1643
+ const matchesByName = a.name === host;
1644
+ const matchesByAlias = a.aliases ? a.aliases.includes(host) : false;
1645
+ if (!matchesByName && !matchesByAlias) return;
1646
+ const nodeEnv = a.env ?? "unknown";
1647
+ if (nodeEnv === env) {
1648
+ sameEnv = id;
1523
1649
  return;
1524
1650
  }
1525
- if (a.aliases && a.aliases.includes(host)) {
1526
- found = id;
1527
- }
1651
+ if (nodeEnv === "unknown" && !envLessMatch) envLessMatch = id;
1652
+ else if (!anyMatch) anyMatch = id;
1528
1653
  });
1529
- return found;
1654
+ return sameEnv ?? envLessMatch ?? anyMatch;
1530
1655
  }
1531
1656
  function frontierIdFor(host) {
1532
1657
  return (0, import_types3.frontierId)(host);
1533
1658
  }
1534
- function ensureServiceNode(graph, serviceName) {
1535
- const id = (0, import_types3.serviceId)(serviceName);
1659
+ function ensureServiceNode(graph, serviceName, env) {
1660
+ const id = (0, import_types3.serviceId)(serviceName, env);
1536
1661
  if (graph.hasNode(id)) return id;
1537
1662
  const node = {
1538
1663
  id,
1539
1664
  type: import_types3.NodeType.ServiceNode,
1540
1665
  name: serviceName,
1541
1666
  language: "unknown",
1542
- discoveredVia: "otel"
1667
+ discoveredVia: "otel",
1668
+ ...env !== "unknown" ? { env } : {}
1543
1669
  };
1544
1670
  graph.addNode(id, node);
1545
1671
  return id;
@@ -1685,7 +1811,7 @@ function buildErrorEventForReceiver(span) {
1685
1811
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1686
1812
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1687
1813
  ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1688
- affectedNode: (0, import_types3.serviceId)(span.service)
1814
+ affectedNode: (0, import_types3.serviceId)(span.service, span.env)
1689
1815
  };
1690
1816
  }
1691
1817
  function makeErrorSpanWriter(errorsPath) {
@@ -1699,7 +1825,8 @@ function makeErrorSpanWriter(errorsPath) {
1699
1825
  async function handleSpan(ctx, span) {
1700
1826
  const ts = span.startTimeIso ?? nowIso(ctx);
1701
1827
  const nowMs = ctx.now ? ctx.now() : Date.now();
1702
- const sourceId = ensureServiceNode(ctx.graph, span.service);
1828
+ const env = span.env ?? "unknown";
1829
+ const sourceId = ensureServiceNode(ctx.graph, span.service, env);
1703
1830
  const isError = span.statusCode === 2;
1704
1831
  cacheSpanService(span, nowMs);
1705
1832
  let affectedNode = sourceId;
@@ -1722,7 +1849,7 @@ async function handleSpan(ctx, span) {
1722
1849
  const host = pickAddress(span);
1723
1850
  let resolvedViaAddress = false;
1724
1851
  if (host && host !== span.service) {
1725
- const targetId = resolveServiceId(ctx.graph, host);
1852
+ const targetId = resolveServiceId(ctx.graph, host, env);
1726
1853
  if (targetId && targetId !== sourceId) {
1727
1854
  upsertObservedEdge(
1728
1855
  ctx.graph,
@@ -1749,9 +1876,9 @@ async function handleSpan(ctx, span) {
1749
1876
  }
1750
1877
  }
1751
1878
  if (!resolvedViaAddress && span.parentSpanId) {
1752
- const parentService = lookupParentSpanService(span.traceId, span.parentSpanId, nowMs);
1753
- if (parentService && parentService !== span.service) {
1754
- const parentId = ensureServiceNode(ctx.graph, parentService);
1879
+ const parent = lookupParentSpan(span.traceId, span.parentSpanId, nowMs);
1880
+ if (parent && parent.service !== span.service) {
1881
+ const parentId = ensureServiceNode(ctx.graph, parent.service, parent.env);
1755
1882
  upsertObservedEdge(
1756
1883
  ctx.graph,
1757
1884
  import_types3.EdgeType.CALLS,
@@ -1947,6 +2074,28 @@ async function readErrorEvents(errorsPath) {
1947
2074
  throw err;
1948
2075
  }
1949
2076
  }
2077
+ function mergeSnapshot(graph, snapshot) {
2078
+ const exported = snapshot.graph;
2079
+ let nodesAdded = 0;
2080
+ let edgesAdded = 0;
2081
+ for (const node of exported.nodes ?? []) {
2082
+ if (graph.hasNode(node.key)) continue;
2083
+ if (!node.attributes) continue;
2084
+ graph.addNode(node.key, node.attributes);
2085
+ nodesAdded++;
2086
+ }
2087
+ for (const edge of exported.edges ?? []) {
2088
+ const attrs = edge.attributes;
2089
+ if (!attrs) continue;
2090
+ const id = edge.key ?? attrs.id;
2091
+ if (!id) continue;
2092
+ if (graph.hasEdge(id)) continue;
2093
+ if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
2094
+ graph.addEdgeWithKey(id, edge.source, edge.target, attrs);
2095
+ edgesAdded++;
2096
+ }
2097
+ return { nodesAdded, edgesAdded };
2098
+ }
1950
2099
 
1951
2100
  // src/extract/services.ts
1952
2101
  init_cjs_shims();
@@ -2343,6 +2492,18 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2343
2492
  }
2344
2493
  return [...found];
2345
2494
  }
2495
+ function detectJsFramework(pkg) {
2496
+ const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
2497
+ if (deps["next"] !== void 0) return "next";
2498
+ if (deps["remix"] !== void 0) return "remix";
2499
+ for (const k of Object.keys(deps)) {
2500
+ if (k.startsWith("@remix-run/")) return "remix";
2501
+ }
2502
+ if (deps["@sveltejs/kit"] !== void 0) return "sveltekit";
2503
+ if (deps["nuxt"] !== void 0) return "nuxt";
2504
+ if (deps["astro"] !== void 0) return "astro";
2505
+ return void 0;
2506
+ }
2346
2507
  async function discoverNodeService(scanPath, dir) {
2347
2508
  const pkgPath = import_node_path8.default.join(dir, "package.json");
2348
2509
  if (!await exists(pkgPath)) return null;
@@ -2354,6 +2515,7 @@ async function discoverNodeService(scanPath, dir) {
2354
2515
  return null;
2355
2516
  }
2356
2517
  if (!pkg.name) return null;
2518
+ const framework = detectJsFramework(pkg);
2357
2519
  const node = {
2358
2520
  id: (0, import_types5.serviceId)(pkg.name),
2359
2521
  type: import_types5.NodeType.ServiceNode,
@@ -2362,7 +2524,8 @@ async function discoverNodeService(scanPath, dir) {
2362
2524
  version: pkg.version,
2363
2525
  dependencies: pkg.dependencies ?? {},
2364
2526
  repoPath: import_node_path8.default.relative(scanPath, dir),
2365
- ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
2527
+ ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
2528
+ ...framework ? { framework } : {}
2366
2529
  };
2367
2530
  return { pkg, dir, node };
2368
2531
  }
@@ -4145,7 +4308,7 @@ init_cjs_shims();
4145
4308
  var import_node_fs18 = require("fs");
4146
4309
  var import_node_path31 = __toESM(require("path"), 1);
4147
4310
  var import_types19 = require("@neat.is/types");
4148
- var SCHEMA_VERSION = 3;
4311
+ var SCHEMA_VERSION = 4;
4149
4312
  function migrateV1ToV2(payload) {
4150
4313
  const nodes = payload.graph.nodes;
4151
4314
  if (Array.isArray(nodes)) {
@@ -4157,6 +4320,9 @@ function migrateV1ToV2(payload) {
4157
4320
  }
4158
4321
  return { ...payload, schemaVersion: 2 };
4159
4322
  }
4323
+ function migrateV3ToV4(payload) {
4324
+ return { ...payload, schemaVersion: 4 };
4325
+ }
4160
4326
  function migrateV2ToV3(payload) {
4161
4327
  const edges = payload.graph.edges;
4162
4328
  if (Array.isArray(edges)) {
@@ -4205,6 +4371,9 @@ async function loadGraphFromDisk(graph, outPath) {
4205
4371
  if (payload.schemaVersion === 2) {
4206
4372
  payload = migrateV2ToV3(payload);
4207
4373
  }
4374
+ if (payload.schemaVersion === 3) {
4375
+ payload = migrateV3ToV4(payload);
4376
+ }
4208
4377
  if (payload.schemaVersion !== SCHEMA_VERSION) {
4209
4378
  throw new Error(
4210
4379
  `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
@@ -4843,6 +5012,7 @@ data: ${JSON.stringify(envelope.payload)}
4843
5012
  }
4844
5013
 
4845
5014
  // src/api.ts
5015
+ init_auth();
4846
5016
  function serializeGraph(graph) {
4847
5017
  const nodes = [];
4848
5018
  graph.forEachNode((_id, attrs) => {
@@ -5115,6 +5285,35 @@ function registerRoutes(scope, ctx) {
5115
5285
  }
5116
5286
  }
5117
5287
  );
5288
+ scope.post("/snapshot", async (req, reply) => {
5289
+ const proj = resolveProject(registry, req, reply);
5290
+ if (!proj) return;
5291
+ const body = req.body;
5292
+ if (!body || typeof body !== "object" || !body.snapshot) {
5293
+ return reply.code(400).send({ error: "request body must be { snapshot: <persisted-graph> }" });
5294
+ }
5295
+ const snap = body.snapshot;
5296
+ if (typeof snap.schemaVersion !== "number" || snap.schemaVersion !== SCHEMA_VERSION) {
5297
+ return reply.code(400).send({
5298
+ error: `unsupported snapshot schemaVersion ${snap.schemaVersion} (expected ${SCHEMA_VERSION})`
5299
+ });
5300
+ }
5301
+ try {
5302
+ const result = mergeSnapshot(proj.graph, snap);
5303
+ return {
5304
+ project: proj.name,
5305
+ nodesAdded: result.nodesAdded,
5306
+ edgesAdded: result.edgesAdded,
5307
+ nodeCount: proj.graph.order,
5308
+ edgeCount: proj.graph.size
5309
+ };
5310
+ } catch (err) {
5311
+ return reply.code(400).send({
5312
+ error: "snapshot merge failed",
5313
+ details: err.message
5314
+ });
5315
+ }
5316
+ });
5118
5317
  scope.post("/graph/scan", async (req, reply) => {
5119
5318
  const proj = resolveProject(registry, req, reply);
5120
5319
  if (!proj) return;
@@ -5208,6 +5407,15 @@ function registerRoutes(scope, ctx) {
5208
5407
  async function buildApi(opts) {
5209
5408
  const app = (0, import_fastify.default)({ logger: false });
5210
5409
  await app.register(import_cors.default, { origin: true });
5410
+ mountBearerAuth(app, {
5411
+ token: opts.authToken,
5412
+ trustProxy: opts.trustProxy,
5413
+ publicRead: opts.publicRead
5414
+ });
5415
+ app.get("/api/config", async () => ({
5416
+ publicRead: opts.publicRead === true,
5417
+ authProxy: opts.trustProxy === true
5418
+ }));
5211
5419
  const startedAt = opts.startedAt ?? Date.now();
5212
5420
  const registry = buildLegacyRegistry(opts);
5213
5421
  const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== void 0;
@@ -5278,6 +5486,7 @@ init_cjs_shims();
5278
5486
  var import_node_fs21 = require("fs");
5279
5487
  var import_node_path36 = __toESM(require("path"), 1);
5280
5488
  init_otel();
5489
+ init_auth();
5281
5490
  function neatHomeFor(opts) {
5282
5491
  if (opts.neatHome && opts.neatHome.length > 0) return import_node_path36.default.resolve(opts.neatHome);
5283
5492
  const env = process.env.NEAT_HOME;
@@ -5484,8 +5693,15 @@ async function startDaemon(opts = {}) {
5484
5693
  const host = resolveHost(opts);
5485
5694
  const restPort = resolveRestPort(opts);
5486
5695
  const otlpPort = resolveOtlpPort(opts);
5696
+ const auth = readAuthEnv();
5697
+ assertBindAuthority(host, auth.authToken);
5487
5698
  try {
5488
- restApp = await buildApi({ projects: registry });
5699
+ restApp = await buildApi({
5700
+ projects: registry,
5701
+ authToken: auth.authToken,
5702
+ trustProxy: auth.trustProxy,
5703
+ publicRead: auth.publicRead
5704
+ });
5489
5705
  restAddress = await restApp.listen({ port: restPort, host });
5490
5706
  console.log(`neatd: REST listening on ${restAddress}`);
5491
5707
  } catch (err) {
@@ -5522,6 +5738,8 @@ async function startDaemon(opts = {}) {
5522
5738
  }
5523
5739
  try {
5524
5740
  otlpApp = await buildOtelReceiver({
5741
+ authToken: auth.otelToken,
5742
+ trustProxy: auth.trustProxy,
5525
5743
  onSpan: async (span) => {
5526
5744
  const slot = await resolveTargetSlot(span.service);
5527
5745
  if (!slot) return;