@bryan-thompson/inspector-assessment-client 1.34.2 → 1.35.2
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/assets/{OAuthCallback-CBcYNwyM.js → OAuthCallback-jfmizOMH.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-B0zFGlM8.js → OAuthDebugCallback-bU5kKvnt.js} +1 -1
- package/dist/assets/{index-Djm_oTDV.js → index-Ce63ds7G.js} +5 -4
- package/dist/index.html +1 -1
- package/lib/lib/assessment/extendedTypes.d.ts +19 -5
- package/lib/lib/assessment/extendedTypes.d.ts.map +1 -1
- package/lib/lib/assessment/resultTypes.d.ts +42 -0
- package/lib/lib/assessment/resultTypes.d.ts.map +1 -1
- package/lib/lib/assessment/sharedSchemas.d.ts +13 -0
- package/lib/lib/assessment/sharedSchemas.d.ts.map +1 -1
- package/lib/lib/assessment/sharedSchemas.js +9 -0
- package/lib/lib/assessment/summarizer/AssessmentSummarizer.d.ts +112 -0
- package/lib/lib/assessment/summarizer/AssessmentSummarizer.d.ts.map +1 -0
- package/lib/lib/assessment/summarizer/AssessmentSummarizer.js +452 -0
- package/lib/lib/assessment/summarizer/index.d.ts +19 -0
- package/lib/lib/assessment/summarizer/index.d.ts.map +1 -0
- package/lib/lib/assessment/summarizer/index.js +19 -0
- package/lib/lib/assessment/summarizer/stageBEnrichmentBuilder.d.ts +36 -0
- package/lib/lib/assessment/summarizer/stageBEnrichmentBuilder.d.ts.map +1 -0
- package/lib/lib/assessment/summarizer/stageBEnrichmentBuilder.js +282 -0
- package/lib/lib/assessment/summarizer/stageBTypes.d.ts +154 -0
- package/lib/lib/assessment/summarizer/stageBTypes.d.ts.map +1 -0
- package/lib/lib/assessment/summarizer/stageBTypes.js +24 -0
- package/lib/lib/assessment/summarizer/tokenEstimator.d.ts +103 -0
- package/lib/lib/assessment/summarizer/tokenEstimator.d.ts.map +1 -0
- package/lib/lib/assessment/summarizer/tokenEstimator.js +225 -0
- package/lib/lib/assessment/summarizer/types.d.ts +187 -0
- package/lib/lib/assessment/summarizer/types.d.ts.map +1 -0
- package/lib/lib/assessment/summarizer/types.js +20 -0
- package/lib/lib/moduleScoring.d.ts +2 -1
- package/lib/lib/moduleScoring.d.ts.map +1 -1
- package/lib/lib/moduleScoring.js +2 -1
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts +8 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +51 -8
- package/lib/services/assessment/modules/securityTests/TestValidityAnalyzer.d.ts +28 -0
- package/lib/services/assessment/modules/securityTests/TestValidityAnalyzer.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/TestValidityAnalyzer.js +180 -0
- package/package.json +1 -1
|
@@ -11,6 +11,9 @@ const DEFAULT_CONFIG = {
|
|
|
11
11
|
confidenceReduceThresholdPercent: 90,
|
|
12
12
|
minimumTestsForAnalysis: 10,
|
|
13
13
|
maxResponseCompareLength: 1000,
|
|
14
|
+
// Issue #135: Enhanced data defaults
|
|
15
|
+
maxSamplePairs: 10,
|
|
16
|
+
maxDistributionEntries: 5,
|
|
14
17
|
};
|
|
15
18
|
/**
|
|
16
19
|
* Analyzes security test results for response uniformity.
|
|
@@ -70,12 +73,23 @@ export class TestValidityAnalyzer {
|
|
|
70
73
|
recommendedConfidence,
|
|
71
74
|
warning: isCompromised
|
|
72
75
|
? {
|
|
76
|
+
// Existing fields
|
|
73
77
|
identicalResponseCount: mostCommonCount,
|
|
74
78
|
totalResponses: testsWithResponses.length,
|
|
75
79
|
percentageIdentical: Math.round(percentageIdentical * 10) / 10,
|
|
76
80
|
sampleResponse: sampleResponse.substring(0, 500),
|
|
77
81
|
detectedPattern,
|
|
78
82
|
explanation: this.generateExplanation(percentageIdentical, detectedPattern, mostCommonCount, testsWithResponses.length),
|
|
83
|
+
// Issue #135: Enhanced fields for Stage B semantic analysis
|
|
84
|
+
responseDiversity: {
|
|
85
|
+
uniqueResponses: responseCounts.size,
|
|
86
|
+
entropyScore: Math.round(this.calculateEntropy(responseCounts) * 100) / 100,
|
|
87
|
+
distribution: this.buildResponseDistribution(responseCounts, testsWithResponses.length),
|
|
88
|
+
},
|
|
89
|
+
toolUniformity: Object.fromEntries(toolUniformity),
|
|
90
|
+
attackPatternCorrelation: this.analyzeAttackPatterns(testsWithResponses),
|
|
91
|
+
samplePairs: this.collectSamplePairs(testsWithResponses),
|
|
92
|
+
responseMetadata: this.collectResponseMetadata(testsWithResponses),
|
|
79
93
|
}
|
|
80
94
|
: undefined,
|
|
81
95
|
toolUniformity,
|
|
@@ -220,4 +234,170 @@ export class TestValidityAnalyzer {
|
|
|
220
234
|
const patternDesc = patternDescriptions[pattern] ?? patternDescriptions.unknown;
|
|
221
235
|
return `${Math.round(percentage)}% of security tests (${identicalCount}/${totalCount}) returned identical responses indicating ${patternDesc}. Tests may not have reached security-relevant code paths. Resolve the underlying issue and re-run the assessment for valid security analysis.`;
|
|
222
236
|
}
|
|
237
|
+
// ==========================================================================
|
|
238
|
+
// Issue #135: Enhanced data methods for Stage B semantic analysis
|
|
239
|
+
// ==========================================================================
|
|
240
|
+
/**
|
|
241
|
+
* Calculate Shannon entropy for response diversity (0=uniform, 1=max diversity)
|
|
242
|
+
*/
|
|
243
|
+
calculateEntropy(counts) {
|
|
244
|
+
const total = Array.from(counts.values()).reduce((a, b) => a + b, 0);
|
|
245
|
+
if (total === 0)
|
|
246
|
+
return 0;
|
|
247
|
+
let entropy = 0;
|
|
248
|
+
for (const count of counts.values()) {
|
|
249
|
+
if (count > 0) {
|
|
250
|
+
const p = count / total;
|
|
251
|
+
entropy -= p * Math.log2(p);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Normalize to 0-1 scale based on max possible entropy
|
|
255
|
+
const maxEntropy = Math.log2(counts.size);
|
|
256
|
+
return maxEntropy > 0 ? entropy / maxEntropy : 0;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Build response distribution sorted by frequency
|
|
260
|
+
*/
|
|
261
|
+
buildResponseDistribution(counts, total) {
|
|
262
|
+
return Array.from(counts.entries())
|
|
263
|
+
.sort((a, b) => b[1] - a[1])
|
|
264
|
+
.slice(0, this.config.maxDistributionEntries)
|
|
265
|
+
.map(([response, count]) => ({
|
|
266
|
+
response: response.substring(0, 200),
|
|
267
|
+
count,
|
|
268
|
+
percentage: Math.round((count / total) * 1000) / 10,
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Extract attack category from test name
|
|
273
|
+
*/
|
|
274
|
+
extractAttackCategory(testName) {
|
|
275
|
+
const lower = testName.toLowerCase();
|
|
276
|
+
if (lower.includes("injection") || lower.includes("sqli"))
|
|
277
|
+
return "injection";
|
|
278
|
+
if (lower.includes("xss") || lower.includes("script"))
|
|
279
|
+
return "xss";
|
|
280
|
+
if (lower.includes("path") || lower.includes("traversal"))
|
|
281
|
+
return "path_traversal";
|
|
282
|
+
if (lower.includes("command") || lower.includes("rce"))
|
|
283
|
+
return "command_injection";
|
|
284
|
+
if (lower.includes("ssrf"))
|
|
285
|
+
return "ssrf";
|
|
286
|
+
if (lower.includes("auth"))
|
|
287
|
+
return "authentication";
|
|
288
|
+
return "other";
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Analyze attack pattern correlation by category
|
|
292
|
+
*/
|
|
293
|
+
analyzeAttackPatterns(tests) {
|
|
294
|
+
const patterns = new Map();
|
|
295
|
+
// Group by attack category
|
|
296
|
+
for (const test of tests) {
|
|
297
|
+
const category = this.extractAttackCategory(test.testName);
|
|
298
|
+
if (!patterns.has(category))
|
|
299
|
+
patterns.set(category, []);
|
|
300
|
+
patterns.get(category).push(test);
|
|
301
|
+
}
|
|
302
|
+
// Build correlation stats
|
|
303
|
+
const result = {};
|
|
304
|
+
for (const [category, categoryTests] of patterns) {
|
|
305
|
+
const withResponse = categoryTests.filter((t) => t.response);
|
|
306
|
+
const counts = this.countNormalizedResponses(withResponse);
|
|
307
|
+
result[category] = {
|
|
308
|
+
testCount: categoryTests.length,
|
|
309
|
+
uniqueResponses: counts.size,
|
|
310
|
+
samplePayload: categoryTests[0]?.payload?.substring(0, 100),
|
|
311
|
+
sampleResponse: categoryTests[0]?.response?.substring(0, 200),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Collect sample payload-response pairs with category diversity
|
|
318
|
+
*/
|
|
319
|
+
collectSamplePairs(tests) {
|
|
320
|
+
const pairs = [];
|
|
321
|
+
const seenCategories = new Set();
|
|
322
|
+
// Prioritize diverse categories
|
|
323
|
+
for (const test of tests) {
|
|
324
|
+
if (pairs.length >= this.config.maxSamplePairs)
|
|
325
|
+
break;
|
|
326
|
+
if (!test.response || !test.payload)
|
|
327
|
+
continue;
|
|
328
|
+
const category = this.extractAttackCategory(test.testName);
|
|
329
|
+
if (!seenCategories.has(category)) {
|
|
330
|
+
seenCategories.add(category);
|
|
331
|
+
pairs.push({
|
|
332
|
+
attackCategory: category,
|
|
333
|
+
payload: test.payload.substring(0, 100),
|
|
334
|
+
response: test.response.substring(0, 300),
|
|
335
|
+
vulnerable: test.vulnerable,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Fill remaining slots with additional samples
|
|
340
|
+
for (const test of tests) {
|
|
341
|
+
if (pairs.length >= this.config.maxSamplePairs)
|
|
342
|
+
break;
|
|
343
|
+
if (!test.response || !test.payload)
|
|
344
|
+
continue;
|
|
345
|
+
const truncatedPayload = test.payload.substring(0, 100);
|
|
346
|
+
if (!pairs.some((p) => p.payload === truncatedPayload)) {
|
|
347
|
+
pairs.push({
|
|
348
|
+
attackCategory: this.extractAttackCategory(test.testName),
|
|
349
|
+
payload: truncatedPayload,
|
|
350
|
+
response: test.response.substring(0, 300),
|
|
351
|
+
vulnerable: test.vulnerable,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return pairs;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Collect response metadata statistics
|
|
359
|
+
*/
|
|
360
|
+
collectResponseMetadata(tests) {
|
|
361
|
+
if (tests.length === 0) {
|
|
362
|
+
return {
|
|
363
|
+
avgLength: 0,
|
|
364
|
+
minLength: 0,
|
|
365
|
+
maxLength: 0,
|
|
366
|
+
emptyCount: 0,
|
|
367
|
+
errorCount: 0,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
let totalLength = 0;
|
|
371
|
+
let minLength = Infinity;
|
|
372
|
+
let maxLength = 0;
|
|
373
|
+
let emptyCount = 0;
|
|
374
|
+
let errorCount = 0;
|
|
375
|
+
let nonEmptyCount = 0;
|
|
376
|
+
for (const test of tests) {
|
|
377
|
+
const response = test.response ?? "";
|
|
378
|
+
const len = response.length;
|
|
379
|
+
// Check for empty/minimal responses
|
|
380
|
+
if (len === 0 ||
|
|
381
|
+
/^(\s*\{\s*\}|\s*\[\s*\]|\s*null\s*|)$/i.test(response.trim())) {
|
|
382
|
+
emptyCount++;
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
totalLength += len;
|
|
386
|
+
minLength = Math.min(minLength, len);
|
|
387
|
+
maxLength = Math.max(maxLength, len);
|
|
388
|
+
nonEmptyCount++;
|
|
389
|
+
}
|
|
390
|
+
// Check for error indicators
|
|
391
|
+
if (/error|exception|fail/i.test(response)) {
|
|
392
|
+
errorCount++;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
avgLength: nonEmptyCount > 0 ? Math.round(totalLength / nonEmptyCount) : 0,
|
|
397
|
+
minLength: minLength === Infinity ? 0 : minLength,
|
|
398
|
+
maxLength,
|
|
399
|
+
emptyCount,
|
|
400
|
+
errorCount,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
223
403
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bryan-thompson/inspector-assessment-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.2",
|
|
4
4
|
"description": "Client-side application for the Enhanced MCP Inspector with assessment capabilities",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bryan Thompson <bryan@triepod.ai>",
|