@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 +105 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +105 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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) {
|