@neat.is/core 0.9.20-dev.20260922 → 0.9.21-dev.20260923

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/neatd.js CHANGED
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  reconcileDaemonRecordSync,
4
4
  startDaemon
5
- } from "./chunk-F7I3G4KC.js";
5
+ } from "./chunk-SK2FEJKB.js";
6
6
  import {
7
7
  listProjects,
8
8
  registryPath
9
- } from "./chunk-KIV7OB2K.js";
9
+ } from "./chunk-NFJ4W5T6.js";
10
10
  import {
11
11
  BindAuthorityError,
12
12
  __require
package/dist/server.cjs CHANGED
@@ -5704,9 +5704,30 @@ function withRecoveredCodeAttrs(attrs, locus) {
5704
5704
  if (locus.codeLineno !== void 0) attrs[CODE_LINENO_ATTR] = locus.codeLineno;
5705
5705
  return attrs;
5706
5706
  }
5707
+ var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
5708
+ "authorization",
5709
+ "proxy-authorization",
5710
+ "cookie",
5711
+ "set-cookie",
5712
+ "x-api-key",
5713
+ "api-key",
5714
+ "x-auth-token",
5715
+ "x-amz-security-token",
5716
+ "x-csrf-token"
5717
+ ]);
5718
+ function attrHeaderName(key) {
5719
+ const lower = key.toLowerCase();
5720
+ const marker = ".header.";
5721
+ const at = lower.lastIndexOf(marker);
5722
+ return at >= 0 ? lower.slice(at + marker.length) : lower;
5723
+ }
5724
+ function isSensitiveAttrKey(key) {
5725
+ return SENSITIVE_HEADER_NAMES.has(attrHeaderName(key));
5726
+ }
5707
5727
  function sanitizeAttributes(attrs) {
5708
5728
  const out = {};
5709
5729
  for (const [k, v] of Object.entries(attrs)) {
5730
+ if (isSensitiveAttrKey(k)) continue;
5710
5731
  if (typeof v === "bigint") out[k] = v.toString();
5711
5732
  else out[k] = v;
5712
5733
  }
@@ -6262,6 +6283,14 @@ async function readErrorFileTail(errorsPath, maxBytes) {
6262
6283
  await handle.close();
6263
6284
  }
6264
6285
  }
6286
+ function redactPersistedAttributes(ev) {
6287
+ if (!ev.attributes) return ev;
6288
+ const scrubbed = sanitizeAttributes(ev.attributes);
6289
+ if (Object.keys(scrubbed).length === Object.keys(ev.attributes).length) return ev;
6290
+ if (Object.keys(scrubbed).length > 0) return { ...ev, attributes: scrubbed };
6291
+ const { attributes: _dropped, ...rest } = ev;
6292
+ return rest;
6293
+ }
6265
6294
  async function readErrorEvents(errorsPath, opts) {
6266
6295
  let raw;
6267
6296
  try {
@@ -6270,7 +6299,7 @@ async function readErrorEvents(errorsPath, opts) {
6270
6299
  if (err.code === "ENOENT") return [];
6271
6300
  throw err;
6272
6301
  }
6273
- const events = raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
6302
+ const events = raw.split("\n").filter((line) => line.length > 0).map((line) => redactPersistedAttributes(JSON.parse(line)));
6274
6303
  const deduped = dedupeIncidents(events);
6275
6304
  const cap = opts?.limit !== void 0 && opts.limit > 0 ? Math.min(opts.limit, INCIDENT_READ_MAX_EVENTS) : INCIDENT_READ_MAX_EVENTS;
6276
6305
  return deduped.length > cap ? deduped.slice(deduped.length - cap) : deduped;
@@ -18756,6 +18785,10 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
18756
18785
  // src/connectors/registry.ts
18757
18786
  init_cjs_shims();
18758
18787
 
18788
+ // src/connectors/gcp-auth.ts
18789
+ init_cjs_shims();
18790
+ var import_node_crypto3 = require("crypto");
18791
+
18759
18792
  // src/connectors/junction.ts
18760
18793
  init_cjs_shims();
18761
18794
  var buckets = /* @__PURE__ */ new Map();
@@ -19017,6 +19050,108 @@ async function dbJunction(run, policy) {
19017
19050
  }
19018
19051
  }
19019
19052
 
19053
+ // src/connectors/gcp-auth.ts
19054
+ var DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token";
19055
+ var JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
19056
+ var ASSERTION_LIFETIME_S = 3600;
19057
+ var DEFAULT_REFRESH_SKEW_MS = 6e4;
19058
+ var GCP_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
19059
+ function parseServiceAccountKey(json) {
19060
+ let raw;
19061
+ try {
19062
+ raw = JSON.parse(json);
19063
+ } catch {
19064
+ throw new Error("gcp service-account key: not valid JSON");
19065
+ }
19066
+ if (!raw || typeof raw !== "object") {
19067
+ throw new Error("gcp service-account key: expected a JSON object");
19068
+ }
19069
+ const obj = raw;
19070
+ const clientEmail = obj["client_email"];
19071
+ const privateKey = obj["private_key"];
19072
+ const projectId = obj["project_id"];
19073
+ if (typeof clientEmail !== "string" || clientEmail.length === 0) {
19074
+ throw new Error("gcp service-account key: missing client_email");
19075
+ }
19076
+ if (typeof privateKey !== "string" || privateKey.length === 0) {
19077
+ throw new Error("gcp service-account key: missing private_key");
19078
+ }
19079
+ if (typeof projectId !== "string" || projectId.length === 0) {
19080
+ throw new Error("gcp service-account key: missing project_id");
19081
+ }
19082
+ const key = { client_email: clientEmail, private_key: privateKey, project_id: projectId };
19083
+ if (typeof obj["token_uri"] === "string" && obj["token_uri"].length > 0) {
19084
+ key.token_uri = obj["token_uri"];
19085
+ }
19086
+ return key;
19087
+ }
19088
+ function base64url(input) {
19089
+ return Buffer.from(input).toString("base64url");
19090
+ }
19091
+ function buildServiceAccountAssertion(key, scope, tokenUri, nowMs) {
19092
+ const iat = Math.floor(nowMs / 1e3);
19093
+ const header = { alg: "RS256", typ: "JWT" };
19094
+ const claims = {
19095
+ iss: key.client_email,
19096
+ scope,
19097
+ aud: tokenUri,
19098
+ iat,
19099
+ exp: iat + ASSERTION_LIFETIME_S
19100
+ };
19101
+ const signingInput = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(claims))}`;
19102
+ const signature = (0, import_node_crypto3.createSign)("RSA-SHA256").update(signingInput).end().sign(key.private_key);
19103
+ return `${signingInput}.${base64url(signature)}`;
19104
+ }
19105
+ async function mintGcpAccessToken(key, scope, opts = {}) {
19106
+ const now = opts.now ?? Date.now;
19107
+ const tokenUri = opts.tokenUri ?? key.token_uri ?? DEFAULT_TOKEN_URI;
19108
+ const mintedAtMs = now();
19109
+ const assertion = buildServiceAccountAssertion(key, scope, tokenUri, mintedAtMs);
19110
+ const body = new URLSearchParams({ grant_type: JWT_BEARER_GRANT, assertion }).toString();
19111
+ const res = await junctionFetch(
19112
+ tokenUri,
19113
+ {
19114
+ method: "POST",
19115
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
19116
+ body
19117
+ },
19118
+ // accountKey: the GCP project id — an identifier, safe to log, the same
19119
+ // per-(provider, accountKey) rate-limit bucket key the GCP connectors use.
19120
+ { provider: "gcp-auth", accountKey: key.project_id, ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {} }
19121
+ );
19122
+ if (!res.ok) {
19123
+ throw new Error(`gcp token mint failed: ${res.status} ${res.statusText}`);
19124
+ }
19125
+ const json = await res.json();
19126
+ const accessToken = json.access_token;
19127
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
19128
+ throw new Error("gcp token mint returned no access_token");
19129
+ }
19130
+ const expiresInS = typeof json.expires_in === "number" && json.expires_in > 0 ? json.expires_in : ASSERTION_LIFETIME_S;
19131
+ return { accessToken, expiresAtMs: mintedAtMs + expiresInS * 1e3 };
19132
+ }
19133
+ function createGcpTokenSource(key, scope, opts = {}) {
19134
+ const now = opts.now ?? Date.now;
19135
+ const skewMs = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;
19136
+ let cached = null;
19137
+ let inFlight = null;
19138
+ const fresh = () => cached !== null && now() < cached.expiresAtMs - skewMs;
19139
+ return async () => {
19140
+ if (!fresh()) {
19141
+ if (!inFlight) {
19142
+ inFlight = mintGcpAccessToken(key, scope, opts).then((minted) => {
19143
+ cached = minted;
19144
+ return minted;
19145
+ }).finally(() => {
19146
+ inFlight = null;
19147
+ });
19148
+ }
19149
+ await inFlight;
19150
+ }
19151
+ return { projectId: key.project_id, accessToken: cached.accessToken };
19152
+ };
19153
+ }
19154
+
19020
19155
  // src/connectors/supabase/index.ts
19021
19156
  init_cjs_shims();
19022
19157
 
@@ -19883,7 +20018,7 @@ var import_types73 = require("@neat.is/types");
19883
20018
 
19884
20019
  // src/connectors/cloudflare/client.ts
19885
20020
  init_cjs_shims();
19886
- var import_node_crypto3 = require("crypto");
20021
+ var import_node_crypto4 = require("crypto");
19887
20022
  var DEFAULT_BASE_URL = "https://api.cloudflare.com/client/v4";
19888
20023
  var DEFAULT_EVENT_LIMIT = 1e3;
19889
20024
  async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
@@ -19896,7 +20031,7 @@ async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
19896
20031
  const body = {
19897
20032
  // Cloudflare's schema requires an identifier per query even for an
19898
20033
  // ad-hoc, unsaved one — a fresh id per tick, never reused.
19899
- queryId: `neat-connector-${(0, import_node_crypto3.randomUUID)()}`,
20034
+ queryId: `neat-connector-${(0, import_node_crypto4.randomUUID)()}`,
19900
20035
  timeframe: { from: window.fromMs, to: window.toMs },
19901
20036
  view: "events",
19902
20037
  limit: config.eventLimit ?? DEFAULT_EVENT_LIMIT,
@@ -21898,7 +22033,8 @@ var PUSH_PROVIDER_DISPATCH = {
21898
22033
  function isPushProvider(provider) {
21899
22034
  return provider in PUSH_PROVIDER_DISPATCH;
21900
22035
  }
21901
- function resolveEntryCredentials(dispatch, entry, env) {
22036
+ var GCP_SERVICE_ACCOUNT_PROVIDERS = /* @__PURE__ */ new Set(["cloud-run", "gcp-lb", "firebase"]);
22037
+ function resolveEntryCredentials(dispatch, entry, env, fetchImpl) {
21902
22038
  let credentials;
21903
22039
  try {
21904
22040
  const resolved = resolveCredential(entry.credential, env);
@@ -21907,6 +22043,15 @@ function resolveEntryCredentials(dispatch, entry, env) {
21907
22043
  if (err instanceof EnvRefUnsetError) return { ok: false, kind: "unset-env", reason: err.message };
21908
22044
  return { ok: false, kind: "error", reason: err.message };
21909
22045
  }
22046
+ if (GCP_SERVICE_ACCOUNT_PROVIDERS.has(entry.provider) && typeof credentials.serviceAccountKey === "string" && credentials.serviceAccountKey.length > 0) {
22047
+ try {
22048
+ const key = parseServiceAccountKey(credentials.serviceAccountKey);
22049
+ const source = createGcpTokenSource(key, GCP_LOGGING_READ_SCOPE, fetchImpl ? { fetchImpl } : {});
22050
+ return { ok: true, credentials: { projectId: key.project_id }, refreshCredentials: source };
22051
+ } catch (err) {
22052
+ return { ok: false, kind: "error", reason: err.message };
22053
+ }
22054
+ }
21910
22055
  const missingCreds = dispatch.requiredCredentialFields.filter((k) => !credentials[k]);
21911
22056
  if (missingCreds.length > 0) {
21912
22057
  return {
@@ -21956,7 +22101,10 @@ function buildRegistration(entry, graph, env = process.env) {
21956
22101
  connector: built.connector,
21957
22102
  credentials,
21958
22103
  resolveTarget: built.resolveTarget,
21959
- ...intervalMs !== void 0 ? { intervalMs } : {}
22104
+ ...intervalMs !== void 0 ? { intervalMs } : {},
22105
+ // A refreshable credential (ADR-230) resolves to a per-tick token source; carry it so the daemon wires
22106
+ // it to the poll loop's `refreshCredentials`. Absent for a static credential.
22107
+ ...creds.refreshCredentials ? { refreshCredentials: creds.refreshCredentials } : {}
21960
22108
  }
21961
22109
  };
21962
22110
  }
@@ -22884,7 +23032,7 @@ init_otel_grpc();
22884
23032
  init_cjs_shims();
22885
23033
  var import_node_fs39 = require("fs");
22886
23034
  var import_node_path75 = __toESM(require("path"), 1);
22887
- var import_node_crypto4 = require("crypto");
23035
+ var import_node_crypto5 = require("crypto");
22888
23036
  var DEFAULT_LIMIT = 10;
22889
23037
  var NOMIC_DIM = 768;
22890
23038
  var MINI_LM_DIM = 384;
@@ -22948,7 +23096,7 @@ function embedText(node) {
22948
23096
  return parts.join(" ");
22949
23097
  }
22950
23098
  function attrsHash(node) {
22951
- return (0, import_node_crypto4.createHash)("sha1").update(embedText(node)).digest("hex").slice(0, 16);
23099
+ return (0, import_node_crypto5.createHash)("sha1").update(embedText(node)).digest("hex").slice(0, 16);
22952
23100
  }
22953
23101
  function cosine(a, b) {
22954
23102
  if (a.length !== b.length) return 0;