@loggerjs/processors 0.3.0 → 0.5.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.js CHANGED
@@ -1006,21 +1006,87 @@ function passesLuhn(input) {
1006
1006
  }
1007
1007
  return sum % 10 === 0;
1008
1008
  }
1009
- const DEFAULT_PATTERNS = [
1010
- {
1011
- name: "email",
1012
- pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi
1013
- },
1014
- {
1015
- name: "bearer-token",
1016
- pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]+\b/g
1017
- },
1018
- {
1019
- name: "credit-card",
1020
- pattern: /\b(?:\d[ -]*?){13,19}\b/g,
1021
- validate: passesLuhn
1009
+ const DEFAULT_PATTERNS = [{
1010
+ name: "bearer-token",
1011
+ pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]+\b/g
1012
+ }, {
1013
+ name: "credit-card",
1014
+ pattern: /\b(?:\d[ -]*?){13,19}\b/g,
1015
+ validate: passesLuhn
1016
+ }];
1017
+ function isAsciiAlpha(code) {
1018
+ return code >= 65 && code <= 90 || code >= 97 && code <= 122;
1019
+ }
1020
+ function isAsciiAlphaNumeric(code) {
1021
+ return isAsciiAlpha(code) || code >= 48 && code <= 57;
1022
+ }
1023
+ function hasChar(code, chars) {
1024
+ return chars.includes(String.fromCharCode(code));
1025
+ }
1026
+ function isEmailLocalChar(code) {
1027
+ return isAsciiAlphaNumeric(code) || hasChar(code, "._%+-");
1028
+ }
1029
+ function isEmailDomainChar(code) {
1030
+ return isAsciiAlphaNumeric(code) || hasChar(code, "-.");
1031
+ }
1032
+ function hasValidEmailDomain(input, start, end) {
1033
+ let labelLength = 0;
1034
+ let dotCount = 0;
1035
+ let tldStart = -1;
1036
+ for (let index = start; index < end; index += 1) {
1037
+ if (input.charCodeAt(index) === 46) {
1038
+ if (labelLength === 0) return false;
1039
+ dotCount += 1;
1040
+ labelLength = 0;
1041
+ tldStart = index + 1;
1042
+ continue;
1043
+ }
1044
+ labelLength += 1;
1022
1045
  }
1023
- ];
1046
+ if (dotCount === 0 || labelLength === 0 || tldStart < 0 || end - tldStart < 2) return false;
1047
+ for (let index = tldStart; index < end; index += 1) if (!isAsciiAlpha(input.charCodeAt(index))) return false;
1048
+ return true;
1049
+ }
1050
+ function redactEmailAddresses(input, path, options) {
1051
+ let output = "";
1052
+ let last = 0;
1053
+ let changed = false;
1054
+ let index = 0;
1055
+ while (index < input.length) {
1056
+ if (!isAsciiAlphaNumeric(input.charCodeAt(index))) {
1057
+ index += 1;
1058
+ continue;
1059
+ }
1060
+ const localStart = index;
1061
+ let cursor = index + 1;
1062
+ while (cursor < input.length && isEmailLocalChar(input.charCodeAt(cursor))) cursor += 1;
1063
+ if (input.charCodeAt(cursor) !== 64) {
1064
+ index = cursor + 1;
1065
+ continue;
1066
+ }
1067
+ const domainStart = cursor + 1;
1068
+ cursor = domainStart;
1069
+ while (cursor < input.length && isEmailDomainChar(input.charCodeAt(cursor))) cursor += 1;
1070
+ if (domainStart < cursor && hasValidEmailDomain(input, domainStart, cursor) && (cursor >= input.length || !isEmailLocalChar(input.charCodeAt(cursor)))) {
1071
+ output += input.slice(last, localStart);
1072
+ output += options.replacement;
1073
+ options.onRedact?.(path, "email");
1074
+ last = cursor;
1075
+ changed = true;
1076
+ index = cursor;
1077
+ continue;
1078
+ }
1079
+ index = domainStart;
1080
+ }
1081
+ if (!changed) return {
1082
+ value: input,
1083
+ changed: false
1084
+ };
1085
+ return {
1086
+ value: output + input.slice(last),
1087
+ changed: true
1088
+ };
1089
+ }
1024
1090
  function matcherMatches(matcher, key, path, value) {
1025
1091
  if (typeof matcher === "string") return matcher.toLowerCase() === key.toLowerCase();
1026
1092
  if (matcher instanceof RegExp) {
@@ -1038,21 +1104,34 @@ function redact(path, reason, options) {
1038
1104
  }
1039
1105
  function guardString(input, path, options) {
1040
1106
  let value = input;
1107
+ let changed = false;
1108
+ if (value.length > options.maxStringLength) {
1109
+ options.onRedact?.(path, "max-string-length");
1110
+ value = `${value.slice(0, options.maxStringLength)}${options.truncateSuffix}`;
1111
+ changed = true;
1112
+ }
1113
+ if (options.redactDefaultEmailPattern) {
1114
+ const emailResult = redactEmailAddresses(value, path, options);
1115
+ value = emailResult.value;
1116
+ changed ||= emailResult.changed;
1117
+ }
1041
1118
  for (const item of options.patterns) {
1042
1119
  item.pattern.lastIndex = 0;
1043
1120
  value = value.replace(item.pattern, (match) => {
1044
1121
  if (item.validate && !item.validate(match)) return match;
1045
1122
  options.onRedact?.(path, item.name);
1123
+ changed = true;
1046
1124
  return item.replacement ?? options.replacement;
1047
1125
  });
1048
1126
  }
1049
1127
  if (value.length > options.maxStringLength) {
1050
1128
  options.onRedact?.(path, "max-string-length");
1051
1129
  value = `${value.slice(0, options.maxStringLength)}${options.truncateSuffix}`;
1130
+ changed = true;
1052
1131
  }
1053
1132
  return {
1054
1133
  value,
1055
- changed: value !== input
1134
+ changed: changed || value !== input
1056
1135
  };
1057
1136
  }
1058
1137
  function guardValue(value, path, key, depth, options, seen) {
@@ -1131,6 +1210,7 @@ function privacyGuardProcessor(options = {}) {
1131
1210
  denyKeys: options.denyKeys ?? DEFAULT_DENY_KEYS,
1132
1211
  allowKeys: options.allowKeys ?? [],
1133
1212
  patterns: options.patterns ?? DEFAULT_PATTERNS,
1213
+ redactDefaultEmailPattern: options.patterns === void 0,
1134
1214
  replacement: options.replacement ?? "[REDACTED]",
1135
1215
  maxDepth: Math.max(0, Math.floor(options.maxDepth ?? 8)),
1136
1216
  maxStringLength: Math.max(1, Math.floor(options.maxStringLength ?? 8192)),