@agent-inspect/mcp-server 6.31.2 → 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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { runReadOnlyMcpServer } from './chunk-YWFDV7U3.mjs';
1
+ import { runReadOnlyMcpServer } from './chunk-OGX7RS23.mjs';
2
2
  import { readFileSync } from 'fs';
3
3
  import path from 'path';
4
4
  import { fileURLToPath } from 'url';
package/dist/index.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,
@@ -7194,6 +7333,11 @@ function exportOpenInference(tree, options) {
7194
7333
  function hexFrom2(seed, byteLen) {
7195
7334
  return crypto__default.default.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, byteLen * 2);
7196
7335
  }
7336
+ function unixNano2(ms) {
7337
+ const whole = Math.trunc(ms);
7338
+ const fractionNs = Math.round((ms - whole) * 1e6);
7339
+ return BigInt(whole) * 1000000n + BigInt(fractionNs);
7340
+ }
7197
7341
  function stringAttr2(key, value) {
7198
7342
  return { key, value: { stringValue: value } };
7199
7343
  }
@@ -7227,10 +7371,10 @@ function exportOtlpJson(tree, options) {
7227
7371
  const ev = n.event;
7228
7372
  const spanId = hexFrom2(`${tree.runId}:${ev.eventId}`, 8);
7229
7373
  const parentSpanId = ev.parentId ? hexFrom2(`${tree.runId}:${ev.parentId}`, 8) : void 0;
7230
- const startNs = String(Math.round(ev.timestamp * 1e6));
7374
+ const startNs = String(unixNano2(ev.timestamp));
7231
7375
  let endNs;
7232
7376
  if (ev.durationMs !== void 0 && Number.isFinite(ev.durationMs)) {
7233
- endNs = String(Math.round(ev.timestamp * 1e6 + ev.durationMs * 1e6));
7377
+ endNs = String(unixNano2(ev.timestamp) + unixNano2(ev.durationMs));
7234
7378
  }
7235
7379
  const attrs2 = [
7236
7380
  stringAttr2("agent_inspect.kind", ev.kind),
@@ -7406,14 +7550,107 @@ function isCredentialSensitiveKey2(key, sensitiveKeys) {
7406
7550
  }
7407
7551
 
7408
7552
  // packages/redact/src/key-value-secret.ts
7409
- var KEY_VALUE_SECRET_PATTERN_SOURCE = String.raw`\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=([^\s"'\\]{8,})`;
7410
- 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(
7411
7555
  KEY_VALUE_SECRET_PATTERN_SOURCE,
7412
7556
  "i"
7413
7557
  );
7414
7558
  function valueContainsKeyValueSecret(value) {
7415
- KEY_VALUE_SECRET_PATTERN.lastIndex = 0;
7416
- 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
+ };
7417
7654
  }
7418
7655
 
7419
7656
  // packages/redact/src/index.ts
@@ -7482,7 +7719,9 @@ function findCompiledKeyRule(key, rules) {
7482
7719
  const exact = toKey2(key);
7483
7720
  const direct = rules.find((candidate) => candidate.key === exact);
7484
7721
  if (direct) return direct;
7485
- return rules.find((candidate) => isCredentialSensitiveKey2(key, [candidate.key]));
7722
+ return rules.find(
7723
+ (candidate) => isCredentialSensitiveKey2(key, [candidate.raw ?? candidate.key])
7724
+ );
7486
7725
  }
7487
7726
  function stableHash2(value) {
7488
7727
  const hash = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
@@ -7681,7 +7920,7 @@ function compileRules2(rules, extraKeys) {
7681
7920
  const out = /* @__PURE__ */ new Map();
7682
7921
  const set = (rule) => {
7683
7922
  const key = toKey2(rule.key);
7684
- out.set(key, { ...rule, key });
7923
+ out.set(key, { ...rule, key, raw: rule.raw ?? rule.key });
7685
7924
  };
7686
7925
  for (const key of DEFAULT_REDACT_KEYS2) {
7687
7926
  set({ key, strategy: "full" });
@@ -7760,6 +7999,7 @@ function createRedactionProfile(profile = "local") {
7760
7999
  }
7761
8000
  var Redactor2 = class {
7762
8001
  #rules;
8002
+ #sensitiveKeys;
7763
8003
  #detectors;
7764
8004
  #profile;
7765
8005
  #replacement;
@@ -7772,6 +8012,7 @@ var Redactor2 = class {
7772
8012
  ...resolved.extraKeys,
7773
8013
  ...options?.extraKeys ?? []
7774
8014
  ]);
8015
+ this.#sensitiveKeys = this.#rules.map((rule) => rule.raw ?? rule.key);
7775
8016
  this.#detectors = [
7776
8017
  ...builtInDetectorsForProfile(this.#profile),
7777
8018
  ...options?.detectors ?? []
@@ -7805,6 +8046,14 @@ var Redactor2 = class {
7805
8046
  #recordFinding(state, finding) {
7806
8047
  if (this.#collectFindings) state.findings.push(finding);
7807
8048
  }
8049
+ #detect(value, key, path16) {
8050
+ for (const detector of this.#detectors) {
8051
+ for (const detection of detector.detect({ path: path16, key, value })) {
8052
+ if ((detection.action ?? "replace") !== "keep") return { detector, detection };
8053
+ }
8054
+ }
8055
+ return void 0;
8056
+ }
7808
8057
  #redactValue(value, key, path16, depth, state) {
7809
8058
  if (depth > this.#maxDepth) {
7810
8059
  this.#recordFinding(
@@ -7823,6 +8072,43 @@ var Redactor2 = class {
7823
8072
  return applyRule(rule, value, this.#replacement);
7824
8073
  }
7825
8074
  }
8075
+ if (typeof value === "string" && looksLikeHttpUrl2(value)) {
8076
+ const url = redactUrlString2(value, {
8077
+ sensitiveKeys: this.#sensitiveKeys,
8078
+ isSensitiveValue: (candidate) => this.#detect(candidate, key, path16) !== 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
+ path16,
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
+ path16
8097
+ );
8098
+ if (residual === void 0) return url.value;
8099
+ this.#recordFinding(
8100
+ state,
8101
+ makeFinding(
8102
+ path16,
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
+ }
7826
8112
  for (const detector of this.#detectors) {
7827
8113
  const detections = detector.detect({ path: path16, key, value });
7828
8114
  for (const detection of detections) {