@posthog/mcp 0.21.4 → 0.22.1

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/README.md CHANGED
@@ -134,6 +134,22 @@ What matters is instance lifetime, not statelessness. A server that is stateless
134
134
  (`sessionIdGenerator: undefined`) but keeps one long-lived server object learns ownership from the
135
135
  first `tools/list` and keeps it, so none of the above applies to it.
136
136
 
137
+ An extra key is not free for a tool that validates strictly (a `.strict()` Zod object, or
138
+ `additionalProperties: false` enforced by the handler): it rejects the call. A low-level server that
139
+ can look up its own tools can resolve ownership without a listing. Return the tool's input schema
140
+ as registered, before PostHog preparation, and the SDK strips the arguments it owns on every
141
+ instance. It never strips a parameter that the returned schema declares. Returning `undefined`
142
+ keeps the behavior above.
143
+
144
+ ```ts
145
+ instrument(server, posthog, {
146
+ resolveOriginalTool: (toolName) => {
147
+ const tool = myToolRegistry.get(toolName)
148
+ return tool ? { inputSchema: tool.inputSchema } : undefined
149
+ },
150
+ })
151
+ ```
152
+
137
153
  The consequence worth knowing: if **your own** tool declares a parameter named `context` and the SDK
138
154
  cannot tell that it is yours, its value is recorded as `$mcp_intent`. It never leaves your project,
139
155
  and it is capped at 2048 characters. Two ways out, both one line:
@@ -221,7 +237,12 @@ open where the SDK cannot tell who declared it, stripping it requires proof that
221
237
  that descriptor itself, so what it declares is known without a listing.
222
238
  - Instrumenting a low-level `Server` learns ownership while serving `tools/list`. A fresh instance
223
239
  that never served one — `createMcpHandler`, or `@rekog/mcp-nest` in its stateless mode — has no
224
- answer, so it records `llm_model` as the self-reported model and strips nothing. A tool that
240
+ answer, so it records `llm_model` as the self-reported model and strips nothing, unless
241
+ `resolveOriginalTool` supplies the tool's schema (see `$mcp_intent` above). Return the schema as
242
+ your `tools/list` advertises it: ownership follows the same rule as a served listing, and a
243
+ listing served on the instance wins. A Zod schema is read the way the MCP SDK advertises it, so a
244
+ union, record or refined object lists as an empty object, gains the injected fields and has them
245
+ stripped; a host that lists its own JSON Schema (an `anyOf`, say) returns that schema. A tool that
225
246
  declares its own `llm_model` on such an instance is therefore recorded under `$mcp_llm_model`
226
247
  until a listing says otherwise; `captureModel: false` or dropping the property in `beforeSend`
227
248
  are the escapes. The SDK never replays your listing handler on the call path to find out.
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-lowlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-lowlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAiD,aAAa,EAAE,MAAM,UAAU,CAAA;AAa5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAoBxC;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CA8BtF"}
1
+ {"version":3,"file":"instrument-lowlevel.d.ts","sourceRoot":"","sources":["../../src/extensions/instrument-lowlevel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAmE,aAAa,EAAE,MAAM,UAAU,CAAA;AAa9G,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAoBxC;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CA8BtF"}
@@ -99,14 +99,26 @@ async function handleToolCallRequest(server, originalCallToolHandler, request, e
99
99
  execute: async ()=>(0, external_feedback_js_namespaceObject.handleFeedback)(report, feedbackOptions, data.logger)
100
100
  });
101
101
  }
102
+ const originalTool = resolveOriginalTool(data, toolName);
102
103
  return await (0, external_instrumentation_js_namespaceObject.captureToolCall)({
103
104
  server,
104
105
  data,
105
106
  request,
106
107
  extra,
107
- execute: (downstreamRequest)=>runOriginalToolHandler(originalCallToolHandler, downstreamRequest, extra)
108
+ execute: (downstreamRequest)=>runOriginalToolHandler(originalCallToolHandler, downstreamRequest, extra),
109
+ parameterOwnership: originalTool && !(toolName && data.toolAnalyticsParameterOwnership.has(toolName)) ? (0, external_analytics_parameters_js_namespaceObject.getAnalyticsParameterOwnership)(originalTool.inputSchema) : void 0,
110
+ inputSchema: originalTool?.inputSchema
108
111
  });
109
112
  }
113
+ function resolveOriginalTool(data, toolName) {
114
+ if (!toolName || !data.options.resolveOriginalTool) return;
115
+ try {
116
+ return data.options.resolveOriginalTool(toolName) ?? void 0;
117
+ } catch (error) {
118
+ data.logger(`Warning: resolveOriginalTool failed for tool ${toolName} - ${error}`);
119
+ return;
120
+ }
121
+ }
110
122
  function runOriginalToolHandler(handler, request, extra) {
111
123
  if (!handler) throw new Error(`Unknown tool: ${request.params?.name || 'unknown'}`);
112
124
  return handler(request, extra);
@@ -67,14 +67,26 @@ async function handleToolCallRequest(server, originalCallToolHandler, request, e
67
67
  execute: async ()=>handleFeedback(report, feedbackOptions, data.logger)
68
68
  });
69
69
  }
70
+ const originalTool = resolveOriginalTool(data, toolName);
70
71
  return await captureToolCall({
71
72
  server,
72
73
  data,
73
74
  request,
74
75
  extra,
75
- execute: (downstreamRequest)=>runOriginalToolHandler(originalCallToolHandler, downstreamRequest, extra)
76
+ execute: (downstreamRequest)=>runOriginalToolHandler(originalCallToolHandler, downstreamRequest, extra),
77
+ parameterOwnership: originalTool && !(toolName && data.toolAnalyticsParameterOwnership.has(toolName)) ? getAnalyticsParameterOwnership(originalTool.inputSchema) : void 0,
78
+ inputSchema: originalTool?.inputSchema
76
79
  });
77
80
  }
81
+ function resolveOriginalTool(data, toolName) {
82
+ if (!toolName || !data.options.resolveOriginalTool) return;
83
+ try {
84
+ return data.options.resolveOriginalTool(toolName) ?? void 0;
85
+ } catch (error) {
86
+ data.logger(`Warning: resolveOriginalTool failed for tool ${toolName} - ${error}`);
87
+ return;
88
+ }
89
+ }
78
90
  function runOriginalToolHandler(handler, request, extra) {
79
91
  if (!handler) throw new Error(`Unknown tool: ${request.params?.name || 'unknown'}`);
80
92
  return handler(request, extra);
@@ -1,34 +1,8 @@
1
+ import { redactPii } from '@posthog/core';
1
2
  type JsonRecord = Record<string, unknown>;
2
- /**
3
- * Sanitizes agent-narrated free text (`$mcp_intent`, the `send_feedback`
4
- * fields): structured PII on top of the passes every captured string gets.
5
- *
6
- * Every step of the order is load-bearing. The binary gate reads the value as it
7
- * arrived, because splicing `[redacted]` into a blob — a Luhn-valid run inside
8
- * base64 is enough — stops it looking like base64 and would ship it whole.
9
- * Credentials go before PII, because a PII pattern can cut a token in half (a
10
- * phone-shaped run inside one) and leave the halves behind. PII goes before the
11
- * URL pass, because the URL rewrite percent-encodes the `@` that the email
12
- * pattern anchors on.
13
- */
3
+ export { redactPii };
14
4
  export declare function sanitizeFreeText(value: string): string;
15
- /**
16
- * Redacts structured personal identifiers (emails, IP addresses, credit-card
17
- * numbers, US SSNs, and phone numbers) from a free-text string. Intended for the
18
- * agent-narrated `$mcp_intent` value only — not for structured tool parameters or
19
- * responses, where the same shapes are often legitimate data. Horizontal Unicode
20
- * spaces are first normalized to an ASCII space so copy-pasted identifiers still
21
- * match. Returns a new string; leaves the input's identifiers untouched when
22
- * nothing matches.
23
- */
24
- export declare function redactPii(value: string): string;
25
5
  export declare function sanitizeCapturedValue(value: unknown): unknown;
26
- /**
27
- * {@link sanitizeCapturedValue} with the intent-grade string pass
28
- * ({@link sanitizeFreeText}) on every string leaf, so nested agent-narrated
29
- * values get structured-PII redaction in the load-bearing order too.
30
- */
31
6
  export declare function sanitizeFreeTextValue(value: unknown): unknown;
32
7
  export declare function buildCapturedMcpParameters(request: unknown): JsonRecord;
33
- export {};
34
8
  //# sourceMappingURL=mcp-payloads.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-payloads.d.ts","sourceRoot":"","sources":["../../src/extensions/mcp-payloads.ts"],"names":[],"mappings":"AA4HA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AA2azC;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKtD;AA6DD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAU/C;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE7D;AAyDD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAiBvE"}
1
+ {"version":3,"file":"mcp-payloads.d.ts","sourceRoot":"","sources":["../../src/extensions/mcp-payloads.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,SAAS,EAKV,MAAM,eAAe,CAAA;AAStB,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAMzC,OAAO,EAAE,SAAS,EAAE,CAAA;AAEpB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE7D;AA6BD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAiBvE"}
@@ -29,334 +29,29 @@ var __webpack_exports__ = {};
29
29
  __webpack_require__.r(__webpack_exports__);
30
30
  __webpack_require__.d(__webpack_exports__, {
31
31
  buildCapturedMcpParameters: ()=>buildCapturedMcpParameters,
32
- redactPii: ()=>redactPii,
32
+ redactPii: ()=>core_namespaceObject.redactPii,
33
33
  sanitizeCapturedValue: ()=>sanitizeCapturedValue,
34
34
  sanitizeFreeText: ()=>sanitizeFreeText,
35
35
  sanitizeFreeTextValue: ()=>sanitizeFreeTextValue
36
36
  });
37
+ const core_namespaceObject = require("@posthog/core");
37
38
  const external_truncation_js_namespaceObject = require("./truncation.js");
38
39
  const CONTEXT_ARGUMENT_NAME = 'context';
39
- const REDACTED_VALUE = '[redacted]';
40
- const BINARY_REDACTED_VALUE = '[binary data redacted - not supported by PostHog MCP analytics]';
41
- const BASE64_PATTERN = /^[A-Za-z0-9+/\n\r]+=*$/;
42
- const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+={0,2}$/;
43
- const BASE64URL_SPECIFIC_CHAR_PATTERN = /[-_]/;
44
- const BASE64_DATA_URL_PREFIX_PATTERN = /^data:[^,\s]*;base64,/i;
45
- const BASE64_DATA_URL_PAYLOAD_PATTERN = /^[A-Za-z0-9+/_-]+={0,2}$/;
46
- const SIZE_GATE = 10240;
47
- const SCANNED_HEAD_LENGTH = 4 * external_truncation_js_namespaceObject.MAX_STRING_LENGTH;
48
- const MIN_REDACTED_HEAD_LENGTH = 2 * external_truncation_js_namespaceObject.MAX_STRING_LENGTH;
49
- const POSTHOG_TOKEN_PATTERN = /\bph[a-z]_[A-Za-z0-9_-]{20,}\b/g;
50
- const SENSITIVE_KEY_PATTERN = /^(authorization|cookie|set-cookie|x-api-key|api[-_]?key|api[-_]?token|access[-_]?token|refresh[-_]?token|token|password|secret|client[-_]?secret|private[-_]?key)$/i;
51
- const URL_PATTERN = /[a-z][a-z0-9+.-]{0,63}:[^\s<>"]+/gi;
52
- const URL_END_PATTERN = /[\s<>"]/;
53
- const URL_PATTERN_ONCE = new RegExp(URL_PATTERN.source, 'i');
54
- const URL_AUTHORITY_SEARCH = /[a-z][a-z0-9+.-]{0,63}:\/\//i;
55
- const URL_AUTHORITY_PATTERN = new RegExp(`^${URL_AUTHORITY_SEARCH.source}`, 'i');
56
- const URL_AUTHORITY_SEARCH_ALL = new RegExp(URL_AUTHORITY_SEARCH.source, 'gi');
57
- const URL_FIELD_CHARACTERS = '=&';
58
- const LEGACY_FIELD_SEPARATOR = ';';
59
- const URL_TRAILING_PUNCTUATION = ".,;:!?)]}'";
60
- const MAX_URL_LENGTH = 8192;
61
- const MAX_URL_QUERY_FIELDS = 128;
62
- const SENSITIVE_QUERY_KEY_SEGMENT_PATTERN = /(^|[-_./;])(auth|token|secret|password|passwd|pwd|credential|signature|sig|key|hmac|sas|bearer|jwt|session|sessionid)([-_./;]|$)/i;
63
- const SENSITIVE_QUERY_KEY_EXACT_PATTERN = /^(code|AWSAccessKeyId|GoogleAccessId|Policy)$/i;
64
- const UNICODE_SPACE_PATTERN = /[\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]/g;
65
- const EMAIL_PATTERN = /[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9.-]{1,255}\.[A-Za-z]{2,24}/g;
66
- const IPV4_PATTERN = /\b(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b/g;
67
- const IPV6_PATTERN = /\b(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}\b|(?<![\w:])(?:[0-9A-Fa-f]{1,4}:){1,7}:(?![\w:])|(?<![\w:])(?:[0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,5}(?![\w])|(?<![\w:])::(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6})(?![\w])/g;
68
- const US_SSN_PATTERN = /\b\d{3}[ .-]\d{2}[ .-]\d{4}\b/g;
69
- const CREDIT_CARD_CANDIDATE_PATTERN = /\b\d(?:[ ./-]?\d){12,}\b/g;
70
- const DIGIT_GROUP_PATTERN = /\d+/g;
71
- const PHONE_NANP_PATTERN = /(?<![\w+])(?:\+?1[ ./-]?)?(?:\(\d{3}\)[ ./-]?|\d{3}[ ./-])\d{3}[ ./-]\d{4}(?![\w])/g;
72
- const PHONE_INTL_PATTERN = /(?<!\w)\+\d{1,3}(?:[ ./()-]{0,2}\d){7,13}(?![\w])/g;
40
+ const SANITIZATION_OPTIONS = {
41
+ maxStringLength: external_truncation_js_namespaceObject.MAX_STRING_LENGTH,
42
+ truncationSuffix: external_truncation_js_namespaceObject.TRUNCATION_SUFFIX
43
+ };
73
44
  function isRecord(value) {
74
45
  return !!value && 'object' == typeof value && !Array.isArray(value);
75
46
  }
76
- function shouldRedactKey(key) {
77
- return SENSITIVE_KEY_PATTERN.test(key);
78
- }
79
- function shouldRedactQueryKey(key) {
80
- return shouldRedactKey(key) || SENSITIVE_QUERY_KEY_SEGMENT_PATTERN.test(key) || SENSITIVE_QUERY_KEY_EXACT_PATTERN.test(key);
81
- }
82
- function isBase64DataUrl(value) {
83
- const prefix = BASE64_DATA_URL_PREFIX_PATTERN.exec(value);
84
- if (!prefix) return false;
85
- let payload;
86
- try {
87
- payload = decodeURIComponent(value.slice(prefix[0].length));
88
- } catch {
89
- return false;
90
- }
91
- return BASE64_DATA_URL_PAYLOAD_PATTERN.test(payload.replace(/[\r\n]/g, ''));
92
- }
93
- function exceedsUrlFieldLimit(fields) {
94
- return fields.split('&', MAX_URL_QUERY_FIELDS + 1).length > MAX_URL_QUERY_FIELDS;
95
- }
96
- function hasLegacySensitiveField(value) {
97
- return value.split(LEGACY_FIELD_SEPARATOR).some((piece)=>shouldRedactQueryKey(piece.split('=', 1)[0]));
98
- }
99
- function sanitizeUrlFieldValue(key, value, allowNestedUrls) {
100
- if (shouldRedactQueryKey(key)) return REDACTED_VALUE;
101
- if (value.includes(LEGACY_FIELD_SEPARATOR) && hasLegacySensitiveField(value)) return REDACTED_VALUE;
102
- if (!URL_PATTERN_ONCE.test(value)) return value;
103
- return allowNestedUrls ? sanitizeUrlsInString(value, {
104
- allowNestedUrls: false,
105
- stripPunctuation: true
106
- }) : REDACTED_VALUE;
107
- }
108
- function sanitizeUrlFields(fields, allowNestedUrls) {
109
- const sanitized = new URLSearchParams();
110
- let changed = false;
111
- let lastFieldSensitive = false;
112
- for (const [key, value] of new URLSearchParams(fields)){
113
- const sanitizedValue = sanitizeUrlFieldValue(key, value, allowNestedUrls);
114
- changed ||= sanitizedValue !== value;
115
- lastFieldSensitive = shouldRedactQueryKey(key) || sanitizedValue !== value;
116
- sanitized.append(key, sanitizedValue);
117
- }
118
- return {
119
- changed,
120
- lastFieldSensitive,
121
- serialized: sanitized.toString()
122
- };
123
- }
124
- function splitTrailingPunctuation(value) {
125
- let end = value.length;
126
- while(end > 0 && URL_TRAILING_PUNCTUATION.includes(value[end - 1]))end--;
127
- return {
128
- address: value.slice(0, end),
129
- suffix: value.slice(end)
130
- };
131
- }
132
- function sanitizeFragmentTail(tail, headLastFieldSensitive, allowNestedUrls) {
133
- if (!headLastFieldSensitive) return sanitizeFragmentPart(tail, allowNestedUrls);
134
- return {
135
- value: '' === tail ? '' : REDACTED_VALUE,
136
- lastFieldSensitive: true
137
- };
138
- }
139
- function splitFragmentParts(hash) {
140
- const fragment = hash.slice(1);
141
- const separator = fragment.indexOf('?');
142
- if (separator < 0) return {
143
- head: fragment,
144
- tail: null
145
- };
146
- return {
147
- head: fragment.slice(0, separator),
148
- tail: fragment.slice(separator + 1)
149
- };
150
- }
151
- function sanitizeFragmentPart(part, allowNestedUrls) {
152
- if (!part.includes('=')) return {
153
- value: sanitizeFragmentText(part, allowNestedUrls),
154
- lastFieldSensitive: false
155
- };
156
- const fields = sanitizeUrlFields(part, allowNestedUrls);
157
- return {
158
- value: fields.changed ? fields.serialized : part,
159
- lastFieldSensitive: fields.lastFieldSensitive
160
- };
161
- }
162
- function sanitizeFragmentText(text, allowNestedUrls) {
163
- if (!allowNestedUrls) return URL_PATTERN_ONCE.test(text) ? REDACTED_VALUE : text;
164
- return sanitizeUrlsInString(text, {
165
- allowNestedUrls: false,
166
- stripPunctuation: true
167
- });
168
- }
169
- function findEmbeddedAuthorityIndexes(value) {
170
- if (!value.includes('://')) return [];
171
- const starts = [
172
- ...value.matchAll(URL_AUTHORITY_SEARCH_ALL)
173
- ].map((match)=>match.index);
174
- const indexes = [];
175
- const delimiters = {
176
- query: false,
177
- fragment: false,
178
- fragmentQuery: false
179
- };
180
- let pending = 0;
181
- let inFields = false;
182
- let lastStructural = '';
183
- for(let cursor = 0; cursor < value.length && pending < starts.length; cursor++){
184
- if (starts[pending] === cursor) {
185
- if (cursor > 0 && '=' !== lastStructural) indexes.push(cursor);
186
- pending++;
187
- }
188
- const character = value[cursor];
189
- if (isDelimiter(character, delimiters, lastStructural)) {
190
- lastStructural = character;
191
- inFields = true;
192
- } else if (inFields && URL_FIELD_CHARACTERS.includes(character)) lastStructural = character;
193
- }
194
- return indexes;
195
- }
196
- function isDelimiter(character, seen, lastStructural) {
197
- if ('#' === character) return seen.fragment ? false : seen.fragment = true;
198
- if ('?' !== character) return false;
199
- if (seen.fragment) {
200
- if ('=' === lastStructural) return false;
201
- return seen.fragmentQuery ? false : seen.fragmentQuery = true;
202
- }
203
- return seen.query ? false : seen.query = true;
204
- }
205
- function sanitizeUrl(value, mode) {
206
- const embedded = findEmbeddedAuthorityIndexes(value);
207
- if (0 === embedded.length) return sanitizeSingleUrl(value, mode);
208
- let result = '';
209
- let start = 0;
210
- for (const index of embedded){
211
- result += sanitizeSingleUrl(value.slice(start, index), {
212
- ...mode,
213
- stripPunctuation: false
214
- });
215
- start = index;
216
- }
217
- return result + sanitizeSingleUrl(value.slice(start), mode);
218
- }
219
- function sanitizeSingleUrl(value, mode) {
220
- if (value.length > MAX_URL_LENGTH && URL_AUTHORITY_PATTERN.test(value)) return REDACTED_VALUE;
221
- const { address, suffix } = mode.stripPunctuation ? splitTrailingPunctuation(value) : {
222
- address: value,
223
- suffix: ''
224
- };
225
- let url;
226
- try {
227
- url = new URL(address);
228
- } catch {
229
- return REDACTED_VALUE + suffix;
230
- }
231
- const query = url.search.slice(1);
232
- const hasFragment = '' !== url.hash;
233
- const fragment = splitFragmentParts(url.hash);
234
- if (exceedsUrlFieldLimit(query) || exceedsUrlFieldLimit(fragment.head) || exceedsUrlFieldLimit(fragment.tail ?? '')) return REDACTED_VALUE + suffix;
235
- let changed = false;
236
- if (url.username || url.password) {
237
- url.username = REDACTED_VALUE;
238
- url.password = '';
239
- changed = true;
240
- }
241
- const sanitizedQuery = sanitizeUrlFields(query, mode.allowNestedUrls);
242
- if (sanitizedQuery.changed) {
243
- url.search = sanitizedQuery.serialized;
244
- changed = true;
245
- }
246
- const sanitizedHead = sanitizeFragmentPart(fragment.head, mode.allowNestedUrls);
247
- const sanitizedTail = null === fragment.tail ? null : sanitizeFragmentTail(fragment.tail, sanitizedHead.lastFieldSensitive, mode.allowNestedUrls);
248
- const sanitizedHash = null === sanitizedTail ? sanitizedHead.value : `${sanitizedHead.value}?${sanitizedTail.value}`;
249
- if (sanitizedHash !== url.hash.slice(1)) {
250
- url.hash = sanitizedHash;
251
- changed = true;
252
- }
253
- const trailingFields = hasFragment ? sanitizedTail ?? sanitizedHead : sanitizedQuery;
254
- return (changed ? url.toString() : address) + (trailingFields.lastFieldSensitive ? '' : suffix);
255
- }
256
- function sanitizeUrlsInString(text, mode) {
257
- return text.replace(URL_PATTERN, (match)=>sanitizeUrl(match, {
258
- ...mode,
259
- stripPunctuation: mode.stripPunctuation && match.length !== text.length
260
- }));
261
- }
262
- function isBinaryBlob(value) {
263
- return value.length >= SIZE_GATE && (BASE64_PATTERN.test(value) || isBase64DataUrl(value) || BASE64URL_SPECIFIC_CHAR_PATTERN.test(value) && BASE64URL_PATTERN.test(value));
264
- }
265
- function redactCredentials(value) {
266
- return value.replace(POSTHOG_TOKEN_PATTERN, REDACTED_VALUE);
267
- }
268
- function redactUrls(value) {
269
- return sanitizeUrlsInString(value, {
270
- allowNestedUrls: true,
271
- stripPunctuation: true
272
- });
273
- }
274
- function redactCapturedHead(value, redact) {
275
- if (value.length > SCANNED_HEAD_LENGTH) {
276
- const head = redact(value.slice(0, headLength(value)));
277
- if (head.length >= MIN_REDACTED_HEAD_LENGTH) return head + external_truncation_js_namespaceObject.TRUNCATION_SUFFIX;
278
- }
279
- return redact(value);
280
- }
281
- function headLength(value) {
282
- let end = SCANNED_HEAD_LENGTH;
283
- while(end > 0 && !URL_END_PATTERN.test(value[end - 1]))end--;
284
- return end;
285
- }
286
- function sanitizeString(value) {
287
- return isBinaryBlob(value) ? BINARY_REDACTED_VALUE : redactCapturedHead(value, (head)=>redactUrls(redactCredentials(head)));
288
- }
289
47
  function sanitizeFreeText(value) {
290
- if (isBinaryBlob(value)) return BINARY_REDACTED_VALUE;
291
- return redactCapturedHead(value, (head)=>redactUrls(redactPii(redactCredentials(head))));
292
- }
293
- function passesLuhn(digits) {
294
- let sum = 0;
295
- let double = false;
296
- for(let index = digits.length - 1; index >= 0; index--){
297
- let digit = digits.charCodeAt(index) - 48;
298
- if (digit < 0 || digit > 9) return false;
299
- if (double) {
300
- digit *= 2;
301
- if (digit > 9) digit -= 9;
302
- }
303
- sum += digit;
304
- double = !double;
305
- }
306
- return sum % 10 === 0;
307
- }
308
- function redactCardInMatch(match) {
309
- const groups = [];
310
- for(let m = DIGIT_GROUP_PATTERN.exec(match); null !== m; m = DIGIT_GROUP_PATTERN.exec(match))groups.push({
311
- digits: m[0],
312
- start: m.index,
313
- end: m.index + m[0].length
314
- });
315
- let output = '';
316
- let cursor = 0;
317
- let first = 0;
318
- while(first < groups.length){
319
- let digits = '';
320
- let matchedLast = -1;
321
- for(let last = first; last < groups.length; last++){
322
- digits += groups[last].digits;
323
- if (digits.length > 19) break;
324
- if (digits.length >= 13 && passesLuhn(digits)) matchedLast = last;
325
- }
326
- if (matchedLast >= 0) {
327
- output += match.slice(cursor, groups[first].start) + REDACTED_VALUE;
328
- cursor = groups[matchedLast].end;
329
- first = matchedLast + 1;
330
- } else first++;
331
- }
332
- return output + match.slice(cursor);
333
- }
334
- function redactPii(value) {
335
- let result = value.replace(UNICODE_SPACE_PATTERN, ' ');
336
- result = result.replace(EMAIL_PATTERN, REDACTED_VALUE);
337
- result = result.replace(IPV4_PATTERN, REDACTED_VALUE);
338
- result = result.replace(IPV6_PATTERN, REDACTED_VALUE);
339
- result = result.replace(CREDIT_CARD_CANDIDATE_PATTERN, redactCardInMatch);
340
- result = result.replace(US_SSN_PATTERN, REDACTED_VALUE);
341
- result = result.replace(PHONE_NANP_PATTERN, REDACTED_VALUE);
342
- result = result.replace(PHONE_INTL_PATTERN, REDACTED_VALUE);
343
- return result;
48
+ return (0, core_namespaceObject.sanitizeFreeText)(value, SANITIZATION_OPTIONS);
344
49
  }
345
50
  function sanitizeCapturedValue(value) {
346
- return sanitizeValueWith(value, sanitizeString);
51
+ return (0, core_namespaceObject.sanitizeCapturedValue)(value, SANITIZATION_OPTIONS);
347
52
  }
348
53
  function sanitizeFreeTextValue(value) {
349
- return sanitizeValueWith(value, sanitizeFreeText);
350
- }
351
- function sanitizeValueWith(value, sanitizeStringFn) {
352
- if (null == value) return value;
353
- if ('string' == typeof value) return sanitizeStringFn(value);
354
- if (Array.isArray(value)) return value.map((item)=>sanitizeValueWith(item, sanitizeStringFn));
355
- if (value instanceof Date) return value;
356
- if ('object' != typeof value) return value;
357
- const result = {};
358
- for (const [key, nestedValue] of Object.entries(value))result[key] = shouldRedactKey(key) ? REDACTED_VALUE : sanitizeValueWith(nestedValue, sanitizeStringFn);
359
- return result;
54
+ return (0, core_namespaceObject.sanitizeFreeTextValue)(value, SANITIZATION_OPTIONS);
360
55
  }
361
56
  function buildCapturedMcpArguments(argumentsValue) {
362
57
  if (!isRecord(argumentsValue)) return sanitizeCapturedValue(argumentsValue);