@agent-inspect/mcp-server 6.31.3 → 6.31.4

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,99 @@ 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 UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6708
+ var HEX_SEGMENT = /^[0-9a-f]{12,}$/i;
6709
+ var NUMERIC_SEGMENT = /^\d{5,}$/;
6710
+ var OPAQUE_SEGMENT = /^[A-Za-z0-9_-]{20,}$/;
6711
+ function looksLikeHttpUrl(value) {
6712
+ return HTTP_URL.test(value.trim());
6713
+ }
6714
+ function parseHttpUrl(value) {
6715
+ try {
6716
+ const url = new URL(value.trim());
6717
+ return url.protocol === "http:" || url.protocol === "https:" ? url : void 0;
6718
+ } catch {
6719
+ return void 0;
6720
+ }
6721
+ }
6722
+ function isIdentifierSegment(segment) {
6723
+ if (UUID_SEGMENT.test(segment)) return true;
6724
+ if (HEX_SEGMENT.test(segment)) return true;
6725
+ if (NUMERIC_SEGMENT.test(segment)) return true;
6726
+ return OPAQUE_SEGMENT.test(segment) && /\d/.test(segment) && /[A-Za-z]/.test(segment);
6727
+ }
6728
+ function decodeParam(value) {
6729
+ try {
6730
+ return decodeURIComponent(value.replace(/\+/g, " "));
6731
+ } catch {
6732
+ return value;
6733
+ }
6734
+ }
6735
+ function redactParams(raw, options, replacement) {
6736
+ let redacted = false;
6737
+ const parts = raw.split("&").map((part) => {
6738
+ if (part.length === 0) return part;
6739
+ const separator = part.indexOf("=");
6740
+ if (separator < 0) return part;
6741
+ const rawKey = part.slice(0, separator);
6742
+ const rawValue = part.slice(separator + 1);
6743
+ const key = decodeParam(rawKey);
6744
+ const value = decodeParam(rawValue);
6745
+ if (isCredentialSensitiveKey(key, options.sensitiveKeys) || options.isSensitiveValue(value)) {
6746
+ redacted = true;
6747
+ return `${rawKey}=${replacement}`;
6748
+ }
6749
+ return part;
6750
+ });
6751
+ return { value: parts.join("&"), redacted };
6752
+ }
6753
+ function redactUrlString(value, options) {
6754
+ const unchanged = {
6755
+ value,
6756
+ credentialRedacted: false,
6757
+ identifiersMasked: false
6758
+ };
6759
+ if (!looksLikeHttpUrl(value)) return unchanged;
6760
+ const url = parseHttpUrl(value);
6761
+ if (!url) return unchanged;
6762
+ const replacement = options.replacement ?? "[REDACTED]";
6763
+ const idPlaceholder = options.idPlaceholder ?? "[id]";
6764
+ let credentialRedacted = url.username.length > 0 || url.password.length > 0;
6765
+ let identifiersMasked = false;
6766
+ let pathname = url.pathname;
6767
+ if (options.maskPathIds === true) {
6768
+ pathname = url.pathname.split("/").map((segment) => {
6769
+ if (segment.length === 0 || !isIdentifierSegment(decodeParam(segment))) {
6770
+ return segment;
6771
+ }
6772
+ identifiersMasked = true;
6773
+ return idPlaceholder;
6774
+ }).join("/");
6775
+ }
6776
+ let search = "";
6777
+ if (url.search.length > 1) {
6778
+ const params = redactParams(url.search.slice(1), options, replacement);
6779
+ if (params.redacted) credentialRedacted = true;
6780
+ search = `?${params.value}`;
6781
+ } else if (url.search.length === 1) {
6782
+ search = "?";
6783
+ }
6784
+ let hash = url.hash;
6785
+ if (url.hash.includes("=")) {
6786
+ const params = redactParams(url.hash.slice(1), options, replacement);
6787
+ if (params.redacted) credentialRedacted = true;
6788
+ hash = `#${params.value}`;
6789
+ }
6790
+ if (!credentialRedacted && !identifiersMasked) return unchanged;
6791
+ return {
6792
+ value: `${url.protocol}//${url.host}${pathname}${search}${hash}`,
6793
+ credentialRedacted,
6794
+ identifiersMasked
6795
+ };
6796
+ }
6797
+
6675
6798
  // packages/core/src/exporters/redact-export.ts
6676
6799
  function isRecord11(value) {
6677
6800
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -6689,21 +6812,34 @@ function deepClone(value) {
6689
6812
  }
6690
6813
  return out;
6691
6814
  }
6692
- function boundAttributeValues(record, maxMetadataValueLength, maxPreviewLength, seen, depth) {
6815
+ function boundAttributeValues(record, sensitiveKeys, maxMetadataValueLength, maxPreviewLength, seen, depth) {
6693
6816
  if (depth > 32) {
6694
6817
  return { truncated: true, reason: "maxDepth" };
6695
6818
  }
6696
6819
  const out = {};
6697
6820
  for (const [key, value] of Object.entries(record)) {
6698
- out[key] = boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth);
6821
+ out[key] = boundValue(
6822
+ value,
6823
+ key,
6824
+ sensitiveKeys,
6825
+ maxMetadataValueLength,
6826
+ maxPreviewLength,
6827
+ seen,
6828
+ depth
6829
+ );
6699
6830
  }
6700
6831
  return out;
6701
6832
  }
6702
- function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen, depth) {
6833
+ function boundValue(value, key, sensitiveKeys, maxMetadataValueLength, maxPreviewLength, seen, depth) {
6703
6834
  if (value === null || typeof value !== "object") {
6704
6835
  if (typeof value === "string") {
6836
+ const rewritten = redactUrlString(value, {
6837
+ sensitiveKeys,
6838
+ isSensitiveValue: stringContainsHighConfidenceCredential,
6839
+ maskPathIds: true
6840
+ }).value;
6705
6841
  return truncateStringForProfile(
6706
- value,
6842
+ stringContainsHighConfidenceCredential(rewritten) ? "[REDACTED]" : rewritten,
6707
6843
  key,
6708
6844
  maxMetadataValueLength,
6709
6845
  maxPreviewLength
@@ -6718,6 +6854,7 @@ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen,
6718
6854
  (item, index) => boundValue(
6719
6855
  item,
6720
6856
  String(index),
6857
+ sensitiveKeys,
6721
6858
  maxMetadataValueLength,
6722
6859
  maxPreviewLength,
6723
6860
  seen,
@@ -6727,6 +6864,7 @@ function boundValue(value, key, maxMetadataValueLength, maxPreviewLength, seen,
6727
6864
  }
6728
6865
  return boundAttributeValues(
6729
6866
  value,
6867
+ sensitiveKeys,
6730
6868
  maxMetadataValueLength,
6731
6869
  maxPreviewLength,
6732
6870
  seen,
@@ -6741,6 +6879,7 @@ function redactEventAttributes(attrs2, redactor, maxMetadataValueLength, maxPrev
6741
6879
  const seen = /* @__PURE__ */ new WeakSet();
6742
6880
  const bounded = boundAttributeValues(
6743
6881
  redacted,
6882
+ [...DEFAULT_REDACT_KEYS, ...redactor.sensitiveKeys],
6744
6883
  maxMetadataValueLength,
6745
6884
  maxPreviewLength,
6746
6885
  seen,
@@ -7411,14 +7550,107 @@ function isCredentialSensitiveKey2(key, sensitiveKeys) {
7411
7550
  }
7412
7551
 
7413
7552
  // 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(
7553
+ 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,})`;
7554
+ var KEY_VALUE_SECRET_PATTERN2 = new RegExp(
7416
7555
  KEY_VALUE_SECRET_PATTERN_SOURCE,
7417
7556
  "i"
7418
7557
  );
7419
7558
  function valueContainsKeyValueSecret(value) {
7420
- KEY_VALUE_SECRET_PATTERN.lastIndex = 0;
7421
- return KEY_VALUE_SECRET_PATTERN.test(value);
7559
+ KEY_VALUE_SECRET_PATTERN2.lastIndex = 0;
7560
+ return KEY_VALUE_SECRET_PATTERN2.test(value);
7561
+ }
7562
+
7563
+ // packages/redact/src/url-redaction.ts
7564
+ var HTTP_URL2 = /^https?:\/\/[^\s]+$/i;
7565
+ var UUID_SEGMENT2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7566
+ var HEX_SEGMENT2 = /^[0-9a-f]{12,}$/i;
7567
+ var NUMERIC_SEGMENT2 = /^\d{5,}$/;
7568
+ var OPAQUE_SEGMENT2 = /^[A-Za-z0-9_-]{20,}$/;
7569
+ function looksLikeHttpUrl2(value) {
7570
+ return HTTP_URL2.test(value.trim());
7571
+ }
7572
+ function parseHttpUrl2(value) {
7573
+ try {
7574
+ const url = new URL(value.trim());
7575
+ return url.protocol === "http:" || url.protocol === "https:" ? url : void 0;
7576
+ } catch {
7577
+ return void 0;
7578
+ }
7579
+ }
7580
+ function isIdentifierSegment2(segment) {
7581
+ if (UUID_SEGMENT2.test(segment)) return true;
7582
+ if (HEX_SEGMENT2.test(segment)) return true;
7583
+ if (NUMERIC_SEGMENT2.test(segment)) return true;
7584
+ return OPAQUE_SEGMENT2.test(segment) && /\d/.test(segment) && /[A-Za-z]/.test(segment);
7585
+ }
7586
+ function decodeParam2(value) {
7587
+ try {
7588
+ return decodeURIComponent(value.replace(/\+/g, " "));
7589
+ } catch {
7590
+ return value;
7591
+ }
7592
+ }
7593
+ function redactParams2(raw, options, replacement) {
7594
+ let redacted = false;
7595
+ const parts = raw.split("&").map((part) => {
7596
+ if (part.length === 0) return part;
7597
+ const separator = part.indexOf("=");
7598
+ if (separator < 0) return part;
7599
+ const rawKey = part.slice(0, separator);
7600
+ const rawValue = part.slice(separator + 1);
7601
+ const key = decodeParam2(rawKey);
7602
+ const value = decodeParam2(rawValue);
7603
+ if (isCredentialSensitiveKey2(key, options.sensitiveKeys) || options.isSensitiveValue(value)) {
7604
+ redacted = true;
7605
+ return `${rawKey}=${replacement}`;
7606
+ }
7607
+ return part;
7608
+ });
7609
+ return { value: parts.join("&"), redacted };
7610
+ }
7611
+ function redactUrlString2(value, options) {
7612
+ const unchanged = {
7613
+ value,
7614
+ credentialRedacted: false,
7615
+ identifiersMasked: false
7616
+ };
7617
+ if (!looksLikeHttpUrl2(value)) return unchanged;
7618
+ const url = parseHttpUrl2(value);
7619
+ if (!url) return unchanged;
7620
+ const replacement = options.replacement ?? "[REDACTED]";
7621
+ const idPlaceholder = options.idPlaceholder ?? "[id]";
7622
+ let credentialRedacted = url.username.length > 0 || url.password.length > 0;
7623
+ let identifiersMasked = false;
7624
+ let pathname = url.pathname;
7625
+ if (options.maskPathIds === true) {
7626
+ pathname = url.pathname.split("/").map((segment) => {
7627
+ if (segment.length === 0 || !isIdentifierSegment2(decodeParam2(segment))) {
7628
+ return segment;
7629
+ }
7630
+ identifiersMasked = true;
7631
+ return idPlaceholder;
7632
+ }).join("/");
7633
+ }
7634
+ let search = "";
7635
+ if (url.search.length > 1) {
7636
+ const params = redactParams2(url.search.slice(1), options, replacement);
7637
+ if (params.redacted) credentialRedacted = true;
7638
+ search = `?${params.value}`;
7639
+ } else if (url.search.length === 1) {
7640
+ search = "?";
7641
+ }
7642
+ let hash = url.hash;
7643
+ if (url.hash.includes("=")) {
7644
+ const params = redactParams2(url.hash.slice(1), options, replacement);
7645
+ if (params.redacted) credentialRedacted = true;
7646
+ hash = `#${params.value}`;
7647
+ }
7648
+ if (!credentialRedacted && !identifiersMasked) return unchanged;
7649
+ return {
7650
+ value: `${url.protocol}//${url.host}${pathname}${search}${hash}`,
7651
+ credentialRedacted,
7652
+ identifiersMasked
7653
+ };
7422
7654
  }
7423
7655
 
7424
7656
  // packages/redact/src/index.ts
@@ -7487,7 +7719,9 @@ function findCompiledKeyRule(key, rules) {
7487
7719
  const exact = toKey2(key);
7488
7720
  const direct = rules.find((candidate) => candidate.key === exact);
7489
7721
  if (direct) return direct;
7490
- return rules.find((candidate) => isCredentialSensitiveKey2(key, [candidate.key]));
7722
+ return rules.find(
7723
+ (candidate) => isCredentialSensitiveKey2(key, [candidate.raw ?? candidate.key])
7724
+ );
7491
7725
  }
7492
7726
  function stableHash2(value) {
7493
7727
  const hash = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
@@ -7686,7 +7920,7 @@ function compileRules2(rules, extraKeys) {
7686
7920
  const out = /* @__PURE__ */ new Map();
7687
7921
  const set = (rule) => {
7688
7922
  const key = toKey2(rule.key);
7689
- out.set(key, { ...rule, key });
7923
+ out.set(key, { ...rule, key, raw: rule.raw ?? rule.key });
7690
7924
  };
7691
7925
  for (const key of DEFAULT_REDACT_KEYS2) {
7692
7926
  set({ key, strategy: "full" });
@@ -7765,6 +7999,7 @@ function createRedactionProfile(profile = "local") {
7765
7999
  }
7766
8000
  var Redactor2 = class {
7767
8001
  #rules;
8002
+ #sensitiveKeys;
7768
8003
  #detectors;
7769
8004
  #profile;
7770
8005
  #replacement;
@@ -7777,6 +8012,7 @@ var Redactor2 = class {
7777
8012
  ...resolved.extraKeys,
7778
8013
  ...options?.extraKeys ?? []
7779
8014
  ]);
8015
+ this.#sensitiveKeys = this.#rules.map((rule) => rule.raw ?? rule.key);
7780
8016
  this.#detectors = [
7781
8017
  ...builtInDetectorsForProfile(this.#profile),
7782
8018
  ...options?.detectors ?? []
@@ -7810,6 +8046,14 @@ var Redactor2 = class {
7810
8046
  #recordFinding(state, finding) {
7811
8047
  if (this.#collectFindings) state.findings.push(finding);
7812
8048
  }
8049
+ #detect(value, key, path17) {
8050
+ for (const detector of this.#detectors) {
8051
+ for (const detection of detector.detect({ path: path17, key, value })) {
8052
+ if ((detection.action ?? "replace") !== "keep") return { detector, detection };
8053
+ }
8054
+ }
8055
+ return void 0;
8056
+ }
7813
8057
  #redactValue(value, key, path17, depth, state) {
7814
8058
  if (depth > this.#maxDepth) {
7815
8059
  this.#recordFinding(
@@ -7828,6 +8072,43 @@ var Redactor2 = class {
7828
8072
  return applyRule(rule, value, this.#replacement);
7829
8073
  }
7830
8074
  }
8075
+ if (typeof value === "string" && looksLikeHttpUrl2(value)) {
8076
+ const url = redactUrlString2(value, {
8077
+ sensitiveKeys: this.#sensitiveKeys,
8078
+ isSensitiveValue: (candidate) => this.#detect(candidate, key, path17) !== void 0,
8079
+ replacement: this.#replacement,
8080
+ maskPathIds: this.#profile !== "local"
8081
+ });
8082
+ if (url.value !== value) {
8083
+ this.#recordFinding(
8084
+ state,
8085
+ makeFinding(
8086
+ path17,
8087
+ url.credentialRedacted ? "value.urlCredential" : "value.urlIdentifier",
8088
+ "replace",
8089
+ "value",
8090
+ url.credentialRedacted ? "error" : "warning"
8091
+ )
8092
+ );
8093
+ const residual = this.#detect(
8094
+ url.value.split(this.#replacement).join(""),
8095
+ key,
8096
+ path17
8097
+ );
8098
+ if (residual === void 0) return url.value;
8099
+ this.#recordFinding(
8100
+ state,
8101
+ makeFinding(
8102
+ path17,
8103
+ residual.detector.id,
8104
+ "replace",
8105
+ residual.detection.matchKind ?? residual.detector.matchKind ?? "custom",
8106
+ residual.detection.severity ?? residual.detector.severity ?? "warning"
8107
+ )
8108
+ );
8109
+ return this.#replacement;
8110
+ }
8111
+ }
7831
8112
  for (const detector of this.#detectors) {
7832
8113
  const detections = detector.detect({ path: path17, key, value });
7833
8114
  for (const detection of detections) {