@diegotsi/flint-core 1.10.1 → 1.10.2

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
@@ -851,6 +851,100 @@ function createFrustrationCollector(opts) {
851
851
  };
852
852
  }
853
853
 
854
+ // src/collectors/sanitize.ts
855
+ var REDACTED = "[REDACTED]";
856
+ var DEFAULT_REDACT_KEYS = [
857
+ "password",
858
+ "passwd",
859
+ "pwd",
860
+ "secret",
861
+ "token",
862
+ "auth",
863
+ "credential",
864
+ "apikey",
865
+ "api_key",
866
+ "session",
867
+ "cookie",
868
+ "ssn",
869
+ "creditcard",
870
+ "credit_card",
871
+ "card_number",
872
+ "cardnumber",
873
+ "cvv",
874
+ "cvc",
875
+ "pin"
876
+ ];
877
+ function createKeyMatcher(extra = []) {
878
+ const needles = [...DEFAULT_REDACT_KEYS, ...extra.map((k) => k.toLowerCase())];
879
+ return (key) => {
880
+ const lower = key.toLowerCase();
881
+ return needles.some((n) => lower.includes(n));
882
+ };
883
+ }
884
+ function redactValue(value, match) {
885
+ if (Array.isArray(value)) return value.map((v) => redactValue(v, match));
886
+ if (value && typeof value === "object") {
887
+ const out = {};
888
+ for (const [k, v] of Object.entries(value)) {
889
+ out[k] = match(k) ? REDACTED : redactValue(v, match);
890
+ }
891
+ return out;
892
+ }
893
+ return value;
894
+ }
895
+ function redactQueryString(qs, match) {
896
+ if (!qs) return qs;
897
+ return qs.split("&").map((pair) => {
898
+ const eq = pair.indexOf("=");
899
+ if (eq === -1) return pair;
900
+ const rawKey = pair.slice(0, eq);
901
+ let key = rawKey;
902
+ try {
903
+ key = decodeURIComponent(rawKey.replace(/\+/g, " "));
904
+ } catch {
905
+ }
906
+ return match(key) ? `${rawKey}=${REDACTED}` : pair;
907
+ }).join("&");
908
+ }
909
+ function sanitizeBody(body, match) {
910
+ if (!body) return body;
911
+ const trimmed = body.trimStart();
912
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
913
+ try {
914
+ const parsed = JSON.parse(body);
915
+ return JSON.stringify(redactValue(parsed, match));
916
+ } catch {
917
+ }
918
+ }
919
+ if (body.includes("=") && !/\s/.test(body)) {
920
+ const redacted = redactQueryString(body, match);
921
+ if (redacted !== body) return redacted;
922
+ }
923
+ return body.replace(
924
+ /("?)([\w.-]+)\1\s*:\s*"(?:[^"\\]|\\.)*"/gi,
925
+ (full, q, key) => match(key) ? `${q}${key}${q}:"${REDACTED}"` : full
926
+ ).replace(/([\w.-]+)=([^&\s]+)/gi, (full, key) => match(key) ? `${key}=${REDACTED}` : full);
927
+ }
928
+ function sanitizeUrl(url, match) {
929
+ const qIdx = url.indexOf("?");
930
+ if (qIdx === -1) return url;
931
+ const base = url.slice(0, qIdx);
932
+ const query = url.slice(qIdx + 1);
933
+ const hashIdx = query.indexOf("#");
934
+ const qs = hashIdx === -1 ? query : query.slice(0, hashIdx);
935
+ const hash = hashIdx === -1 ? "" : query.slice(hashIdx);
936
+ const redacted = redactQueryString(qs, match);
937
+ return `${base}?${redacted}${hash}`;
938
+ }
939
+ function sanitizeHeaders(headers, match) {
940
+ if (!headers) return headers;
941
+ const out = {};
942
+ for (const [k, v] of Object.entries(headers)) {
943
+ out[k] = match(k) ? REDACTED : v;
944
+ }
945
+ return out;
946
+ }
947
+
854
948
  // src/collectors/network.ts
855
949
  var MAX_ENTRIES3 = 50;
856
950
  var MAX_FULL_URL = 2e3;
@@ -1026,6 +1120,7 @@ function extractContentType(headers) {
1026
1120
  function createNetworkCollector(extraBlockedHosts = [], options) {
1027
1121
  const maxEntries = options?.maxEntries ?? MAX_ENTRIES3;
1028
1122
  const maxResponseBody = options?.maxResponseBody ?? MAX_RESPONSE_BODY;
1123
+ const matchSecret = createKeyMatcher(options?.redactKeys);
1029
1124
  const entries = [];
1030
1125
  const blocked = /* @__PURE__ */ new Set([...DEFAULT_BLOCKED_HOSTS, ...extraBlockedHosts]);
1031
1126
  let origFetch = null;
@@ -1037,6 +1132,12 @@ function createNetworkCollector(extraBlockedHosts = [], options) {
1037
1132
  const seenUrls = /* @__PURE__ */ new Set();
1038
1133
  function push(entry) {
1039
1134
  seenUrls.add(entry.fullUrl ?? entry.url);
1135
+ if (entry.fullUrl) entry.fullUrl = sanitizeUrl(entry.fullUrl, matchSecret);
1136
+ entry.url = sanitizeUrl(entry.url, matchSecret);
1137
+ entry.requestBody = sanitizeBody(entry.requestBody, matchSecret);
1138
+ entry.responseBody = sanitizeBody(entry.responseBody, matchSecret);
1139
+ entry.requestHeaders = sanitizeHeaders(entry.requestHeaders, matchSecret);
1140
+ entry.responseHeaders = sanitizeHeaders(entry.responseHeaders, matchSecret);
1040
1141
  entries.push(entry);
1041
1142
  if (entries.length > maxEntries) entries.shift();
1042
1143
  }
@@ -1102,7 +1203,8 @@ function createNetworkCollector(extraBlockedHosts = [], options) {
1102
1203
  try {
1103
1204
  const clone = res.clone();
1104
1205
  clone.text().then((text) => {
1105
- entry.responseBody = text.length > maxResponseBody ? text.slice(0, maxResponseBody) + "\u2026" : text;
1206
+ const truncated = text.length > maxResponseBody ? text.slice(0, maxResponseBody) + "\u2026" : text;
1207
+ entry.responseBody = sanitizeBody(truncated, matchSecret);
1106
1208
  if (!entry.responseSize && text.length) entry.responseSize = text.length;
1107
1209
  }).catch(() => {
1108
1210
  });
@@ -1335,6 +1437,7 @@ function init(config) {
1335
1437
  enableReplay = false,
1336
1438
  replayBufferMs = DEFAULT_REPLAY_BUFFER_MS,
1337
1439
  blockedHosts = [],
1440
+ redactKeys,
1338
1441
  frustration: frustrationOpts,
1339
1442
  onFrustration,
1340
1443
  _replayRecorder,
@@ -1358,7 +1461,7 @@ function init(config) {
1358
1461
  const allBlockedHosts = [...blockedHosts, ...flintHost ? [flintHost] : []];
1359
1462
  const consoleCol = enableConsole ? _collectors?.console?.() ?? createConsoleCollector() : null;
1360
1463
  consoleCol?.start();
1361
- const networkCol = enableNetwork ? _collectors?.network?.(allBlockedHosts) ?? createNetworkCollector(allBlockedHosts) : null;
1464
+ const networkCol = enableNetwork ? _collectors?.network?.(allBlockedHosts, { redactKeys }) ?? createNetworkCollector(allBlockedHosts, { redactKeys }) : null;
1362
1465
  networkCol?.start();
1363
1466
  const formErrorsCol = enableFormErrors ? createFormErrorCollector() : null;
1364
1467
  if (formErrorsCol) {