@agent-inspect/mcp-server 6.4.0 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +840 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +840 -73
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -1801,6 +1801,9 @@ function toMetadataSafeStatus(status) {
|
|
|
1801
1801
|
if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
|
|
1802
1802
|
return status;
|
|
1803
1803
|
}
|
|
1804
|
+
function bundleFailsOnSafety(status, allowUnsafe) {
|
|
1805
|
+
return status === "UNSAFE" || status === "UNKNOWN";
|
|
1806
|
+
}
|
|
1804
1807
|
|
|
1805
1808
|
// packages/core/src/bundle/manifest.ts
|
|
1806
1809
|
var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
|
|
@@ -1833,6 +1836,44 @@ var STATUS_RANK = {
|
|
|
1833
1836
|
warning: 1,
|
|
1834
1837
|
pass: 2
|
|
1835
1838
|
};
|
|
1839
|
+
var DEFAULT_SENSITIVE_KEYS = [
|
|
1840
|
+
"authorization",
|
|
1841
|
+
"cookie",
|
|
1842
|
+
"token",
|
|
1843
|
+
"apikey",
|
|
1844
|
+
"api_key",
|
|
1845
|
+
"password",
|
|
1846
|
+
"secret",
|
|
1847
|
+
"email"
|
|
1848
|
+
];
|
|
1849
|
+
var DEFAULT_RAW_CONTENT_KEYS = [
|
|
1850
|
+
"body",
|
|
1851
|
+
"headers",
|
|
1852
|
+
"input",
|
|
1853
|
+
"messages",
|
|
1854
|
+
"output",
|
|
1855
|
+
"payload",
|
|
1856
|
+
"prompt",
|
|
1857
|
+
"requestbody",
|
|
1858
|
+
"request_body",
|
|
1859
|
+
"responsebody",
|
|
1860
|
+
"response_body",
|
|
1861
|
+
"rawprompt",
|
|
1862
|
+
"raw_prompt",
|
|
1863
|
+
"rawoutput",
|
|
1864
|
+
"raw_output",
|
|
1865
|
+
"toolinput",
|
|
1866
|
+
"tool_input",
|
|
1867
|
+
"tooloutput",
|
|
1868
|
+
"tool_output"
|
|
1869
|
+
];
|
|
1870
|
+
var DEFAULT_SECRET_PATTERNS = [
|
|
1871
|
+
{ id: "bearer-token", pattern: /Bearer\s+[A-Za-z0-9._~+/-]{12,}=*/ },
|
|
1872
|
+
{ id: "openai-key", pattern: /sk-[A-Za-z0-9_-]{16,}/ },
|
|
1873
|
+
{ id: "aws-access-key", pattern: /AKIA[0-9A-Z]{16}/ },
|
|
1874
|
+
{ id: "github-token", pattern: /gh[opsu]_[A-Za-z0-9_]{20,}/ },
|
|
1875
|
+
{ id: "key-value-secret", pattern: /(api[_-]?key|token|password|secret)=\S{8,}/i }
|
|
1876
|
+
];
|
|
1836
1877
|
function compareStrings(a, b) {
|
|
1837
1878
|
return (a ?? "").localeCompare(b ?? "");
|
|
1838
1879
|
}
|
|
@@ -2028,7 +2069,7 @@ function eventEvidence(event, path13) {
|
|
|
2028
2069
|
kind: event.kind,
|
|
2029
2070
|
name: event.name,
|
|
2030
2071
|
status: event.status,
|
|
2031
|
-
...{}
|
|
2072
|
+
...path13 ? { path: path13 } : {}
|
|
2032
2073
|
};
|
|
2033
2074
|
}
|
|
2034
2075
|
function runEvidence(run) {
|
|
@@ -2045,6 +2086,84 @@ function failFinding(ruleId, message, evidence, expected, actual) {
|
|
|
2045
2086
|
evidence: [...evidence]
|
|
2046
2087
|
};
|
|
2047
2088
|
}
|
|
2089
|
+
function isRecord6(value) {
|
|
2090
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2091
|
+
}
|
|
2092
|
+
function normalizedKey(value) {
|
|
2093
|
+
return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
|
|
2094
|
+
}
|
|
2095
|
+
function lastPathSegment(path13) {
|
|
2096
|
+
const parts = path13.split(".");
|
|
2097
|
+
return parts[parts.length - 1] ?? path13;
|
|
2098
|
+
}
|
|
2099
|
+
function valueType(value) {
|
|
2100
|
+
if (Array.isArray(value)) return "array";
|
|
2101
|
+
if (value === null) return "null";
|
|
2102
|
+
return typeof value;
|
|
2103
|
+
}
|
|
2104
|
+
function serializedByteLength(value) {
|
|
2105
|
+
try {
|
|
2106
|
+
return Buffer.byteLength(JSON.stringify(value), "utf-8");
|
|
2107
|
+
} catch {
|
|
2108
|
+
return void 0;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
function pushValueEntries(entries, event, value, path13, key, depth = 0) {
|
|
2112
|
+
entries.push({ event, path: path13, key, value });
|
|
2113
|
+
if (depth >= 8) return;
|
|
2114
|
+
if (Array.isArray(value)) {
|
|
2115
|
+
for (const [index, item] of value.entries()) {
|
|
2116
|
+
pushValueEntries(entries, event, item, `${path13}.${index}`, String(index), depth + 1);
|
|
2117
|
+
}
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
if (!isRecord6(value)) return;
|
|
2121
|
+
for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
|
|
2122
|
+
pushValueEntries(
|
|
2123
|
+
entries,
|
|
2124
|
+
event,
|
|
2125
|
+
value[nestedKey],
|
|
2126
|
+
`${path13}.${nestedKey}`,
|
|
2127
|
+
nestedKey,
|
|
2128
|
+
depth + 1
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
function eventValueEntries(event, options = {}) {
|
|
2133
|
+
const entries = [];
|
|
2134
|
+
if (event.attributes !== void 0) {
|
|
2135
|
+
pushValueEntries(entries, event, event.attributes, "attributes", "attributes");
|
|
2136
|
+
}
|
|
2137
|
+
if (options.includeSummaries) {
|
|
2138
|
+
if (event.inputSummary !== void 0) {
|
|
2139
|
+
pushValueEntries(entries, event, event.inputSummary, "inputSummary", "inputSummary");
|
|
2140
|
+
}
|
|
2141
|
+
if (event.outputSummary !== void 0) {
|
|
2142
|
+
pushValueEntries(entries, event, event.outputSummary, "outputSummary", "outputSummary");
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
if (options.includeError && event.error !== void 0) {
|
|
2146
|
+
pushValueEntries(entries, event, event.error, "error", "error");
|
|
2147
|
+
}
|
|
2148
|
+
return entries;
|
|
2149
|
+
}
|
|
2150
|
+
function limitFindings(findings, maxFindings) {
|
|
2151
|
+
if (maxFindings === void 0 || findings.length <= maxFindings) return findings;
|
|
2152
|
+
return findings.slice(0, Math.max(0, maxFindings));
|
|
2153
|
+
}
|
|
2154
|
+
function hasRedactionMarker(value, markers) {
|
|
2155
|
+
return markers.some((marker) => value.includes(marker)) || /^\[HASH:[A-Za-z0-9_-]+\]$/.test(value);
|
|
2156
|
+
}
|
|
2157
|
+
function isSensitiveKey(key, sensitiveKeys) {
|
|
2158
|
+
if (!key) return false;
|
|
2159
|
+
const normalized = normalizedKey(key);
|
|
2160
|
+
return sensitiveKeys.some((sensitive) => normalized.includes(normalizedKey(sensitive)));
|
|
2161
|
+
}
|
|
2162
|
+
function isRawContentKey(key, forbiddenKeys) {
|
|
2163
|
+
if (!key) return false;
|
|
2164
|
+
const normalized = normalizedKey(key);
|
|
2165
|
+
return forbiddenKeys.some((forbidden) => normalized === normalizedKey(forbidden));
|
|
2166
|
+
}
|
|
2048
2167
|
function createRunStatusRule(options = {}) {
|
|
2049
2168
|
const expected = options.expected ?? "ok";
|
|
2050
2169
|
const allowIncomplete = options.allowIncomplete === true;
|
|
@@ -2084,6 +2203,157 @@ function createRunStatusRule(options = {}) {
|
|
|
2084
2203
|
}
|
|
2085
2204
|
};
|
|
2086
2205
|
}
|
|
2206
|
+
function createSafetyRedactionRule(options = {}) {
|
|
2207
|
+
const sensitiveKeys = options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS;
|
|
2208
|
+
const markers = options.redactedMarkers ?? ["[REDACTED]", "[REDACTED:"];
|
|
2209
|
+
return {
|
|
2210
|
+
id: "safety.redaction",
|
|
2211
|
+
category: "safety",
|
|
2212
|
+
defaultSeverity: "error",
|
|
2213
|
+
evaluate(context) {
|
|
2214
|
+
const findings = [];
|
|
2215
|
+
for (const event of context.events) {
|
|
2216
|
+
for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
|
|
2217
|
+
if (!isSensitiveKey(entry.key ?? lastPathSegment(entry.path), sensitiveKeys)) continue;
|
|
2218
|
+
if (typeof entry.value === "string" && hasRedactionMarker(entry.value, markers)) continue;
|
|
2219
|
+
findings.push(
|
|
2220
|
+
failFinding(
|
|
2221
|
+
"safety.redaction",
|
|
2222
|
+
`Sensitive-looking field at ${entry.path} is not redacted.`,
|
|
2223
|
+
[eventEvidence(event, entry.path)],
|
|
2224
|
+
"redaction marker",
|
|
2225
|
+
{ path: entry.path, valueType: valueType(entry.value) }
|
|
2226
|
+
)
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
return limitFindings(findings, options.maxFindings);
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
function createSafetyRawContentRule(options = {}) {
|
|
2235
|
+
const forbiddenKeys = options.forbiddenKeys ?? DEFAULT_RAW_CONTENT_KEYS;
|
|
2236
|
+
return {
|
|
2237
|
+
id: "safety.rawPrompt",
|
|
2238
|
+
category: "safety",
|
|
2239
|
+
defaultSeverity: "error",
|
|
2240
|
+
evaluate(context) {
|
|
2241
|
+
const findings = [];
|
|
2242
|
+
for (const event of context.events) {
|
|
2243
|
+
for (const entry of eventValueEntries(event, { includeSummaries: options.includeSummaries })) {
|
|
2244
|
+
const key = entry.key ?? lastPathSegment(entry.path);
|
|
2245
|
+
if (!isRawContentKey(key, forbiddenKeys)) continue;
|
|
2246
|
+
findings.push(
|
|
2247
|
+
failFinding(
|
|
2248
|
+
"safety.rawPrompt",
|
|
2249
|
+
`Raw content-like field ${entry.path} is present.`,
|
|
2250
|
+
[eventEvidence(event, entry.path)],
|
|
2251
|
+
"metadata-only trace fields",
|
|
2252
|
+
{ path: entry.path, valueType: valueType(entry.value) }
|
|
2253
|
+
)
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
return limitFindings(findings, options.maxFindings);
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
function createSafetySecretPatternRule(options = {}) {
|
|
2262
|
+
const patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
|
|
2263
|
+
const maxStringLength = options.maxStringLength ?? 4096;
|
|
2264
|
+
return {
|
|
2265
|
+
id: "safety.secretPattern",
|
|
2266
|
+
category: "safety",
|
|
2267
|
+
defaultSeverity: "error",
|
|
2268
|
+
evaluate(context) {
|
|
2269
|
+
const findings = [];
|
|
2270
|
+
for (const event of context.events) {
|
|
2271
|
+
for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
|
|
2272
|
+
if (typeof entry.value !== "string") continue;
|
|
2273
|
+
const sample = entry.value.slice(0, maxStringLength);
|
|
2274
|
+
for (const pattern of patterns) {
|
|
2275
|
+
pattern.pattern.lastIndex = 0;
|
|
2276
|
+
if (!pattern.pattern.test(sample)) continue;
|
|
2277
|
+
pattern.pattern.lastIndex = 0;
|
|
2278
|
+
findings.push(
|
|
2279
|
+
failFinding(
|
|
2280
|
+
"safety.secretPattern",
|
|
2281
|
+
`Secret-like pattern ${pattern.id} matched at ${entry.path}.`,
|
|
2282
|
+
[eventEvidence(event, entry.path)],
|
|
2283
|
+
"no secret-like strings",
|
|
2284
|
+
{ pattern: pattern.id, path: entry.path }
|
|
2285
|
+
)
|
|
2286
|
+
);
|
|
2287
|
+
break;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
return limitFindings(findings, options.maxFindings);
|
|
2292
|
+
}
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
function createSafetyOversizedAttributeRule(options) {
|
|
2296
|
+
return {
|
|
2297
|
+
id: "safety.oversizedAttribute",
|
|
2298
|
+
category: "safety",
|
|
2299
|
+
defaultSeverity: "error",
|
|
2300
|
+
evaluate(context) {
|
|
2301
|
+
const findings = [];
|
|
2302
|
+
for (const event of context.events) {
|
|
2303
|
+
for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
|
|
2304
|
+
if (typeof entry.value === "string" && options.maxStringLength !== void 0 && entry.value.length > options.maxStringLength) {
|
|
2305
|
+
findings.push(
|
|
2306
|
+
failFinding(
|
|
2307
|
+
"safety.oversizedAttribute",
|
|
2308
|
+
`String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
|
|
2309
|
+
[eventEvidence(event, entry.path)],
|
|
2310
|
+
{ maxStringLength: options.maxStringLength },
|
|
2311
|
+
{ path: entry.path, length: entry.value.length }
|
|
2312
|
+
)
|
|
2313
|
+
);
|
|
2314
|
+
}
|
|
2315
|
+
if (Array.isArray(entry.value) && options.maxArrayLength !== void 0 && entry.value.length > options.maxArrayLength) {
|
|
2316
|
+
findings.push(
|
|
2317
|
+
failFinding(
|
|
2318
|
+
"safety.oversizedAttribute",
|
|
2319
|
+
`Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
|
|
2320
|
+
[eventEvidence(event, entry.path)],
|
|
2321
|
+
{ maxArrayLength: options.maxArrayLength },
|
|
2322
|
+
{ path: entry.path, length: entry.value.length }
|
|
2323
|
+
)
|
|
2324
|
+
);
|
|
2325
|
+
}
|
|
2326
|
+
if (isRecord6(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
|
|
2327
|
+
findings.push(
|
|
2328
|
+
failFinding(
|
|
2329
|
+
"safety.oversizedAttribute",
|
|
2330
|
+
`Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
|
|
2331
|
+
[eventEvidence(event, entry.path)],
|
|
2332
|
+
{ maxObjectKeys: options.maxObjectKeys },
|
|
2333
|
+
{ path: entry.path, keys: Object.keys(entry.value).length }
|
|
2334
|
+
)
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
if (options.maxSerializedBytes !== void 0) {
|
|
2338
|
+
const bytes = serializedByteLength(entry.value);
|
|
2339
|
+
if (bytes !== void 0 && bytes > options.maxSerializedBytes) {
|
|
2340
|
+
findings.push(
|
|
2341
|
+
failFinding(
|
|
2342
|
+
"safety.oversizedAttribute",
|
|
2343
|
+
`Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
|
|
2344
|
+
[eventEvidence(event, entry.path)],
|
|
2345
|
+
{ maxSerializedBytes: options.maxSerializedBytes },
|
|
2346
|
+
{ path: entry.path, bytes }
|
|
2347
|
+
)
|
|
2348
|
+
);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
return limitFindings(findings, options.maxFindings);
|
|
2354
|
+
}
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2087
2357
|
function runTraceChecks(input, options = {}) {
|
|
2088
2358
|
const selected = resolveSelectedRun(input, options.runId);
|
|
2089
2359
|
if (selected.diagnostics.length > 0) {
|
|
@@ -2130,14 +2400,14 @@ function runTraceChecks(input, options = {}) {
|
|
|
2130
2400
|
}
|
|
2131
2401
|
|
|
2132
2402
|
// packages/core/src/persisted/token-usage.ts
|
|
2133
|
-
function
|
|
2403
|
+
function isRecord7(value) {
|
|
2134
2404
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2135
2405
|
}
|
|
2136
2406
|
function nonNegativeFinite(value) {
|
|
2137
2407
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
2138
2408
|
}
|
|
2139
2409
|
function normalizeTokenUsage(value) {
|
|
2140
|
-
if (!
|
|
2410
|
+
if (!isRecord7(value)) return void 0;
|
|
2141
2411
|
const input = nonNegativeFinite(value.input);
|
|
2142
2412
|
const output = nonNegativeFinite(value.output);
|
|
2143
2413
|
const suppliedTotal = nonNegativeFinite(value.total);
|
|
@@ -2895,7 +3165,7 @@ function persistedEventsForParsedTrace(parsed) {
|
|
|
2895
3165
|
sourceName: "agent-inspect-jsonl-reader"
|
|
2896
3166
|
});
|
|
2897
3167
|
}
|
|
2898
|
-
function
|
|
3168
|
+
function isRecord8(value) {
|
|
2899
3169
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2900
3170
|
}
|
|
2901
3171
|
function isNonEmptyString3(value) {
|
|
@@ -2910,13 +3180,13 @@ function readStringField(record, keys) {
|
|
|
2910
3180
|
}
|
|
2911
3181
|
function readRecordField(record, key) {
|
|
2912
3182
|
const value = record[key];
|
|
2913
|
-
return
|
|
3183
|
+
return isRecord8(value) ? value : void 0;
|
|
2914
3184
|
}
|
|
2915
3185
|
function parseJsonDocument(content) {
|
|
2916
3186
|
return JSON.parse(content);
|
|
2917
3187
|
}
|
|
2918
3188
|
function looksLikeOpenInferenceSpan(value) {
|
|
2919
|
-
if (!
|
|
3189
|
+
if (!isRecord8(value)) return false;
|
|
2920
3190
|
const attributes = readRecordField(value, "attributes");
|
|
2921
3191
|
return readStringField(value, ["trace_id", "traceId"]) !== void 0 && readStringField(value, ["span_id", "spanId"]) !== void 0 && (readStringField(value, ["name"]) !== void 0 || attributes?.["openinference.span.kind"] !== void 0);
|
|
2922
3192
|
}
|
|
@@ -2941,7 +3211,7 @@ function extractOpenInferenceDocument(root) {
|
|
|
2941
3211
|
unsupportedFields
|
|
2942
3212
|
};
|
|
2943
3213
|
}
|
|
2944
|
-
if (!
|
|
3214
|
+
if (!isRecord8(root)) return void 0;
|
|
2945
3215
|
const rootFormat = root.format;
|
|
2946
3216
|
const rootCompatibility = root.compatibility;
|
|
2947
3217
|
const version = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
|
|
@@ -3081,7 +3351,7 @@ function summarizeAttributeValue(value) {
|
|
|
3081
3351
|
if (Array.isArray(value)) {
|
|
3082
3352
|
return { type: "array", length: value.length };
|
|
3083
3353
|
}
|
|
3084
|
-
if (
|
|
3354
|
+
if (isRecord8(value)) {
|
|
3085
3355
|
return { type: "object", keyCount: Object.keys(value).length };
|
|
3086
3356
|
}
|
|
3087
3357
|
if (value === null) {
|
|
@@ -3168,7 +3438,7 @@ function mapOpenInferenceKind(span, attributes, pathPrefix) {
|
|
|
3168
3438
|
}
|
|
3169
3439
|
}
|
|
3170
3440
|
function mapOpenInferenceStatus(status) {
|
|
3171
|
-
if (!
|
|
3441
|
+
if (!isRecord8(status)) return void 0;
|
|
3172
3442
|
const rawCode = status.code;
|
|
3173
3443
|
if (typeof rawCode !== "string") return void 0;
|
|
3174
3444
|
switch (rawCode.toUpperCase()) {
|
|
@@ -3268,7 +3538,7 @@ function mapOpenInferenceSpan(span, index, version) {
|
|
|
3268
3538
|
warnings.push(...kindWarnings);
|
|
3269
3539
|
const status = mapOpenInferenceStatus(span.status);
|
|
3270
3540
|
const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
|
|
3271
|
-
const errorMessage =
|
|
3541
|
+
const errorMessage = isRecord8(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
|
|
3272
3542
|
const event = {
|
|
3273
3543
|
schemaVersion: "0.2",
|
|
3274
3544
|
eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
|
|
@@ -3414,7 +3684,7 @@ var openInferenceJsonReader = {
|
|
|
3414
3684
|
}
|
|
3415
3685
|
};
|
|
3416
3686
|
function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
|
|
3417
|
-
if (!
|
|
3687
|
+
if (!isRecord8(value)) {
|
|
3418
3688
|
unsupportedFields.push(field);
|
|
3419
3689
|
warnings.push({
|
|
3420
3690
|
code: "otlp_attribute_value_invalid",
|
|
@@ -3436,15 +3706,15 @@ function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
|
|
|
3436
3706
|
if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
|
|
3437
3707
|
return value.doubleValue;
|
|
3438
3708
|
}
|
|
3439
|
-
if (
|
|
3709
|
+
if (isRecord8(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
|
|
3440
3710
|
return value.arrayValue.values.map(
|
|
3441
3711
|
(item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
|
|
3442
3712
|
);
|
|
3443
3713
|
}
|
|
3444
|
-
if (
|
|
3714
|
+
if (isRecord8(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
|
|
3445
3715
|
const out = {};
|
|
3446
3716
|
for (const [index, item] of value.kvlistValue.values.entries()) {
|
|
3447
|
-
if (!
|
|
3717
|
+
if (!isRecord8(item) || typeof item.key !== "string") {
|
|
3448
3718
|
unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
|
|
3449
3719
|
continue;
|
|
3450
3720
|
}
|
|
@@ -3495,7 +3765,7 @@ function parseOtlpAttributes(value, pathPrefix) {
|
|
|
3495
3765
|
}
|
|
3496
3766
|
for (const [index, item] of value.entries()) {
|
|
3497
3767
|
const field = `${pathPrefix}[${index}]`;
|
|
3498
|
-
if (!
|
|
3768
|
+
if (!isRecord8(item) || typeof item.key !== "string") {
|
|
3499
3769
|
unsupportedFields.push(field);
|
|
3500
3770
|
warnings.push({
|
|
3501
3771
|
code: "otlp_attribute_invalid",
|
|
@@ -3518,16 +3788,16 @@ function parseOtlpAttributes(value, pathPrefix) {
|
|
|
3518
3788
|
return { attributes, warnings, unsupportedFields };
|
|
3519
3789
|
}
|
|
3520
3790
|
function looksLikeOtlpSpan(value) {
|
|
3521
|
-
return
|
|
3791
|
+
return isRecord8(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
|
|
3522
3792
|
}
|
|
3523
3793
|
function extractOtlpDocument(root) {
|
|
3524
|
-
if (!
|
|
3794
|
+
if (!isRecord8(root) || !Array.isArray(root.resourceSpans)) return void 0;
|
|
3525
3795
|
const spans = [];
|
|
3526
3796
|
const warnings = [];
|
|
3527
3797
|
const unsupportedFields = [];
|
|
3528
3798
|
for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
|
|
3529
3799
|
const resourcePath = `resourceSpans[${resourceIndex}]`;
|
|
3530
|
-
if (!
|
|
3800
|
+
if (!isRecord8(resourceSpan)) {
|
|
3531
3801
|
unsupportedFields.push(resourcePath);
|
|
3532
3802
|
continue;
|
|
3533
3803
|
}
|
|
@@ -3550,7 +3820,7 @@ function extractOtlpDocument(root) {
|
|
|
3550
3820
|
}
|
|
3551
3821
|
for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
|
|
3552
3822
|
const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
|
|
3553
|
-
if (!
|
|
3823
|
+
if (!isRecord8(scopeSpan)) {
|
|
3554
3824
|
unsupportedFields.push(scopePath);
|
|
3555
3825
|
continue;
|
|
3556
3826
|
}
|
|
@@ -3617,7 +3887,7 @@ function extractOtlpDocument(root) {
|
|
|
3617
3887
|
};
|
|
3618
3888
|
}
|
|
3619
3889
|
function mapOtlpStatus(status) {
|
|
3620
|
-
if (!
|
|
3890
|
+
if (!isRecord8(status)) return void 0;
|
|
3621
3891
|
const rawCode = status.code;
|
|
3622
3892
|
if (typeof rawCode !== "string") return void 0;
|
|
3623
3893
|
switch (rawCode.toUpperCase()) {
|
|
@@ -3717,7 +3987,7 @@ function mapOtlpEvents(value, pathPrefix) {
|
|
|
3717
3987
|
const events = [];
|
|
3718
3988
|
for (const [index, event] of value.entries()) {
|
|
3719
3989
|
const eventPath = `${pathPrefix}[${index}]`;
|
|
3720
|
-
if (!
|
|
3990
|
+
if (!isRecord8(event)) {
|
|
3721
3991
|
unsupportedFields.push(eventPath);
|
|
3722
3992
|
continue;
|
|
3723
3993
|
}
|
|
@@ -3855,7 +4125,7 @@ function mapOtlpSpan(context) {
|
|
|
3855
4125
|
warnings.push(...kindWarnings);
|
|
3856
4126
|
const status = mapOtlpStatus(span.status);
|
|
3857
4127
|
const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
|
|
3858
|
-
const errorMessage =
|
|
4128
|
+
const errorMessage = isRecord8(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
|
|
3859
4129
|
const event = {
|
|
3860
4130
|
schemaVersion: "0.2",
|
|
3861
4131
|
eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
|
|
@@ -4651,7 +4921,7 @@ function diffRuns(left, right, options) {
|
|
|
4651
4921
|
var EXPORT_PAYLOAD_VERSION = "0.1.2";
|
|
4652
4922
|
|
|
4653
4923
|
// packages/core/src/exporters/redact-export.ts
|
|
4654
|
-
function
|
|
4924
|
+
function isRecord9(value) {
|
|
4655
4925
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4656
4926
|
}
|
|
4657
4927
|
function deepClone(value) {
|
|
@@ -4725,7 +4995,7 @@ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPrevi
|
|
|
4725
4995
|
0
|
|
4726
4996
|
);
|
|
4727
4997
|
const err = bounded.error;
|
|
4728
|
-
if (
|
|
4998
|
+
if (isRecord9(err) && typeof err.message === "string") {
|
|
4729
4999
|
bounded.error = {
|
|
4730
5000
|
...err,
|
|
4731
5001
|
message: truncateStringForProfile(
|
|
@@ -5328,6 +5598,453 @@ function exportRunTree(tree, options) {
|
|
|
5328
5598
|
}
|
|
5329
5599
|
}
|
|
5330
5600
|
|
|
5601
|
+
// packages/mcp-server/src/assess-trace.ts
|
|
5602
|
+
var DEFAULT_MAX_STRING_LENGTH = 16384;
|
|
5603
|
+
var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
|
|
5604
|
+
var DEFAULT_MAX_OBJECT_KEYS = 200;
|
|
5605
|
+
var DEFAULT_MAX_SERIALIZED_BYTES = 128 * 1024;
|
|
5606
|
+
function buildMcpSafetyRules() {
|
|
5607
|
+
return [
|
|
5608
|
+
createSafetyRawContentRule(),
|
|
5609
|
+
createSafetyRedactionRule(),
|
|
5610
|
+
createSafetySecretPatternRule(),
|
|
5611
|
+
createSafetyOversizedAttributeRule({
|
|
5612
|
+
maxStringLength: DEFAULT_MAX_STRING_LENGTH,
|
|
5613
|
+
maxArrayLength: DEFAULT_MAX_ARRAY_LENGTH,
|
|
5614
|
+
maxObjectKeys: DEFAULT_MAX_OBJECT_KEYS,
|
|
5615
|
+
maxSerializedBytes: DEFAULT_MAX_SERIALIZED_BYTES
|
|
5616
|
+
})
|
|
5617
|
+
];
|
|
5618
|
+
}
|
|
5619
|
+
function statusFrom(findings, hasErrors) {
|
|
5620
|
+
if (hasErrors) return "UNKNOWN";
|
|
5621
|
+
if (findings.some((item) => item.severity === "error")) return "UNSAFE";
|
|
5622
|
+
if (findings.some((item) => item.severity === "warning")) return "SAFE WITH WARNINGS";
|
|
5623
|
+
return "SAFE";
|
|
5624
|
+
}
|
|
5625
|
+
function assessTraceForMcp(read, runId) {
|
|
5626
|
+
const rules = buildMcpSafetyRules();
|
|
5627
|
+
const checkResult = runTraceChecks({ read }, { rules, runId });
|
|
5628
|
+
const hasErrors = checkResult.diagnostics.some((item) => item.severity === "error");
|
|
5629
|
+
const status = statusFrom(checkResult.findings, hasErrors);
|
|
5630
|
+
return {
|
|
5631
|
+
status,
|
|
5632
|
+
errors: checkResult.diagnostics.filter((item) => item.severity === "error").length + checkResult.findings.filter((item) => item.severity === "error").length,
|
|
5633
|
+
warnings: checkResult.diagnostics.filter((item) => item.severity === "warning").length + checkResult.findings.filter((item) => item.severity === "warning").length,
|
|
5634
|
+
findings: checkResult.findings.length
|
|
5635
|
+
};
|
|
5636
|
+
}
|
|
5637
|
+
var DEFAULT_REDACT_KEYS2 = [
|
|
5638
|
+
"authorization",
|
|
5639
|
+
"cookie",
|
|
5640
|
+
"token",
|
|
5641
|
+
"apiKey",
|
|
5642
|
+
"password",
|
|
5643
|
+
"secret",
|
|
5644
|
+
"email"
|
|
5645
|
+
];
|
|
5646
|
+
var SHARE_PROFILE_EXTRA_KEYS2 = [
|
|
5647
|
+
"userEmail",
|
|
5648
|
+
"customerEmail",
|
|
5649
|
+
"phone",
|
|
5650
|
+
"phoneNumber",
|
|
5651
|
+
"address",
|
|
5652
|
+
"ip",
|
|
5653
|
+
"ipAddress",
|
|
5654
|
+
"sessionId",
|
|
5655
|
+
"requestId",
|
|
5656
|
+
"correlationId",
|
|
5657
|
+
"decisionId",
|
|
5658
|
+
"groupId",
|
|
5659
|
+
"customerId",
|
|
5660
|
+
"userId",
|
|
5661
|
+
"accountId",
|
|
5662
|
+
"tenantId",
|
|
5663
|
+
"orgId",
|
|
5664
|
+
"organizationId",
|
|
5665
|
+
"traceId",
|
|
5666
|
+
"spanId",
|
|
5667
|
+
"parentSpanId"
|
|
5668
|
+
];
|
|
5669
|
+
var STRICT_PROFILE_EXTRA_KEYS2 = [
|
|
5670
|
+
"prompt",
|
|
5671
|
+
"completion",
|
|
5672
|
+
"input",
|
|
5673
|
+
"output",
|
|
5674
|
+
"inputPreview",
|
|
5675
|
+
"outputPreview",
|
|
5676
|
+
"message",
|
|
5677
|
+
"messages",
|
|
5678
|
+
"transcript",
|
|
5679
|
+
"context",
|
|
5680
|
+
"document",
|
|
5681
|
+
"documents",
|
|
5682
|
+
"chunk",
|
|
5683
|
+
"chunks",
|
|
5684
|
+
"retrieval",
|
|
5685
|
+
"query"
|
|
5686
|
+
];
|
|
5687
|
+
function isRecord10(value) {
|
|
5688
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5689
|
+
}
|
|
5690
|
+
function toKey2(key) {
|
|
5691
|
+
return key.toLowerCase();
|
|
5692
|
+
}
|
|
5693
|
+
function stableHash2(value) {
|
|
5694
|
+
const hash = crypto.createHash("sha256").update(value, "utf8").digest("hex");
|
|
5695
|
+
return hash.slice(0, 8);
|
|
5696
|
+
}
|
|
5697
|
+
function stringifyScalar(value) {
|
|
5698
|
+
if (typeof value === "string") return value;
|
|
5699
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
5700
|
+
return String(value);
|
|
5701
|
+
}
|
|
5702
|
+
return void 0;
|
|
5703
|
+
}
|
|
5704
|
+
function patternDetector(options) {
|
|
5705
|
+
return {
|
|
5706
|
+
id: options.id,
|
|
5707
|
+
severity: options.severity ?? "warning",
|
|
5708
|
+
matchKind: "value",
|
|
5709
|
+
detect(input) {
|
|
5710
|
+
if (typeof input.value !== "string") return [];
|
|
5711
|
+
options.pattern.lastIndex = 0;
|
|
5712
|
+
return options.pattern.test(input.value) ? [{ action: "replace", severity: options.severity ?? "warning", matchKind: "value" }] : [];
|
|
5713
|
+
}
|
|
5714
|
+
};
|
|
5715
|
+
}
|
|
5716
|
+
function digitsOnly(value) {
|
|
5717
|
+
return value.replace(/\D/g, "");
|
|
5718
|
+
}
|
|
5719
|
+
function passesLuhn(value) {
|
|
5720
|
+
const digits = digitsOnly(value);
|
|
5721
|
+
if (digits.length < 13 || digits.length > 19) return false;
|
|
5722
|
+
let sum = 0;
|
|
5723
|
+
let double = false;
|
|
5724
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
5725
|
+
let digit = Number(digits[i]);
|
|
5726
|
+
if (double) {
|
|
5727
|
+
digit *= 2;
|
|
5728
|
+
if (digit > 9) digit -= 9;
|
|
5729
|
+
}
|
|
5730
|
+
sum += digit;
|
|
5731
|
+
double = !double;
|
|
5732
|
+
}
|
|
5733
|
+
return sum % 10 === 0;
|
|
5734
|
+
}
|
|
5735
|
+
var credentialDetectors = [
|
|
5736
|
+
patternDetector({
|
|
5737
|
+
id: "value.authorizationHeader",
|
|
5738
|
+
pattern: /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
|
|
5739
|
+
severity: "error"
|
|
5740
|
+
}),
|
|
5741
|
+
patternDetector({
|
|
5742
|
+
id: "value.bearerToken",
|
|
5743
|
+
pattern: /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
|
|
5744
|
+
severity: "error"
|
|
5745
|
+
}),
|
|
5746
|
+
patternDetector({
|
|
5747
|
+
id: "value.cookie",
|
|
5748
|
+
pattern: /\b[a-z0-9_.-]+=[^;\s]+(?:;\s*[a-z0-9_.-]+=[^;\s]+)+/i,
|
|
5749
|
+
severity: "error"
|
|
5750
|
+
}),
|
|
5751
|
+
patternDetector({
|
|
5752
|
+
id: "value.jwt",
|
|
5753
|
+
pattern: /\beyJ[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\b/,
|
|
5754
|
+
severity: "error"
|
|
5755
|
+
}),
|
|
5756
|
+
patternDetector({
|
|
5757
|
+
id: "value.providerApiKey",
|
|
5758
|
+
pattern: /\b(?:sk-(?:proj-)?[a-zA-Z0-9_-]{16,}|sk-ant-[a-zA-Z0-9_-]{16,}|AIza[0-9A-Za-z_-]{20,})\b/,
|
|
5759
|
+
severity: "error"
|
|
5760
|
+
}),
|
|
5761
|
+
patternDetector({
|
|
5762
|
+
id: "value.githubToken",
|
|
5763
|
+
pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
|
|
5764
|
+
severity: "error"
|
|
5765
|
+
}),
|
|
5766
|
+
patternDetector({
|
|
5767
|
+
id: "value.awsAccessKey",
|
|
5768
|
+
pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
|
|
5769
|
+
severity: "error"
|
|
5770
|
+
}),
|
|
5771
|
+
patternDetector({
|
|
5772
|
+
id: "value.privateKey",
|
|
5773
|
+
pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*-----END [A-Z ]*PRIVATE KEY-----/,
|
|
5774
|
+
severity: "error"
|
|
5775
|
+
}),
|
|
5776
|
+
{
|
|
5777
|
+
id: "value.creditCard",
|
|
5778
|
+
severity: "error",
|
|
5779
|
+
matchKind: "value",
|
|
5780
|
+
detect(input) {
|
|
5781
|
+
if (typeof input.value !== "string") return [];
|
|
5782
|
+
const candidatePattern = /(?:\d[ -]?){13,19}/g;
|
|
5783
|
+
for (const match of input.value.matchAll(candidatePattern)) {
|
|
5784
|
+
const candidate = match[0] ?? "";
|
|
5785
|
+
if (passesLuhn(candidate)) {
|
|
5786
|
+
return [{ action: "replace", severity: "error", matchKind: "value" }];
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
return [];
|
|
5790
|
+
}
|
|
5791
|
+
}
|
|
5792
|
+
];
|
|
5793
|
+
var identifierDetectors = [
|
|
5794
|
+
patternDetector({
|
|
5795
|
+
id: "value.email",
|
|
5796
|
+
pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i
|
|
5797
|
+
}),
|
|
5798
|
+
patternDetector({
|
|
5799
|
+
id: "value.phone",
|
|
5800
|
+
pattern: /\b(?:\+\d{1,3}[\s.-]?)?(?:\(?\d{3}\)?[\s.-])\d{3}[\s.-]\d{4}\b/
|
|
5801
|
+
}),
|
|
5802
|
+
patternDetector({
|
|
5803
|
+
id: "value.ipv4",
|
|
5804
|
+
pattern: /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/
|
|
5805
|
+
}),
|
|
5806
|
+
patternDetector({
|
|
5807
|
+
id: "value.ipv6",
|
|
5808
|
+
pattern: /\b(?:[0-9a-f]{1,4}:){2,7}[0-9a-f]{1,4}\b/i
|
|
5809
|
+
})
|
|
5810
|
+
];
|
|
5811
|
+
function builtInDetectorsForProfile(profile) {
|
|
5812
|
+
if (profile === "local") return credentialDetectors;
|
|
5813
|
+
return [...credentialDetectors, ...identifierDetectors];
|
|
5814
|
+
}
|
|
5815
|
+
function compileRules2(rules, extraKeys) {
|
|
5816
|
+
const out = /* @__PURE__ */ new Map();
|
|
5817
|
+
const set = (rule) => {
|
|
5818
|
+
const key = toKey2(rule.key);
|
|
5819
|
+
out.set(key, { ...rule, key });
|
|
5820
|
+
};
|
|
5821
|
+
for (const key of DEFAULT_REDACT_KEYS2) {
|
|
5822
|
+
set({ key, strategy: "full" });
|
|
5823
|
+
}
|
|
5824
|
+
for (const key of extraKeys ?? []) {
|
|
5825
|
+
if (typeof key === "string" && key.length > 0) {
|
|
5826
|
+
set({ key, strategy: "full" });
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
for (const rule of rules ?? []) {
|
|
5830
|
+
if (typeof rule === "string") {
|
|
5831
|
+
set({ key: rule, strategy: "full" });
|
|
5832
|
+
continue;
|
|
5833
|
+
}
|
|
5834
|
+
if (rule.strategy === "full") set({ key: rule.key, strategy: "full" });
|
|
5835
|
+
if (rule.strategy === "hash") set({ key: rule.key, strategy: "hash" });
|
|
5836
|
+
if (rule.strategy === "prefix") {
|
|
5837
|
+
set({
|
|
5838
|
+
key: rule.key,
|
|
5839
|
+
strategy: "prefix",
|
|
5840
|
+
keep: typeof rule.keep === "number" ? rule.keep : 8
|
|
5841
|
+
});
|
|
5842
|
+
}
|
|
5843
|
+
}
|
|
5844
|
+
return [...out.values()];
|
|
5845
|
+
}
|
|
5846
|
+
function actionForRule(rule) {
|
|
5847
|
+
if (rule.strategy === "full") return "replace";
|
|
5848
|
+
return rule.strategy;
|
|
5849
|
+
}
|
|
5850
|
+
function applyRule(rule, value, replacement) {
|
|
5851
|
+
if (rule.strategy === "full") return replacement;
|
|
5852
|
+
const asString = stringifyScalar(value);
|
|
5853
|
+
if (rule.strategy === "prefix") {
|
|
5854
|
+
if (asString === void 0) return replacement;
|
|
5855
|
+
const keep = Math.max(0, Math.floor(rule.keep));
|
|
5856
|
+
return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
|
|
5857
|
+
}
|
|
5858
|
+
if (rule.strategy === "hash") {
|
|
5859
|
+
if (asString === void 0) return "[HASH:unknown]";
|
|
5860
|
+
return `[HASH:${stableHash2(asString)}]`;
|
|
5861
|
+
}
|
|
5862
|
+
return value;
|
|
5863
|
+
}
|
|
5864
|
+
function childPath(path13, key) {
|
|
5865
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
|
|
5866
|
+
return path13 ? `${path13}.${key}` : key;
|
|
5867
|
+
}
|
|
5868
|
+
return `${path13 || "$"}[${JSON.stringify(key)}]`;
|
|
5869
|
+
}
|
|
5870
|
+
function indexPath(path13, index) {
|
|
5871
|
+
return `${path13 || "$"}[${index}]`;
|
|
5872
|
+
}
|
|
5873
|
+
function makeFinding(path13, detector, action, matchKind, severity = "warning", preview) {
|
|
5874
|
+
return preview === void 0 ? { path: path13, detector, action, severity, matchKind } : { path: path13, detector, action, severity, matchKind, preview };
|
|
5875
|
+
}
|
|
5876
|
+
function createRedactionProfile(profile = "local") {
|
|
5877
|
+
switch (profile) {
|
|
5878
|
+
case "local":
|
|
5879
|
+
return { profile: "local", extraKeys: [] };
|
|
5880
|
+
case "share":
|
|
5881
|
+
return {
|
|
5882
|
+
profile: "share",
|
|
5883
|
+
extraKeys: SHARE_PROFILE_EXTRA_KEYS2,
|
|
5884
|
+
maxMetadataValueLengthCap: 500,
|
|
5885
|
+
maxPreviewLengthCap: 200
|
|
5886
|
+
};
|
|
5887
|
+
case "strict":
|
|
5888
|
+
return {
|
|
5889
|
+
profile: "strict",
|
|
5890
|
+
extraKeys: [...SHARE_PROFILE_EXTRA_KEYS2, ...STRICT_PROFILE_EXTRA_KEYS2],
|
|
5891
|
+
maxMetadataValueLengthCap: 200,
|
|
5892
|
+
maxPreviewLengthCap: 80
|
|
5893
|
+
};
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
var Redactor2 = class {
|
|
5897
|
+
#rules;
|
|
5898
|
+
#detectors;
|
|
5899
|
+
#profile;
|
|
5900
|
+
#replacement;
|
|
5901
|
+
#maxDepth;
|
|
5902
|
+
#collectFindings;
|
|
5903
|
+
constructor(options) {
|
|
5904
|
+
const resolved = createRedactionProfile(options?.profile ?? "local");
|
|
5905
|
+
this.#profile = resolved.profile;
|
|
5906
|
+
this.#rules = compileRules2(options?.rules, [
|
|
5907
|
+
...resolved.extraKeys,
|
|
5908
|
+
...options?.extraKeys ?? []
|
|
5909
|
+
]);
|
|
5910
|
+
this.#detectors = [
|
|
5911
|
+
...builtInDetectorsForProfile(this.#profile),
|
|
5912
|
+
...options?.detectors ?? []
|
|
5913
|
+
];
|
|
5914
|
+
this.#replacement = options?.replacement ?? "[REDACTED]";
|
|
5915
|
+
this.#maxDepth = options?.maxDepth ?? 32;
|
|
5916
|
+
this.#collectFindings = options?.collectFindings ?? true;
|
|
5917
|
+
}
|
|
5918
|
+
redactValue(key, value) {
|
|
5919
|
+
return this.#redactValue(value, key, key, 0, {
|
|
5920
|
+
findings: [],
|
|
5921
|
+
seen: /* @__PURE__ */ new WeakMap()
|
|
5922
|
+
});
|
|
5923
|
+
}
|
|
5924
|
+
redactRecord(record) {
|
|
5925
|
+
return this.redact(record).value;
|
|
5926
|
+
}
|
|
5927
|
+
redact(value) {
|
|
5928
|
+
const state = {
|
|
5929
|
+
findings: [],
|
|
5930
|
+
seen: /* @__PURE__ */ new WeakMap()
|
|
5931
|
+
};
|
|
5932
|
+
const redacted = this.#redactValue(value, void 0, "$", 0, state);
|
|
5933
|
+
return {
|
|
5934
|
+
value: redacted,
|
|
5935
|
+
findings: state.findings,
|
|
5936
|
+
redacted: state.findings.some((finding) => finding.action !== "keep"),
|
|
5937
|
+
profile: this.#profile
|
|
5938
|
+
};
|
|
5939
|
+
}
|
|
5940
|
+
#recordFinding(state, finding) {
|
|
5941
|
+
if (this.#collectFindings) state.findings.push(finding);
|
|
5942
|
+
}
|
|
5943
|
+
#redactValue(value, key, path13, depth, state) {
|
|
5944
|
+
if (depth > this.#maxDepth) {
|
|
5945
|
+
this.#recordFinding(
|
|
5946
|
+
state,
|
|
5947
|
+
makeFinding(path13, "structure.maxDepth", "truncate", "value", "warning")
|
|
5948
|
+
);
|
|
5949
|
+
return "[Truncated]";
|
|
5950
|
+
}
|
|
5951
|
+
if (key !== void 0) {
|
|
5952
|
+
const rule = this.#rules.find((candidate) => candidate.key === toKey2(key));
|
|
5953
|
+
if (rule) {
|
|
5954
|
+
this.#recordFinding(
|
|
5955
|
+
state,
|
|
5956
|
+
makeFinding(path13, `key.${rule.key}`, actionForRule(rule), "key", "warning")
|
|
5957
|
+
);
|
|
5958
|
+
return applyRule(rule, value, this.#replacement);
|
|
5959
|
+
}
|
|
5960
|
+
}
|
|
5961
|
+
for (const detector of this.#detectors) {
|
|
5962
|
+
const detections = detector.detect({ path: path13, key, value });
|
|
5963
|
+
for (const detection of detections) {
|
|
5964
|
+
const action = detection.action ?? "replace";
|
|
5965
|
+
this.#recordFinding(
|
|
5966
|
+
state,
|
|
5967
|
+
makeFinding(
|
|
5968
|
+
path13,
|
|
5969
|
+
detector.id,
|
|
5970
|
+
action,
|
|
5971
|
+
detection.matchKind ?? detector.matchKind ?? "custom",
|
|
5972
|
+
detection.severity ?? detector.severity ?? "warning",
|
|
5973
|
+
detection.preview
|
|
5974
|
+
)
|
|
5975
|
+
);
|
|
5976
|
+
if (action !== "keep") {
|
|
5977
|
+
return detection.replacement ?? this.#replacement;
|
|
5978
|
+
}
|
|
5979
|
+
}
|
|
5980
|
+
}
|
|
5981
|
+
if (Array.isArray(value)) {
|
|
5982
|
+
if (state.seen.has(value)) return state.seen.get(value);
|
|
5983
|
+
const out = [];
|
|
5984
|
+
state.seen.set(value, out);
|
|
5985
|
+
value.forEach((item, index) => {
|
|
5986
|
+
out[index] = this.#redactValue(item, void 0, indexPath(path13, index), depth + 1, state);
|
|
5987
|
+
});
|
|
5988
|
+
return out;
|
|
5989
|
+
}
|
|
5990
|
+
if (isRecord10(value)) {
|
|
5991
|
+
if (state.seen.has(value)) return state.seen.get(value);
|
|
5992
|
+
const out = {};
|
|
5993
|
+
state.seen.set(value, out);
|
|
5994
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
5995
|
+
out[entryKey] = this.#redactValue(
|
|
5996
|
+
entryValue,
|
|
5997
|
+
entryKey,
|
|
5998
|
+
childPath(path13 === "$" ? "" : path13, entryKey),
|
|
5999
|
+
depth + 1,
|
|
6000
|
+
state
|
|
6001
|
+
);
|
|
6002
|
+
}
|
|
6003
|
+
return out;
|
|
6004
|
+
}
|
|
6005
|
+
return value;
|
|
6006
|
+
}
|
|
6007
|
+
};
|
|
6008
|
+
function createRedactor(options) {
|
|
6009
|
+
return new Redactor2(options);
|
|
6010
|
+
}
|
|
6011
|
+
function redact(value, options) {
|
|
6012
|
+
return createRedactor(options).redact(value);
|
|
6013
|
+
}
|
|
6014
|
+
|
|
6015
|
+
// packages/mcp-server/src/prepare-result.ts
|
|
6016
|
+
var DEFAULT_MCP_RESULT_MAX_BYTES = 512 * 1024;
|
|
6017
|
+
function stableStringify(value) {
|
|
6018
|
+
return JSON.stringify(value);
|
|
6019
|
+
}
|
|
6020
|
+
function prepareMcpToolResult(payload, options = {}) {
|
|
6021
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MCP_RESULT_MAX_BYTES;
|
|
6022
|
+
const profile = options.redactionProfile ?? "share";
|
|
6023
|
+
const diagnostics = [];
|
|
6024
|
+
const redacted = redact(payload, { profile });
|
|
6025
|
+
let result = redacted.value;
|
|
6026
|
+
const redactionFindings = redacted.findings.length;
|
|
6027
|
+
if (redactionFindings > 0) {
|
|
6028
|
+
diagnostics.push(`Redacted ${redactionFindings} sensitive value(s) from MCP tool result.`);
|
|
6029
|
+
}
|
|
6030
|
+
let text = stableStringify(result);
|
|
6031
|
+
let truncated = false;
|
|
6032
|
+
if (text.length > maxBytes) {
|
|
6033
|
+
truncated = true;
|
|
6034
|
+
diagnostics.push(
|
|
6035
|
+
`MCP tool result truncated from ${text.length} to ${maxBytes} bytes.`
|
|
6036
|
+
);
|
|
6037
|
+
text = `${text.slice(0, maxBytes)}
|
|
6038
|
+
\u2026[truncated]`;
|
|
6039
|
+
try {
|
|
6040
|
+
result = JSON.parse(text.replace(/\n…\[truncated\]$/, ""));
|
|
6041
|
+
} catch {
|
|
6042
|
+
result = { truncated: true, preview: text.slice(0, maxBytes) };
|
|
6043
|
+
}
|
|
6044
|
+
}
|
|
6045
|
+
return { payload: result, diagnostics, truncated, redactionFindings };
|
|
6046
|
+
}
|
|
6047
|
+
|
|
5331
6048
|
// packages/mcp-server/src/tools.ts
|
|
5332
6049
|
var READ_ONLY_TOOLS = [
|
|
5333
6050
|
{
|
|
@@ -5444,6 +6161,20 @@ function textResult(payload) {
|
|
|
5444
6161
|
isError: false
|
|
5445
6162
|
};
|
|
5446
6163
|
}
|
|
6164
|
+
function deliverMcpPayload(payload, context) {
|
|
6165
|
+
const prepared = prepareMcpToolResult(payload, {
|
|
6166
|
+
redactionProfile: redactionProfileForExport(context)
|
|
6167
|
+
});
|
|
6168
|
+
const body = prepared.diagnostics.length > 0 || prepared.truncated ? {
|
|
6169
|
+
...typeof prepared.payload === "object" && prepared.payload !== null && !Array.isArray(prepared.payload) ? prepared.payload : { value: prepared.payload },
|
|
6170
|
+
_mcp: {
|
|
6171
|
+
diagnostics: prepared.diagnostics,
|
|
6172
|
+
truncated: prepared.truncated,
|
|
6173
|
+
redactionFindings: prepared.redactionFindings
|
|
6174
|
+
}
|
|
6175
|
+
} : prepared.payload;
|
|
6176
|
+
return textResult(body);
|
|
6177
|
+
}
|
|
5447
6178
|
function errorResult2(message) {
|
|
5448
6179
|
return {
|
|
5449
6180
|
content: [{ type: "text", text: message }],
|
|
@@ -5493,25 +6224,29 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5493
6224
|
files,
|
|
5494
6225
|
(fileName) => td.getPath(fileName)
|
|
5495
6226
|
);
|
|
5496
|
-
return
|
|
6227
|
+
return deliverMcpPayload(
|
|
5497
6228
|
metas.map((meta) => ({
|
|
5498
6229
|
runId: meta.runId,
|
|
5499
6230
|
name: meta.name,
|
|
5500
6231
|
status: meta.status,
|
|
5501
6232
|
file: path.basename(meta.filePath)
|
|
5502
|
-
}))
|
|
6233
|
+
})),
|
|
6234
|
+
context
|
|
5503
6235
|
);
|
|
5504
6236
|
}
|
|
5505
6237
|
case "read_trace": {
|
|
5506
6238
|
const runId = String(args.runId ?? "");
|
|
5507
6239
|
const { read } = await openRunTrace(context, runId);
|
|
5508
6240
|
const events = read.events.length > context.maxEvents ? read.events.slice(0, context.maxEvents) : read.events;
|
|
5509
|
-
return
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
6241
|
+
return deliverMcpPayload(
|
|
6242
|
+
{
|
|
6243
|
+
runId,
|
|
6244
|
+
format: read.format,
|
|
6245
|
+
truncated: read.events.length > events.length,
|
|
6246
|
+
events
|
|
6247
|
+
},
|
|
6248
|
+
context
|
|
6249
|
+
);
|
|
5515
6250
|
}
|
|
5516
6251
|
case "search_traces": {
|
|
5517
6252
|
const query = String(args.query ?? "").trim();
|
|
@@ -5528,17 +6263,20 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5528
6263
|
name: query,
|
|
5529
6264
|
limit: 25
|
|
5530
6265
|
});
|
|
5531
|
-
return
|
|
6266
|
+
return deliverMcpPayload(results, context);
|
|
5532
6267
|
}
|
|
5533
6268
|
case "find_first_error": {
|
|
5534
6269
|
const runId = String(args.runId ?? "");
|
|
5535
6270
|
const { read } = await openRunTrace(context, runId);
|
|
5536
6271
|
const timeline = buildRunTimeline(legacyTraceEvents(read.events));
|
|
5537
6272
|
const firstError = timeline.entries.find((entry) => entry.isError);
|
|
5538
|
-
return
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
6273
|
+
return deliverMcpPayload(
|
|
6274
|
+
{
|
|
6275
|
+
runId,
|
|
6276
|
+
firstError: firstError ?? null
|
|
6277
|
+
},
|
|
6278
|
+
context
|
|
6279
|
+
);
|
|
5542
6280
|
}
|
|
5543
6281
|
case "find_slowest_path": {
|
|
5544
6282
|
const runId = String(args.runId ?? "");
|
|
@@ -5548,11 +6286,14 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5548
6286
|
slowTopN: 5
|
|
5549
6287
|
});
|
|
5550
6288
|
const ranked = [...timeline.entries].filter((entry) => entry.durationMs !== void 0 && Number.isFinite(entry.durationMs)).sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0)).slice(0, 5);
|
|
5551
|
-
return
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
6289
|
+
return deliverMcpPayload(
|
|
6290
|
+
{
|
|
6291
|
+
runId,
|
|
6292
|
+
slowest: ranked[0] ?? null,
|
|
6293
|
+
top: ranked
|
|
6294
|
+
},
|
|
6295
|
+
context
|
|
6296
|
+
);
|
|
5556
6297
|
}
|
|
5557
6298
|
case "compare_runs": {
|
|
5558
6299
|
const leftRunId = String(args.leftRunId ?? "");
|
|
@@ -5563,11 +6304,14 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5563
6304
|
manualTraceEventsToComparableRun(legacyTraceEvents(left.read.events)),
|
|
5564
6305
|
manualTraceEventsToComparableRun(legacyTraceEvents(right.read.events))
|
|
5565
6306
|
);
|
|
5566
|
-
return
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
6307
|
+
return deliverMcpPayload(
|
|
6308
|
+
{
|
|
6309
|
+
summary: diff.summary,
|
|
6310
|
+
differences: diff.differences.slice(0, 50),
|
|
6311
|
+
truncated: diff.differences.length > 50
|
|
6312
|
+
},
|
|
6313
|
+
context
|
|
6314
|
+
);
|
|
5571
6315
|
}
|
|
5572
6316
|
case "run_checks": {
|
|
5573
6317
|
const runId = String(args.runId ?? "");
|
|
@@ -5576,7 +6320,7 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5576
6320
|
{ read },
|
|
5577
6321
|
{ rules: [createRunStatusRule()], select: ["run.status"], runId }
|
|
5578
6322
|
);
|
|
5579
|
-
return
|
|
6323
|
+
return deliverMcpPayload(result, context);
|
|
5580
6324
|
}
|
|
5581
6325
|
case "create_share_safe_report": {
|
|
5582
6326
|
const runId = String(args.runId ?? "");
|
|
@@ -5586,43 +6330,55 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5586
6330
|
const profile = redactionProfileForExport(context);
|
|
5587
6331
|
const markdown = exportMarkdown(run, {
|
|
5588
6332
|
redacted: true});
|
|
5589
|
-
return
|
|
6333
|
+
return deliverMcpPayload({ runId, profile, markdown: markdown.content }, context);
|
|
5590
6334
|
}
|
|
5591
6335
|
case "summarize_failed_run": {
|
|
5592
6336
|
const runId = String(args.runId ?? "");
|
|
5593
6337
|
const { read } = await openRunTrace(context, runId);
|
|
5594
6338
|
const traceEvents = legacyTraceEvents(read.events);
|
|
5595
6339
|
const summary = buildRunWhatSummary(traceEvents);
|
|
5596
|
-
return
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
6340
|
+
return deliverMcpPayload(
|
|
6341
|
+
{
|
|
6342
|
+
runId,
|
|
6343
|
+
status: summary.status,
|
|
6344
|
+
summary: renderRunWhat(summary),
|
|
6345
|
+
failedStepNames: summary.failedStepNames,
|
|
6346
|
+
correlation: summary.correlation ?? null
|
|
6347
|
+
},
|
|
6348
|
+
context
|
|
6349
|
+
);
|
|
5603
6350
|
}
|
|
5604
6351
|
case "retrieve_decision_notes": {
|
|
5605
6352
|
const runId = String(args.runId ?? "");
|
|
5606
6353
|
const { read } = await openRunTrace(context, runId);
|
|
5607
6354
|
const notes = decisionNotes(read.events);
|
|
5608
|
-
return
|
|
6355
|
+
return deliverMcpPayload({ runId, decisions: notes, count: notes.length }, context);
|
|
5609
6356
|
}
|
|
5610
6357
|
case "find_failed_observation": {
|
|
5611
6358
|
const runId = String(args.runId ?? "");
|
|
5612
6359
|
const { read } = await openRunTrace(context, runId);
|
|
5613
6360
|
const outcomes = extractOutcomesFromTraceEvents(legacyTraceEvents(read.events));
|
|
5614
6361
|
const failed = outcomes.filter((outcome) => outcome.status === "failed");
|
|
5615
|
-
return
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
6362
|
+
return deliverMcpPayload(
|
|
6363
|
+
{
|
|
6364
|
+
runId,
|
|
6365
|
+
failed,
|
|
6366
|
+
count: failed.length
|
|
6367
|
+
},
|
|
6368
|
+
context
|
|
6369
|
+
);
|
|
5620
6370
|
}
|
|
5621
6371
|
case "create_share_safe_bundle": {
|
|
5622
6372
|
const runId = String(args.runId ?? "");
|
|
5623
6373
|
const { read } = await openRunTrace(context, runId);
|
|
5624
6374
|
const run = read.runs.find((item) => item.runId === runId) ?? read.runs[0];
|
|
5625
6375
|
if (!run) return errorResult2(`Run tree not found: ${runId}`);
|
|
6376
|
+
const safety = assessTraceForMcp(read, runId);
|
|
6377
|
+
if (bundleFailsOnSafety(safety.status)) {
|
|
6378
|
+
return errorResult2(
|
|
6379
|
+
`Share-safe bundle refused: safety status is ${safety.status}. Resolve findings before export.`
|
|
6380
|
+
);
|
|
6381
|
+
}
|
|
5626
6382
|
const profile = redactionProfileForExport(context);
|
|
5627
6383
|
const markdown = exportMarkdown(run, {
|
|
5628
6384
|
redacted: true});
|
|
@@ -5636,20 +6392,31 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5636
6392
|
profile,
|
|
5637
6393
|
resolve: { runIds: [runId] },
|
|
5638
6394
|
checks: {
|
|
5639
|
-
aggregateStatus:
|
|
5640
|
-
runs: [
|
|
6395
|
+
aggregateStatus: safety.status,
|
|
6396
|
+
runs: [
|
|
6397
|
+
{
|
|
6398
|
+
runId,
|
|
6399
|
+
status: safety.status,
|
|
6400
|
+
errors: safety.errors,
|
|
6401
|
+
warnings: safety.warnings,
|
|
6402
|
+
findings: safety.findings
|
|
6403
|
+
}
|
|
6404
|
+
]
|
|
5641
6405
|
},
|
|
5642
6406
|
files: ["report.md", "tree.json"]
|
|
5643
6407
|
});
|
|
5644
|
-
return
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
6408
|
+
return deliverMcpPayload(
|
|
6409
|
+
{
|
|
6410
|
+
runId,
|
|
6411
|
+
profile,
|
|
6412
|
+
metadata,
|
|
6413
|
+
files: {
|
|
6414
|
+
"report.md": markdown.content,
|
|
6415
|
+
"tree.json": tree.content
|
|
6416
|
+
}
|
|
6417
|
+
},
|
|
6418
|
+
context
|
|
6419
|
+
);
|
|
5653
6420
|
}
|
|
5654
6421
|
default:
|
|
5655
6422
|
return errorResult2(`Unknown tool: ${name}`);
|