@agent-inspect/mcp-server 6.31.3 → 6.31.5

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/cli.cjs CHANGED
@@ -46,6 +46,80 @@ function extractCorrelationMetadata(record) {
46
46
  }
47
47
  return found ? out : void 0;
48
48
  }
49
+
50
+ // packages/core/src/safety/sensitive-key.ts
51
+ function keyHasExplicitSeparator(value) {
52
+ return /[_\-.]/.test(value);
53
+ }
54
+ function normalizeSensitiveKey(value) {
55
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
56
+ }
57
+ var NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
58
+ [
59
+ "tokens",
60
+ "max_tokens",
61
+ "min_tokens",
62
+ "ls_max_tokens",
63
+ "token_count",
64
+ "token_limit",
65
+ "token_budget",
66
+ "input_tokens",
67
+ "output_tokens",
68
+ "total_tokens",
69
+ "cached_tokens",
70
+ "prompt_tokens",
71
+ "completion_tokens"
72
+ ].map(normalizeSensitiveKey)
73
+ );
74
+ var DEFAULT_CREDENTIAL_SENSITIVE_KEYS = [
75
+ "authorization",
76
+ "cookie",
77
+ "token",
78
+ "access_token",
79
+ "accesstoken",
80
+ "auth_token",
81
+ "authtoken",
82
+ "refresh_token",
83
+ "refreshtoken",
84
+ "id_token",
85
+ "idtoken",
86
+ "bearer_token",
87
+ "bearertoken",
88
+ "api_token",
89
+ "apitoken",
90
+ "apikey",
91
+ "api_key",
92
+ "password",
93
+ "secret",
94
+ "email"
95
+ ];
96
+ function isTokenCredentialKey(normalized) {
97
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
98
+ if (normalized === "token") return true;
99
+ if (normalized.endsWith("tokens")) return false;
100
+ return normalized.endsWith("token");
101
+ }
102
+ function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSITIVE_KEYS) {
103
+ if (!key) return false;
104
+ const normalized = normalizeSensitiveKey(key);
105
+ if (!normalized) return false;
106
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
107
+ const allowPrefixCompound = keyHasExplicitSeparator(key);
108
+ for (const sensitive of sensitiveKeys) {
109
+ const s = normalizeSensitiveKey(sensitive);
110
+ if (!s) continue;
111
+ if (s === "token") {
112
+ if (isTokenCredentialKey(normalized)) return true;
113
+ continue;
114
+ }
115
+ if (normalized === s) return true;
116
+ if (normalized.endsWith(`_${s}`)) return true;
117
+ if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
118
+ }
119
+ return false;
120
+ }
121
+
122
+ // packages/core/src/logs/redactor.ts
49
123
  var DEFAULT_REDACT_KEYS = [
50
124
  "authorization",
51
125
  "cookie",
@@ -69,7 +143,7 @@ function compileRules(rules, extraKeys) {
69
143
  const out = /* @__PURE__ */ new Map();
70
144
  const set = (r) => {
71
145
  const k = toKey(r.key);
72
- out.set(k, { ...r, key: k });
146
+ out.set(k, { ...r, key: k, raw: r.raw ?? r.key });
73
147
  };
74
148
  for (const k of DEFAULT_REDACT_KEYS) {
75
149
  set({ key: k, strategy: "full" });
@@ -98,9 +172,13 @@ var Redactor = class {
98
172
  constructor(options) {
99
173
  this.#rules = compileRules(options?.rules, options?.extraKeys);
100
174
  }
175
+ /** Field names this redactor treats as sensitive, for value-level helpers. */
176
+ get sensitiveKeys() {
177
+ return this.#rules.map((rule) => rule.raw ?? rule.key);
178
+ }
101
179
  redactValue(key, value) {
102
180
  const k = toKey(key);
103
- const rule = this.#rules.find((r) => r.key === k);
181
+ const rule = this.#rules.find((r) => r.key === k) ?? this.#rules.find((r) => isCredentialSensitiveKey(key, [r.raw ?? r.key]));
104
182
  if (!rule) {
105
183
  return this.#redactNested(value);
106
184
  }
@@ -139,6 +217,30 @@ var Redactor = class {
139
217
  }
140
218
  };
141
219
 
220
+ // packages/core/src/safety/credential-value-patterns.ts
221
+ var KEY_VALUE_SECRET_PATTERN = /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=(?!\[(?:REDACTED(?::[^\]]*)?|HASH:[0-9a-f]{8})\](?![^\s"'\\&#?;/]))([^\s"'\\]{8,})/i;
222
+ var HIGH_CONFIDENCE_CREDENTIAL_PATTERNS = [
223
+ /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
224
+ /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
225
+ /\b[a-z0-9_.-]+=[^;\s]+(?:;\s*[a-z0-9_.-]+=[^;\s]+)+/i,
226
+ /\beyJ[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\b/,
227
+ /\b(?:sk-(?:proj-)?[a-zA-Z0-9_-]{16,}|sk-ant-[a-zA-Z0-9_-]{16,}|AIza[0-9A-Za-z_-]{20,})\b/,
228
+ /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
229
+ /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
230
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*-----END [A-Z ]*PRIVATE KEY-----/,
231
+ KEY_VALUE_SECRET_PATTERN
232
+ ];
233
+ function stringContainsHighConfidenceCredential(value) {
234
+ for (const pattern of HIGH_CONFIDENCE_CREDENTIAL_PATTERNS) {
235
+ pattern.lastIndex = 0;
236
+ if (pattern.test(value)) {
237
+ pattern.lastIndex = 0;
238
+ return true;
239
+ }
240
+ }
241
+ return false;
242
+ }
243
+
142
244
  // packages/core/src/redaction-profiles.ts
143
245
  var SHARE_PROFILE_EXTRA_KEYS = [
144
246
  "userEmail",
@@ -2309,78 +2411,6 @@ function formatProgrammaticDiagnostic(code, detail) {
2309
2411
  return `${code}: ${summary} Remediation: ${spec.remediation}`;
2310
2412
  }
2311
2413
 
2312
- // packages/core/src/safety/sensitive-key.ts
2313
- function keyHasExplicitSeparator(value) {
2314
- return /[_\-.]/.test(value);
2315
- }
2316
- function normalizeSensitiveKey(value) {
2317
- return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2318
- }
2319
- var NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
2320
- [
2321
- "tokens",
2322
- "max_tokens",
2323
- "min_tokens",
2324
- "ls_max_tokens",
2325
- "token_count",
2326
- "token_limit",
2327
- "token_budget",
2328
- "input_tokens",
2329
- "output_tokens",
2330
- "total_tokens",
2331
- "cached_tokens",
2332
- "prompt_tokens",
2333
- "completion_tokens"
2334
- ].map(normalizeSensitiveKey)
2335
- );
2336
- var DEFAULT_CREDENTIAL_SENSITIVE_KEYS = [
2337
- "authorization",
2338
- "cookie",
2339
- "token",
2340
- "access_token",
2341
- "accesstoken",
2342
- "auth_token",
2343
- "authtoken",
2344
- "refresh_token",
2345
- "refreshtoken",
2346
- "id_token",
2347
- "idtoken",
2348
- "bearer_token",
2349
- "bearertoken",
2350
- "api_token",
2351
- "apitoken",
2352
- "apikey",
2353
- "api_key",
2354
- "password",
2355
- "secret",
2356
- "email"
2357
- ];
2358
- function isTokenCredentialKey(normalized) {
2359
- if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
2360
- if (normalized === "token") return true;
2361
- if (normalized.endsWith("tokens")) return false;
2362
- return normalized.endsWith("token");
2363
- }
2364
- function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSITIVE_KEYS) {
2365
- if (!key) return false;
2366
- const normalized = normalizeSensitiveKey(key);
2367
- if (!normalized) return false;
2368
- if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
2369
- const allowPrefixCompound = keyHasExplicitSeparator(key);
2370
- for (const sensitive of sensitiveKeys) {
2371
- const s = normalizeSensitiveKey(sensitive);
2372
- if (!s) continue;
2373
- if (s === "token") {
2374
- if (isTokenCredentialKey(normalized)) return true;
2375
- continue;
2376
- }
2377
- if (normalized === s) return true;
2378
- if (normalized.endsWith(`_${s}`)) return true;
2379
- if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
2380
- }
2381
- return false;
2382
- }
2383
-
2384
2414
  // packages/core/src/checks/logical-events.ts
2385
2415
  function isRecord6(value) {
2386
2416
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3331,7 +3361,7 @@ var DEFAULT_SECRET_PATTERNS = [
3331
3361
  // Keep in sync with packages/redact/src/key-value-secret.ts (KEY_VALUE_SECRET_PATTERN_SOURCE).
3332
3362
  {
3333
3363
  id: "key-value-secret",
3334
- pattern: /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=([^\s"'\\]{8,})/i
3364
+ pattern: /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=(?!\[(?:REDACTED(?::[^\]]*)?|HASH:[0-9a-f]{8})\](?![^\s"'\\&#?;/]))([^\s"'\\]{8,})/i
3335
3365
  }
3336
3366
  ];
3337
3367
  function compareStrings(a, b) {
@@ -6672,6 +6702,124 @@ function openTrace(input, options = {}) {
6672
6702
  // packages/core/src/exporters/types.ts
6673
6703
  var EXPORT_PAYLOAD_VERSION = "0.1.2";
6674
6704
 
6705
+ // packages/core/src/safety/url-redaction.ts
6706
+ var HTTP_URL = /^https?:\/\/[^\s]+$/i;
6707
+ var CONNECTION_PROTOCOLS = /* @__PURE__ */ new Set([
6708
+ "postgres:",
6709
+ "postgresql:",
6710
+ "mysql:",
6711
+ "mariadb:",
6712
+ "mongodb:",
6713
+ "mongodb+srv:",
6714
+ "redis:",
6715
+ "rediss:",
6716
+ "amqp:",
6717
+ "amqps:"
6718
+ ]);
6719
+ var CONNECTION_URI = /^(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|rediss?|amqps?):\/\/[^\s]+$/i;
6720
+ var UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6721
+ var HEX_SEGMENT = /^[0-9a-f]{12,}$/i;
6722
+ var NUMERIC_SEGMENT = /^\d{5,}$/;
6723
+ var OPAQUE_SEGMENT = /^[A-Za-z0-9_-]{20,}$/;
6724
+ function looksLikeHttpUrl(value) {
6725
+ return HTTP_URL.test(value.trim());
6726
+ }
6727
+ function looksLikeConnectionUri(value) {
6728
+ return CONNECTION_URI.test(value.trim());
6729
+ }
6730
+ function looksLikeUrlForRedaction(value) {
6731
+ return looksLikeHttpUrl(value) || looksLikeConnectionUri(value);
6732
+ }
6733
+ function parseRedactableUrl(value) {
6734
+ try {
6735
+ const url = new URL(value.trim());
6736
+ if (url.protocol === "http:" || url.protocol === "https:") return url;
6737
+ if (CONNECTION_PROTOCOLS.has(url.protocol)) return url;
6738
+ return void 0;
6739
+ } catch {
6740
+ return void 0;
6741
+ }
6742
+ }
6743
+ function isHttpProtocol(protocol) {
6744
+ return protocol === "http:" || protocol === "https:";
6745
+ }
6746
+ function isIdentifierSegment(segment) {
6747
+ if (UUID_SEGMENT.test(segment)) return true;
6748
+ if (HEX_SEGMENT.test(segment)) return true;
6749
+ if (NUMERIC_SEGMENT.test(segment)) return true;
6750
+ return OPAQUE_SEGMENT.test(segment) && /\d/.test(segment) && /[A-Za-z]/.test(segment);
6751
+ }
6752
+ function decodeParam(value) {
6753
+ try {
6754
+ return decodeURIComponent(value.replace(/\+/g, " "));
6755
+ } catch {
6756
+ return value;
6757
+ }
6758
+ }
6759
+ function redactParams(raw, options, replacement) {
6760
+ let redacted = false;
6761
+ const parts = raw.split("&").map((part) => {
6762
+ if (part.length === 0) return part;
6763
+ const separator = part.indexOf("=");
6764
+ if (separator < 0) return part;
6765
+ const rawKey = part.slice(0, separator);
6766
+ const rawValue = part.slice(separator + 1);
6767
+ const key = decodeParam(rawKey);
6768
+ const value = decodeParam(rawValue);
6769
+ if (isCredentialSensitiveKey(key, options.sensitiveKeys) || options.isSensitiveValue(value)) {
6770
+ redacted = true;
6771
+ return `${rawKey}=${replacement}`;
6772
+ }
6773
+ return part;
6774
+ });
6775
+ return { value: parts.join("&"), redacted };
6776
+ }
6777
+ function redactUrlString(value, options) {
6778
+ const unchanged = {
6779
+ value,
6780
+ credentialRedacted: false,
6781
+ identifiersMasked: false
6782
+ };
6783
+ if (!looksLikeUrlForRedaction(value)) return unchanged;
6784
+ const url = parseRedactableUrl(value);
6785
+ if (!url) return unchanged;
6786
+ const replacement = options.replacement ?? "[REDACTED]";
6787
+ const idPlaceholder = options.idPlaceholder ?? "[id]";
6788
+ const http = isHttpProtocol(url.protocol);
6789
+ let credentialRedacted = url.username.length > 0 || url.password.length > 0;
6790
+ let identifiersMasked = false;
6791
+ let pathname = url.pathname;
6792
+ if (http && options.maskPathIds === true) {
6793
+ pathname = url.pathname.split("/").map((segment) => {
6794
+ if (segment.length === 0 || !isIdentifierSegment(decodeParam(segment))) {
6795
+ return segment;
6796
+ }
6797
+ identifiersMasked = true;
6798
+ return idPlaceholder;
6799
+ }).join("/");
6800
+ }
6801
+ let search = "";
6802
+ if (url.search.length > 1) {
6803
+ const params = redactParams(url.search.slice(1), options, replacement);
6804
+ if (params.redacted) credentialRedacted = true;
6805
+ search = `?${params.value}`;
6806
+ } else if (url.search.length === 1) {
6807
+ search = "?";
6808
+ }
6809
+ let hash = url.hash;
6810
+ if (url.hash.includes("=")) {
6811
+ const params = redactParams(url.hash.slice(1), options, replacement);
6812
+ if (params.redacted) credentialRedacted = true;
6813
+ hash = `#${params.value}`;
6814
+ }
6815
+ if (!credentialRedacted && !identifiersMasked) return unchanged;
6816
+ return {
6817
+ value: `${url.protocol}//${url.host}${pathname}${search}${hash}`,
6818
+ credentialRedacted,
6819
+ identifiersMasked
6820
+ };
6821
+ }
6822
+
6675
6823
  // packages/core/src/exporters/redact-export.ts
6676
6824
  function isRecord11(value) {
6677
6825
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -6689,21 +6837,34 @@ function deepClone(value) {
6689
6837
  }
6690
6838
  return out;
6691
6839
  }
6692
- function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
6840
+ function boundAttributeValues(record, sensitiveKeys, maxMetadataValueLength, maxPreviewLength, seen, depth) {
6693
6841
  if (depth > 32) {
6694
6842
  return { truncated: true, reason: "maxDepth" };
6695
6843
  }
6696
6844
  const out = {};
6697
6845
  for (const [key, value] of Object.entries(record)) {
6698
- out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
6846
+ out[key] = boundValue(
6847
+ value,
6848
+ key,
6849
+ sensitiveKeys,
6850
+ maxMetadataValueLength,
6851
+ maxPreviewLength,
6852
+ seen,
6853
+ depth
6854
+ );
6699
6855
  }
6700
6856
  return out;
6701
6857
  }
6702
- function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
6858
+ function boundValue(value, key, sensitiveKeys, maxMetadataValueLength, maxPreviewLength, seen, depth) {
6703
6859
  if (value === null || typeof value !== "object") {
6704
6860
  if (typeof value === "string") {
6861
+ const rewritten = redactUrlString(value, {
6862
+ sensitiveKeys,
6863
+ isSensitiveValue: stringContainsHighConfidenceCredential,
6864
+ maskPathIds: true
6865
+ }).value;
6705
6866
  return truncateStringForProfile(
6706
- value,
6867
+ stringContainsHighConfidenceCredential(rewritten) ? "[REDACTED]" : rewritten,
6707
6868
  key,
6708
6869
  maxMetadataValueLength,
6709
6870
  maxPreviewLength
@@ -6718,6 +6879,7 @@ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen,
6718
6879
  (item, index) => boundValue(
6719
6880
  item,
6720
6881
  String(index),
6882
+ sensitiveKeys,
6721
6883
  maxMetadataValueLength,
6722
6884
  maxPreviewLength,
6723
6885
  seen,
@@ -6727,6 +6889,7 @@ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen,
6727
6889
  }
6728
6890
  return boundAttributeValues(
6729
6891
  value,
6892
+ sensitiveKeys,
6730
6893
  maxMetadataValueLength,
6731
6894
  maxPreviewLength,
6732
6895
  seen,
@@ -6741,6 +6904,7 @@ function redactEventAttributes(attrs2, redactor, maxMetadataValueLength, maxPrev
6741
6904
  const seen = /* @__PURE__ */ new WeakSet();
6742
6905
  const bounded = boundAttributeValues(
6743
6906
  redacted,
6907
+ [...DEFAULT_REDACT_KEYS, ...redactor.sensitiveKeys],
6744
6908
  maxMetadataValueLength,
6745
6909
  maxPreviewLength,
6746
6910
  seen,
@@ -7411,14 +7575,132 @@ function isCredentialSensitiveKey2(key, sensitiveKeys) {
7411
7575
  }
7412
7576
 
7413
7577
  // packages/redact/src/key-value-secret.ts
7414
- var KEY_VALUE_SECRET_PATTERN_SOURCE = String.raw`\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=([^\s"'\\]{8,})`;
7415
- var KEY_VALUE_SECRET_PATTERN = new RegExp(
7578
+ var KEY_VALUE_SECRET_PATTERN_SOURCE = String.raw`\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=(?!\[(?:REDACTED(?::[^\]]*)?|HASH:[0-9a-f]{8})\](?![^\s"'\\&#?;/]))([^\s"'\\]{8,})`;
7579
+ var KEY_VALUE_SECRET_PATTERN2 = new RegExp(
7416
7580
  KEY_VALUE_SECRET_PATTERN_SOURCE,
7417
7581
  "i"
7418
7582
  );
7419
7583
  function valueContainsKeyValueSecret(value) {
7420
- KEY_VALUE_SECRET_PATTERN.lastIndex = 0;
7421
- return KEY_VALUE_SECRET_PATTERN.test(value);
7584
+ KEY_VALUE_SECRET_PATTERN2.lastIndex = 0;
7585
+ return KEY_VALUE_SECRET_PATTERN2.test(value);
7586
+ }
7587
+
7588
+ // packages/redact/src/url-redaction.ts
7589
+ var HTTP_URL2 = /^https?:\/\/[^\s]+$/i;
7590
+ var CONNECTION_PROTOCOLS2 = /* @__PURE__ */ new Set([
7591
+ "postgres:",
7592
+ "postgresql:",
7593
+ "mysql:",
7594
+ "mariadb:",
7595
+ "mongodb:",
7596
+ "mongodb+srv:",
7597
+ "redis:",
7598
+ "rediss:",
7599
+ "amqp:",
7600
+ "amqps:"
7601
+ ]);
7602
+ var CONNECTION_URI2 = /^(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|rediss?|amqps?):\/\/[^\s]+$/i;
7603
+ var UUID_SEGMENT2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7604
+ var HEX_SEGMENT2 = /^[0-9a-f]{12,}$/i;
7605
+ var NUMERIC_SEGMENT2 = /^\d{5,}$/;
7606
+ var OPAQUE_SEGMENT2 = /^[A-Za-z0-9_-]{20,}$/;
7607
+ function looksLikeHttpUrl2(value) {
7608
+ return HTTP_URL2.test(value.trim());
7609
+ }
7610
+ function looksLikeConnectionUri2(value) {
7611
+ return CONNECTION_URI2.test(value.trim());
7612
+ }
7613
+ function looksLikeUrlForRedaction2(value) {
7614
+ return looksLikeHttpUrl2(value) || looksLikeConnectionUri2(value);
7615
+ }
7616
+ function parseRedactableUrl2(value) {
7617
+ try {
7618
+ const url = new URL(value.trim());
7619
+ if (url.protocol === "http:" || url.protocol === "https:") return url;
7620
+ if (CONNECTION_PROTOCOLS2.has(url.protocol)) return url;
7621
+ return void 0;
7622
+ } catch {
7623
+ return void 0;
7624
+ }
7625
+ }
7626
+ function isHttpProtocol2(protocol) {
7627
+ return protocol === "http:" || protocol === "https:";
7628
+ }
7629
+ function isIdentifierSegment2(segment) {
7630
+ if (UUID_SEGMENT2.test(segment)) return true;
7631
+ if (HEX_SEGMENT2.test(segment)) return true;
7632
+ if (NUMERIC_SEGMENT2.test(segment)) return true;
7633
+ return OPAQUE_SEGMENT2.test(segment) && /\d/.test(segment) && /[A-Za-z]/.test(segment);
7634
+ }
7635
+ function decodeParam2(value) {
7636
+ try {
7637
+ return decodeURIComponent(value.replace(/\+/g, " "));
7638
+ } catch {
7639
+ return value;
7640
+ }
7641
+ }
7642
+ function redactParams2(raw, options, replacement) {
7643
+ let redacted = false;
7644
+ const parts = raw.split("&").map((part) => {
7645
+ if (part.length === 0) return part;
7646
+ const separator = part.indexOf("=");
7647
+ if (separator < 0) return part;
7648
+ const rawKey = part.slice(0, separator);
7649
+ const rawValue = part.slice(separator + 1);
7650
+ const key = decodeParam2(rawKey);
7651
+ const value = decodeParam2(rawValue);
7652
+ if (isCredentialSensitiveKey2(key, options.sensitiveKeys) || options.isSensitiveValue(value)) {
7653
+ redacted = true;
7654
+ return `${rawKey}=${replacement}`;
7655
+ }
7656
+ return part;
7657
+ });
7658
+ return { value: parts.join("&"), redacted };
7659
+ }
7660
+ function redactUrlString2(value, options) {
7661
+ const unchanged = {
7662
+ value,
7663
+ credentialRedacted: false,
7664
+ identifiersMasked: false
7665
+ };
7666
+ if (!looksLikeUrlForRedaction2(value)) return unchanged;
7667
+ const url = parseRedactableUrl2(value);
7668
+ if (!url) return unchanged;
7669
+ const replacement = options.replacement ?? "[REDACTED]";
7670
+ const idPlaceholder = options.idPlaceholder ?? "[id]";
7671
+ const http = isHttpProtocol2(url.protocol);
7672
+ let credentialRedacted = url.username.length > 0 || url.password.length > 0;
7673
+ let identifiersMasked = false;
7674
+ let pathname = url.pathname;
7675
+ if (http && options.maskPathIds === true) {
7676
+ pathname = url.pathname.split("/").map((segment) => {
7677
+ if (segment.length === 0 || !isIdentifierSegment2(decodeParam2(segment))) {
7678
+ return segment;
7679
+ }
7680
+ identifiersMasked = true;
7681
+ return idPlaceholder;
7682
+ }).join("/");
7683
+ }
7684
+ let search = "";
7685
+ if (url.search.length > 1) {
7686
+ const params = redactParams2(url.search.slice(1), options, replacement);
7687
+ if (params.redacted) credentialRedacted = true;
7688
+ search = `?${params.value}`;
7689
+ } else if (url.search.length === 1) {
7690
+ search = "?";
7691
+ }
7692
+ let hash = url.hash;
7693
+ if (url.hash.includes("=")) {
7694
+ const params = redactParams2(url.hash.slice(1), options, replacement);
7695
+ if (params.redacted) credentialRedacted = true;
7696
+ hash = `#${params.value}`;
7697
+ }
7698
+ if (!credentialRedacted && !identifiersMasked) return unchanged;
7699
+ return {
7700
+ value: `${url.protocol}//${url.host}${pathname}${search}${hash}`,
7701
+ credentialRedacted,
7702
+ identifiersMasked
7703
+ };
7422
7704
  }
7423
7705
 
7424
7706
  // packages/redact/src/index.ts
@@ -7487,7 +7769,9 @@ function findCompiledKeyRule(key, rules) {
7487
7769
  const exact = toKey2(key);
7488
7770
  const direct = rules.find((candidate) => candidate.key === exact);
7489
7771
  if (direct) return direct;
7490
- return rules.find((candidate) => isCredentialSensitiveKey2(key, [candidate.key]));
7772
+ return rules.find(
7773
+ (candidate) => isCredentialSensitiveKey2(key, [candidate.raw ?? candidate.key])
7774
+ );
7491
7775
  }
7492
7776
  function stableHash2(value) {
7493
7777
  const hash = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
@@ -7686,7 +7970,7 @@ function compileRules2(rules, extraKeys) {
7686
7970
  const out = /* @__PURE__ */ new Map();
7687
7971
  const set = (rule) => {
7688
7972
  const key = toKey2(rule.key);
7689
- out.set(key, { ...rule, key });
7973
+ out.set(key, { ...rule, key, raw: rule.raw ?? rule.key });
7690
7974
  };
7691
7975
  for (const key of DEFAULT_REDACT_KEYS2) {
7692
7976
  set({ key, strategy: "full" });
@@ -7765,6 +8049,7 @@ function createRedactionProfile(profile = "local") {
7765
8049
  }
7766
8050
  var Redactor2 = class {
7767
8051
  #rules;
8052
+ #sensitiveKeys;
7768
8053
  #detectors;
7769
8054
  #profile;
7770
8055
  #replacement;
@@ -7777,6 +8062,7 @@ var Redactor2 = class {
7777
8062
  ...resolved.extraKeys,
7778
8063
  ...options?.extraKeys ?? []
7779
8064
  ]);
8065
+ this.#sensitiveKeys = this.#rules.map((rule) => rule.raw ?? rule.key);
7780
8066
  this.#detectors = [
7781
8067
  ...builtInDetectorsForProfile(this.#profile),
7782
8068
  ...options?.detectors ?? []
@@ -7810,6 +8096,14 @@ var Redactor2 = class {
7810
8096
  #recordFinding(state, finding) {
7811
8097
  if (this.#collectFindings) state.findings.push(finding);
7812
8098
  }
8099
+ #detect(value, key, path17) {
8100
+ for (const detector of this.#detectors) {
8101
+ for (const detection of detector.detect({ path: path17, key, value })) {
8102
+ if ((detection.action ?? "replace") !== "keep") return { detector, detection };
8103
+ }
8104
+ }
8105
+ return void 0;
8106
+ }
7813
8107
  #redactValue(value, key, path17, depth, state) {
7814
8108
  if (depth > this.#maxDepth) {
7815
8109
  this.#recordFinding(
@@ -7828,6 +8122,43 @@ var Redactor2 = class {
7828
8122
  return applyRule(rule, value, this.#replacement);
7829
8123
  }
7830
8124
  }
8125
+ if (typeof value === "string" && looksLikeUrlForRedaction2(value)) {
8126
+ const url = redactUrlString2(value, {
8127
+ sensitiveKeys: this.#sensitiveKeys,
8128
+ isSensitiveValue: (candidate) => this.#detect(candidate, key, path17) !== void 0,
8129
+ replacement: this.#replacement,
8130
+ maskPathIds: this.#profile !== "local"
8131
+ });
8132
+ if (url.value !== value) {
8133
+ this.#recordFinding(
8134
+ state,
8135
+ makeFinding(
8136
+ path17,
8137
+ url.credentialRedacted ? "value.urlCredential" : "value.urlIdentifier",
8138
+ "replace",
8139
+ "value",
8140
+ url.credentialRedacted ? "error" : "warning"
8141
+ )
8142
+ );
8143
+ const residual = this.#detect(
8144
+ url.value.split(this.#replacement).join(""),
8145
+ key,
8146
+ path17
8147
+ );
8148
+ if (residual === void 0) return url.value;
8149
+ this.#recordFinding(
8150
+ state,
8151
+ makeFinding(
8152
+ path17,
8153
+ residual.detector.id,
8154
+ "replace",
8155
+ residual.detection.matchKind ?? residual.detector.matchKind ?? "custom",
8156
+ residual.detection.severity ?? residual.detector.severity ?? "warning"
8157
+ )
8158
+ );
8159
+ return this.#replacement;
8160
+ }
8161
+ }
7831
8162
  for (const detector of this.#detectors) {
7832
8163
  const detections = detector.detect({ path: path17, key, value });
7833
8164
  for (const detection of detections) {