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

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.
@@ -4690,9 +4690,30 @@ function withRecoveredCodeAttrs(attrs, locus) {
4690
4690
  if (locus.codeLineno !== void 0) attrs[CODE_LINENO_ATTR] = locus.codeLineno;
4691
4691
  return attrs;
4692
4692
  }
4693
+ var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
4694
+ "authorization",
4695
+ "proxy-authorization",
4696
+ "cookie",
4697
+ "set-cookie",
4698
+ "x-api-key",
4699
+ "api-key",
4700
+ "x-auth-token",
4701
+ "x-amz-security-token",
4702
+ "x-csrf-token"
4703
+ ]);
4704
+ function attrHeaderName(key) {
4705
+ const lower = key.toLowerCase();
4706
+ const marker = ".header.";
4707
+ const at = lower.lastIndexOf(marker);
4708
+ return at >= 0 ? lower.slice(at + marker.length) : lower;
4709
+ }
4710
+ function isSensitiveAttrKey(key) {
4711
+ return SENSITIVE_HEADER_NAMES.has(attrHeaderName(key));
4712
+ }
4693
4713
  function sanitizeAttributes(attrs) {
4694
4714
  const out = {};
4695
4715
  for (const [k, v] of Object.entries(attrs)) {
4716
+ if (isSensitiveAttrKey(k)) continue;
4696
4717
  if (typeof v === "bigint") out[k] = v.toString();
4697
4718
  else out[k] = v;
4698
4719
  }
@@ -5283,6 +5304,14 @@ async function readErrorFileTail(errorsPath, maxBytes) {
5283
5304
  await handle.close();
5284
5305
  }
5285
5306
  }
5307
+ function redactPersistedAttributes(ev) {
5308
+ if (!ev.attributes) return ev;
5309
+ const scrubbed = sanitizeAttributes(ev.attributes);
5310
+ if (Object.keys(scrubbed).length === Object.keys(ev.attributes).length) return ev;
5311
+ if (Object.keys(scrubbed).length > 0) return { ...ev, attributes: scrubbed };
5312
+ const { attributes: _dropped, ...rest } = ev;
5313
+ return rest;
5314
+ }
5286
5315
  async function readErrorEvents(errorsPath, opts) {
5287
5316
  let raw;
5288
5317
  try {
@@ -5291,7 +5320,7 @@ async function readErrorEvents(errorsPath, opts) {
5291
5320
  if (err.code === "ENOENT") return [];
5292
5321
  throw err;
5293
5322
  }
5294
- const events = raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
5323
+ const events = raw.split("\n").filter((line) => line.length > 0).map((line) => redactPersistedAttributes(JSON.parse(line)));
5295
5324
  const deduped = dedupeIncidents(events);
5296
5325
  const cap = opts?.limit !== void 0 && opts.limit > 0 ? Math.min(opts.limit, INCIDENT_READ_MAX_EVENTS) : INCIDENT_READ_MAX_EVENTS;
5297
5326
  return deduped.length > cap ? deduped.slice(deduped.length - cap) : deduped;
@@ -18599,6 +18628,9 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
18599
18628
  };
18600
18629
  }
18601
18630
 
18631
+ // src/connectors/gcp-auth.ts
18632
+ import { createSign } from "crypto";
18633
+
18602
18634
  // src/connectors/junction.ts
18603
18635
  var buckets = /* @__PURE__ */ new Map();
18604
18636
  function bucketMapKey(provider, accountKey) {
@@ -18859,6 +18891,108 @@ async function dbJunction(run, policy) {
18859
18891
  }
18860
18892
  }
18861
18893
 
18894
+ // src/connectors/gcp-auth.ts
18895
+ var DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token";
18896
+ var JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer";
18897
+ var ASSERTION_LIFETIME_S = 3600;
18898
+ var DEFAULT_REFRESH_SKEW_MS = 6e4;
18899
+ var GCP_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
18900
+ function parseServiceAccountKey(json) {
18901
+ let raw;
18902
+ try {
18903
+ raw = JSON.parse(json);
18904
+ } catch {
18905
+ throw new Error("gcp service-account key: not valid JSON");
18906
+ }
18907
+ if (!raw || typeof raw !== "object") {
18908
+ throw new Error("gcp service-account key: expected a JSON object");
18909
+ }
18910
+ const obj = raw;
18911
+ const clientEmail = obj["client_email"];
18912
+ const privateKey = obj["private_key"];
18913
+ const projectId = obj["project_id"];
18914
+ if (typeof clientEmail !== "string" || clientEmail.length === 0) {
18915
+ throw new Error("gcp service-account key: missing client_email");
18916
+ }
18917
+ if (typeof privateKey !== "string" || privateKey.length === 0) {
18918
+ throw new Error("gcp service-account key: missing private_key");
18919
+ }
18920
+ if (typeof projectId !== "string" || projectId.length === 0) {
18921
+ throw new Error("gcp service-account key: missing project_id");
18922
+ }
18923
+ const key = { client_email: clientEmail, private_key: privateKey, project_id: projectId };
18924
+ if (typeof obj["token_uri"] === "string" && obj["token_uri"].length > 0) {
18925
+ key.token_uri = obj["token_uri"];
18926
+ }
18927
+ return key;
18928
+ }
18929
+ function base64url(input) {
18930
+ return Buffer.from(input).toString("base64url");
18931
+ }
18932
+ function buildServiceAccountAssertion(key, scope, tokenUri, nowMs) {
18933
+ const iat = Math.floor(nowMs / 1e3);
18934
+ const header = { alg: "RS256", typ: "JWT" };
18935
+ const claims = {
18936
+ iss: key.client_email,
18937
+ scope,
18938
+ aud: tokenUri,
18939
+ iat,
18940
+ exp: iat + ASSERTION_LIFETIME_S
18941
+ };
18942
+ const signingInput = `${base64url(JSON.stringify(header))}.${base64url(JSON.stringify(claims))}`;
18943
+ const signature = createSign("RSA-SHA256").update(signingInput).end().sign(key.private_key);
18944
+ return `${signingInput}.${base64url(signature)}`;
18945
+ }
18946
+ async function mintGcpAccessToken(key, scope, opts = {}) {
18947
+ const now = opts.now ?? Date.now;
18948
+ const tokenUri = opts.tokenUri ?? key.token_uri ?? DEFAULT_TOKEN_URI;
18949
+ const mintedAtMs = now();
18950
+ const assertion = buildServiceAccountAssertion(key, scope, tokenUri, mintedAtMs);
18951
+ const body = new URLSearchParams({ grant_type: JWT_BEARER_GRANT, assertion }).toString();
18952
+ const res = await junctionFetch(
18953
+ tokenUri,
18954
+ {
18955
+ method: "POST",
18956
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
18957
+ body
18958
+ },
18959
+ // accountKey: the GCP project id — an identifier, safe to log, the same
18960
+ // per-(provider, accountKey) rate-limit bucket key the GCP connectors use.
18961
+ { provider: "gcp-auth", accountKey: key.project_id, ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {} }
18962
+ );
18963
+ if (!res.ok) {
18964
+ throw new Error(`gcp token mint failed: ${res.status} ${res.statusText}`);
18965
+ }
18966
+ const json = await res.json();
18967
+ const accessToken = json.access_token;
18968
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
18969
+ throw new Error("gcp token mint returned no access_token");
18970
+ }
18971
+ const expiresInS = typeof json.expires_in === "number" && json.expires_in > 0 ? json.expires_in : ASSERTION_LIFETIME_S;
18972
+ return { accessToken, expiresAtMs: mintedAtMs + expiresInS * 1e3 };
18973
+ }
18974
+ function createGcpTokenSource(key, scope, opts = {}) {
18975
+ const now = opts.now ?? Date.now;
18976
+ const skewMs = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;
18977
+ let cached = null;
18978
+ let inFlight = null;
18979
+ const fresh = () => cached !== null && now() < cached.expiresAtMs - skewMs;
18980
+ return async () => {
18981
+ if (!fresh()) {
18982
+ if (!inFlight) {
18983
+ inFlight = mintGcpAccessToken(key, scope, opts).then((minted) => {
18984
+ cached = minted;
18985
+ return minted;
18986
+ }).finally(() => {
18987
+ inFlight = null;
18988
+ });
18989
+ }
18990
+ await inFlight;
18991
+ }
18992
+ return { projectId: key.project_id, accessToken: cached.accessToken };
18993
+ };
18994
+ }
18995
+
18862
18996
  // src/connectors/supabase/client.ts
18863
18997
  var DEFAULT_SUPABASE_MANAGEMENT_API_URL = "https://api.supabase.com";
18864
18998
  var DEFAULT_LOG_LIMIT = 1e3;
@@ -21665,7 +21799,8 @@ function getProviderFieldSchema(provider) {
21665
21799
  function knownProviderNames() {
21666
21800
  return [...Object.keys(PROVIDER_DISPATCH), ...Object.keys(PUSH_PROVIDER_DISPATCH)].sort();
21667
21801
  }
21668
- function resolveEntryCredentials(dispatch, entry, env) {
21802
+ var GCP_SERVICE_ACCOUNT_PROVIDERS = /* @__PURE__ */ new Set(["cloud-run", "gcp-lb", "firebase"]);
21803
+ function resolveEntryCredentials(dispatch, entry, env, fetchImpl) {
21669
21804
  let credentials;
21670
21805
  try {
21671
21806
  const resolved = resolveCredential(entry.credential, env);
@@ -21674,6 +21809,15 @@ function resolveEntryCredentials(dispatch, entry, env) {
21674
21809
  if (err instanceof EnvRefUnsetError) return { ok: false, kind: "unset-env", reason: err.message };
21675
21810
  return { ok: false, kind: "error", reason: err.message };
21676
21811
  }
21812
+ if (GCP_SERVICE_ACCOUNT_PROVIDERS.has(entry.provider) && typeof credentials.serviceAccountKey === "string" && credentials.serviceAccountKey.length > 0) {
21813
+ try {
21814
+ const key = parseServiceAccountKey(credentials.serviceAccountKey);
21815
+ const source = createGcpTokenSource(key, GCP_LOGGING_READ_SCOPE, fetchImpl ? { fetchImpl } : {});
21816
+ return { ok: true, credentials: { projectId: key.project_id }, refreshCredentials: source };
21817
+ } catch (err) {
21818
+ return { ok: false, kind: "error", reason: err.message };
21819
+ }
21820
+ }
21677
21821
  const missingCreds = dispatch.requiredCredentialFields.filter((k) => !credentials[k]);
21678
21822
  if (missingCreds.length > 0) {
21679
21823
  return {
@@ -21723,7 +21867,10 @@ function buildRegistration(entry, graph, env = process.env) {
21723
21867
  connector: built.connector,
21724
21868
  credentials,
21725
21869
  resolveTarget: built.resolveTarget,
21726
- ...intervalMs !== void 0 ? { intervalMs } : {}
21870
+ ...intervalMs !== void 0 ? { intervalMs } : {},
21871
+ // A refreshable credential (ADR-230) resolves to a per-tick token source; carry it so the daemon wires
21872
+ // it to the poll loop's `refreshCredentials`. Absent for a static credential.
21873
+ ...creds.refreshCredentials ? { refreshCredentials: creds.refreshCredentials } : {}
21727
21874
  }
21728
21875
  };
21729
21876
  }
@@ -21732,7 +21879,7 @@ async function validateConnectorEntry(entry, env = process.env, fetchImpl) {
21732
21879
  if (!dispatch) {
21733
21880
  return { status: "unknown-provider", reason: `unknown provider "${entry.provider}"` };
21734
21881
  }
21735
- const creds = resolveEntryCredentials(dispatch, entry, env);
21882
+ const creds = resolveEntryCredentials(dispatch, entry, env, fetchImpl);
21736
21883
  if (!creds.ok) {
21737
21884
  if (creds.kind === "unset-env") return { status: "unset-env", reason: creds.reason };
21738
21885
  return { status: "missing-field", reason: creds.reason };
@@ -21745,8 +21892,16 @@ async function validateConnectorEntry(entry, env = process.env, fetchImpl) {
21745
21892
  reason: `options missing required field(s): ${missingOpts.join(", ")}`
21746
21893
  };
21747
21894
  }
21895
+ let credentials = creds.credentials;
21896
+ if (creds.refreshCredentials) {
21897
+ try {
21898
+ credentials = { ...credentials, ...await creds.refreshCredentials() };
21899
+ } catch (err) {
21900
+ return { status: "rejected", reason: err.message };
21901
+ }
21902
+ }
21748
21903
  const result = await dispatch.validate({
21749
- credentials: creds.credentials,
21904
+ credentials,
21750
21905
  options,
21751
21906
  ...fetchImpl ? { fetchImpl } : {}
21752
21907
  });
@@ -21792,7 +21947,13 @@ async function startConnectorPolling(input) {
21792
21947
  },
21793
21948
  input.graph,
21794
21949
  registration.resolveTarget,
21795
- { intervalMs: registration.intervalMs, connectorId: registration.id }
21950
+ {
21951
+ intervalMs: registration.intervalMs,
21952
+ connectorId: registration.id,
21953
+ // A refreshable credential (ADR-230) mints a fresh token each tick; the static path leaves this unset
21954
+ // and the loop reuses ctx.credentials, exactly as before.
21955
+ ...registration.refreshCredentials ? { refreshCredentials: registration.refreshCredentials } : {}
21956
+ }
21796
21957
  )
21797
21958
  );
21798
21959
  return () => {
@@ -22859,4 +23020,4 @@ export {
22859
23020
  deprovisionConnector,
22860
23021
  buildApi
22861
23022
  };
22862
- //# sourceMappingURL=chunk-KIV7OB2K.js.map
23023
+ //# sourceMappingURL=chunk-NFJ4W5T6.js.map