@bryan-thompson/inspector-assessment 1.37.0 → 1.38.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/cli/build/lib/assessment-runner/assessment-executor.js +29 -1
- package/cli/build/lib/assessment-runner/source-loader.js +11 -0
- package/cli/package.json +1 -1
- package/client/dist/assets/{OAuthCallback-6-wM7Zc1.js → OAuthCallback-AngeBaCl.js} +1 -1
- package/client/dist/assets/{OAuthDebugCallback-Bw9-AzzP.js → OAuthDebugCallback--FE6_fPs.js} +1 -1
- package/client/dist/assets/{index-DyCdQP10.js → index-BQC95Boo.js} +4 -4
- package/client/dist/index.html +1 -1
- package/client/lib/lib/assessment/coreTypes.d.ts +37 -0
- package/client/lib/lib/assessment/coreTypes.d.ts.map +1 -1
- package/client/lib/lib/assessment/resultTypes.d.ts +26 -1
- package/client/lib/lib/assessment/resultTypes.d.ts.map +1 -1
- package/client/lib/lib/securityPatterns/advancedExploitPatterns.d.ts +13 -0
- package/client/lib/lib/securityPatterns/advancedExploitPatterns.d.ts.map +1 -0
- package/client/lib/lib/securityPatterns/advancedExploitPatterns.js +504 -0
- package/client/lib/lib/securityPatterns/authSessionPatterns.d.ts +12 -0
- package/client/lib/lib/securityPatterns/authSessionPatterns.d.ts.map +1 -0
- package/client/lib/lib/securityPatterns/authSessionPatterns.js +357 -0
- package/client/lib/lib/securityPatterns/index.d.ts +18 -0
- package/client/lib/lib/securityPatterns/index.d.ts.map +1 -0
- package/client/lib/lib/securityPatterns/index.js +18 -0
- package/client/lib/lib/securityPatterns/injectionPatterns.d.ts +13 -0
- package/client/lib/lib/securityPatterns/injectionPatterns.d.ts.map +1 -0
- package/client/lib/lib/securityPatterns/injectionPatterns.js +356 -0
- package/client/lib/lib/securityPatterns/resourceExhaustionPatterns.d.ts +12 -0
- package/client/lib/lib/securityPatterns/resourceExhaustionPatterns.d.ts.map +1 -0
- package/client/lib/lib/securityPatterns/resourceExhaustionPatterns.js +215 -0
- package/client/lib/lib/securityPatterns/toolSpecificPatterns.d.ts +13 -0
- package/client/lib/lib/securityPatterns/toolSpecificPatterns.d.ts.map +1 -0
- package/client/lib/lib/securityPatterns/toolSpecificPatterns.js +373 -0
- package/client/lib/lib/securityPatterns/types.d.ts +20 -0
- package/client/lib/lib/securityPatterns/types.d.ts.map +1 -0
- package/client/lib/lib/securityPatterns/types.js +6 -0
- package/client/lib/lib/securityPatterns/utils.d.ts +56 -0
- package/client/lib/lib/securityPatterns/utils.d.ts.map +1 -0
- package/client/lib/lib/securityPatterns/utils.js +96 -0
- package/client/lib/lib/securityPatterns/validationPatterns.d.ts +13 -0
- package/client/lib/lib/securityPatterns/validationPatterns.d.ts.map +1 -0
- package/client/lib/lib/securityPatterns/validationPatterns.js +110 -0
- package/client/lib/lib/securityPatterns.d.ts +18 -69
- package/client/lib/lib/securityPatterns.d.ts.map +1 -1
- package/client/lib/lib/securityPatterns.js +18 -1946
- package/client/lib/services/assessment/AssessmentOrchestrator.d.ts +4 -1
- package/client/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -1
- package/client/lib/services/assessment/helpers/ExternalAPIDependencyDetector.d.ts +96 -5
- package/client/lib/services/assessment/helpers/ExternalAPIDependencyDetector.d.ts.map +1 -1
- package/client/lib/services/assessment/helpers/ExternalAPIDependencyDetector.js +202 -16
- package/client/lib/services/assessment/helpers/StdioTransportDetector.d.ts +137 -0
- package/client/lib/services/assessment/helpers/StdioTransportDetector.d.ts.map +1 -0
- package/client/lib/services/assessment/helpers/StdioTransportDetector.js +315 -0
- package/client/lib/services/assessment/helpers/ToolAnnotationExtractor.d.ts +34 -0
- package/client/lib/services/assessment/helpers/ToolAnnotationExtractor.d.ts.map +1 -0
- package/client/lib/services/assessment/helpers/ToolAnnotationExtractor.js +85 -0
- package/client/lib/services/assessment/modules/ErrorHandlingAssessor.d.ts +17 -0
- package/client/lib/services/assessment/modules/ErrorHandlingAssessor.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/ErrorHandlingAssessor.js +162 -10
- package/client/lib/services/assessment/modules/ProtocolComplianceAssessor.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/ProtocolComplianceAssessor.js +30 -0
- package/client/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/SecurityAssessor.js +6 -0
- package/client/lib/services/assessment/modules/securityTests/AnnotationAwareSeverity.d.ts +55 -0
- package/client/lib/services/assessment/modules/securityTests/AnnotationAwareSeverity.d.ts.map +1 -0
- package/client/lib/services/assessment/modules/securityTests/AnnotationAwareSeverity.js +135 -0
- package/client/lib/services/assessment/modules/securityTests/SafeResponseDetector.d.ts +6 -0
- package/client/lib/services/assessment/modules/securityTests/SafeResponseDetector.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/securityTests/SafeResponseDetector.js +9 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts +20 -0
- package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.js +37 -0
- package/client/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts +11 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityPayloadTester.js +26 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +1 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +10 -1
- package/client/lib/services/assessment/modules/securityTests/index.d.ts +1 -0
- package/client/lib/services/assessment/modules/securityTests/index.d.ts.map +1 -1
- package/client/lib/services/assessment/modules/securityTests/index.js +1 -0
- package/client/package.json +1 -1
- package/package.json +1 -1
- package/server/package.json +1 -1
|
@@ -365,11 +365,15 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
365
365
|
}
|
|
366
366
|
async testInvalidValues(tool, callTool, isExternalAPI = false) {
|
|
367
367
|
const schema = this.getToolSchema(tool);
|
|
368
|
-
|
|
368
|
+
// Issue #173: Destructure metadata from new return type
|
|
369
|
+
const { params: testInput, testedParameter, parameterIsRequired, } = this.generateInvalidValueParams(schema);
|
|
369
370
|
try {
|
|
370
371
|
const response = await this.executeWithTimeout(callTool(tool.name, testInput), 5000);
|
|
371
372
|
const isError = this.isErrorResponse(response);
|
|
372
373
|
const errorInfo = this.extractErrorInfo(response);
|
|
374
|
+
const responseText = this.extractResponseTextSafe(response);
|
|
375
|
+
// Issue #173: Detect suggestions in response
|
|
376
|
+
const { hasSuggestions, suggestions } = this.detectSuggestionPatterns(responseText);
|
|
373
377
|
// Issue #168: For external API tools, check if error is an external service error
|
|
374
378
|
if (isExternalAPI && isError && this.isExternalServiceError(errorInfo)) {
|
|
375
379
|
return {
|
|
@@ -385,6 +389,11 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
385
389
|
},
|
|
386
390
|
passed: true,
|
|
387
391
|
reason: "External API service error (validation cannot be tested when service unavailable)",
|
|
392
|
+
// Issue #173 metadata
|
|
393
|
+
testedParameter,
|
|
394
|
+
parameterIsRequired,
|
|
395
|
+
hasSuggestions,
|
|
396
|
+
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
388
397
|
};
|
|
389
398
|
}
|
|
390
399
|
// For invalid values, any error response is good
|
|
@@ -402,6 +411,11 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
402
411
|
},
|
|
403
412
|
passed: isError,
|
|
404
413
|
reason: isError ? undefined : "Tool accepted invalid values",
|
|
414
|
+
// Issue #173 metadata
|
|
415
|
+
testedParameter,
|
|
416
|
+
parameterIsRequired,
|
|
417
|
+
hasSuggestions,
|
|
418
|
+
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
405
419
|
};
|
|
406
420
|
}
|
|
407
421
|
catch (error) {
|
|
@@ -422,6 +436,9 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
422
436
|
passed: false,
|
|
423
437
|
reason: "Connection error - unable to test",
|
|
424
438
|
isConnectionError: true,
|
|
439
|
+
// Issue #173 metadata
|
|
440
|
+
testedParameter,
|
|
441
|
+
parameterIsRequired,
|
|
425
442
|
};
|
|
426
443
|
}
|
|
427
444
|
// Check if the error message is meaningful (not just a generic crash)
|
|
@@ -434,6 +451,8 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
434
451
|
messageLower.includes("validation") ||
|
|
435
452
|
messageLower.includes("error");
|
|
436
453
|
// Removed: (errorInfo.message?.length ?? 0) > 15 - this was causing false positives
|
|
454
|
+
// Issue #173: Detect suggestions in error message
|
|
455
|
+
const { hasSuggestions, suggestions } = this.detectSuggestionPatterns(messageLower);
|
|
437
456
|
return {
|
|
438
457
|
toolName: tool.name,
|
|
439
458
|
testType: "invalid_values",
|
|
@@ -447,6 +466,11 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
447
466
|
},
|
|
448
467
|
passed: isMeaningfulError,
|
|
449
468
|
reason: isMeaningfulError ? undefined : "Generic unhandled exception",
|
|
469
|
+
// Issue #173 metadata
|
|
470
|
+
testedParameter,
|
|
471
|
+
parameterIsRequired,
|
|
472
|
+
hasSuggestions,
|
|
473
|
+
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
450
474
|
};
|
|
451
475
|
}
|
|
452
476
|
}
|
|
@@ -569,11 +593,26 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
569
593
|
}
|
|
570
594
|
return params;
|
|
571
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Issue #173: Return type for generateInvalidValueParams with metadata
|
|
598
|
+
* Tracks which parameter is being tested and whether it's required
|
|
599
|
+
*/
|
|
572
600
|
generateInvalidValueParams(schema) {
|
|
573
601
|
const params = {};
|
|
574
|
-
|
|
575
|
-
|
|
602
|
+
let testedParameter = "value";
|
|
603
|
+
let parameterIsRequired = false;
|
|
604
|
+
if (!schema?.properties) {
|
|
605
|
+
return { params: { value: null }, testedParameter, parameterIsRequired };
|
|
606
|
+
}
|
|
607
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
608
|
+
let firstParamSet = false;
|
|
576
609
|
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
610
|
+
// Track the first parameter being tested (for contextual scoring)
|
|
611
|
+
if (!firstParamSet) {
|
|
612
|
+
testedParameter = key;
|
|
613
|
+
parameterIsRequired = requiredSet.has(key);
|
|
614
|
+
firstParamSet = true;
|
|
615
|
+
}
|
|
577
616
|
if (prop.type === "string") {
|
|
578
617
|
if (prop.enum) {
|
|
579
618
|
params[key] = "not_in_enum"; // Value not in enum
|
|
@@ -600,7 +639,7 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
600
639
|
}
|
|
601
640
|
}
|
|
602
641
|
}
|
|
603
|
-
return params;
|
|
642
|
+
return { params, testedParameter, parameterIsRequired };
|
|
604
643
|
}
|
|
605
644
|
generateParamsWithValue(tool, value) {
|
|
606
645
|
const schema = this.getToolSchema(tool);
|
|
@@ -623,11 +662,13 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
623
662
|
/**
|
|
624
663
|
* Analyze invalid_values response to determine scoring impact
|
|
625
664
|
* Issue #99: Contextual empty string validation scoring
|
|
665
|
+
* Issue #173: Bonus points for suggestions and graceful degradation
|
|
626
666
|
*
|
|
627
667
|
* Classifications:
|
|
628
668
|
* - safe_rejection: Tool rejected with error (no penalty)
|
|
629
669
|
* - safe_reflection: Tool stored/echoed without executing (no penalty)
|
|
630
670
|
* - defensive_programming: Tool handled gracefully (no penalty)
|
|
671
|
+
* - graceful_degradation: Optional param handled with neutral response (no penalty + bonus)
|
|
631
672
|
* - execution_detected: Tool executed input (penalty)
|
|
632
673
|
* - unknown: Cannot determine (partial penalty)
|
|
633
674
|
*/
|
|
@@ -635,14 +676,30 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
635
676
|
const responseText = this.extractResponseTextSafe(test.actualResponse.rawResponse);
|
|
636
677
|
// Case 1: Tool rejected with error - best case (no penalty)
|
|
637
678
|
if (test.actualResponse.isError) {
|
|
679
|
+
// Issue #173: Check for suggestions bonus
|
|
680
|
+
const suggestionBonus = test.hasSuggestions ? 10 : 0;
|
|
638
681
|
return {
|
|
639
682
|
shouldPenalize: false,
|
|
640
683
|
penaltyAmount: 0,
|
|
641
684
|
classification: "safe_rejection",
|
|
642
685
|
reason: "Tool properly rejected invalid input",
|
|
686
|
+
bonusPoints: suggestionBonus,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
// Issue #173 Case 2: Graceful degradation for OPTIONAL parameters
|
|
690
|
+
// If the parameter is optional and the response is neutral (empty results),
|
|
691
|
+
// this is valid graceful degradation behavior, not a failure
|
|
692
|
+
if (test.parameterIsRequired === false &&
|
|
693
|
+
this.isNeutralGracefulResponse(responseText)) {
|
|
694
|
+
return {
|
|
695
|
+
shouldPenalize: false,
|
|
696
|
+
penaltyAmount: 0,
|
|
697
|
+
classification: "graceful_degradation",
|
|
698
|
+
reason: "Tool handled optional empty parameter gracefully (valid behavior)",
|
|
699
|
+
bonusPoints: 15, // Graceful degradation bonus
|
|
643
700
|
};
|
|
644
701
|
}
|
|
645
|
-
// Case
|
|
702
|
+
// Case 3: Defensive programming patterns (no penalty)
|
|
646
703
|
// Check BEFORE execution detection because patterns like "query returned 0"
|
|
647
704
|
// might match execution indicators but are actually safe
|
|
648
705
|
if (this.isDefensiveProgrammingResponse(responseText)) {
|
|
@@ -651,18 +708,20 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
651
708
|
penaltyAmount: 0,
|
|
652
709
|
classification: "defensive_programming",
|
|
653
710
|
reason: "Tool handled empty input defensively",
|
|
711
|
+
bonusPoints: 0,
|
|
654
712
|
};
|
|
655
713
|
}
|
|
656
|
-
// Case
|
|
714
|
+
// Case 4: Safe reflection patterns (no penalty)
|
|
657
715
|
if (this.safeResponseDetector.isReflectionResponse(responseText)) {
|
|
658
716
|
return {
|
|
659
717
|
shouldPenalize: false,
|
|
660
718
|
penaltyAmount: 0,
|
|
661
719
|
classification: "safe_reflection",
|
|
662
720
|
reason: "Tool safely reflected input without execution",
|
|
721
|
+
bonusPoints: 0,
|
|
663
722
|
};
|
|
664
723
|
}
|
|
665
|
-
// Case
|
|
724
|
+
// Case 5: Check for execution evidence - VULNERABLE (full penalty)
|
|
666
725
|
if (this.executionDetector.hasExecutionEvidence(responseText) ||
|
|
667
726
|
this.executionDetector.detectExecutionArtifacts(responseText)) {
|
|
668
727
|
return {
|
|
@@ -670,14 +729,16 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
670
729
|
penaltyAmount: 100,
|
|
671
730
|
classification: "execution_detected",
|
|
672
731
|
reason: "Tool executed input without validation",
|
|
732
|
+
bonusPoints: 0,
|
|
673
733
|
};
|
|
674
734
|
}
|
|
675
|
-
// Case
|
|
735
|
+
// Case 6: Unknown - partial penalty for manual review
|
|
676
736
|
return {
|
|
677
737
|
shouldPenalize: true,
|
|
678
738
|
penaltyAmount: 25,
|
|
679
739
|
classification: "unknown",
|
|
680
740
|
reason: "Unable to determine safety - manual review recommended",
|
|
741
|
+
bonusPoints: 0,
|
|
681
742
|
};
|
|
682
743
|
}
|
|
683
744
|
/**
|
|
@@ -715,10 +776,76 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
715
776
|
];
|
|
716
777
|
return patterns.some((p) => p.test(responseText));
|
|
717
778
|
}
|
|
779
|
+
/**
|
|
780
|
+
* Issue #173: Detect helpful suggestion patterns in error responses
|
|
781
|
+
* Patterns like: "Did you mean: Button, Checkbox?"
|
|
782
|
+
* Returns extracted suggestions for bonus scoring
|
|
783
|
+
*/
|
|
784
|
+
detectSuggestionPatterns(responseText) {
|
|
785
|
+
// Issue #173: ReDoS protection - limit input length before regex matching
|
|
786
|
+
const truncatedText = responseText.slice(0, 2000);
|
|
787
|
+
// Issue #173: Bonus points - see docs/ASSESSMENT_CATALOG.md for scoring table
|
|
788
|
+
// Suggestions: +10 points for helpful error messages like "Did you mean: X?"
|
|
789
|
+
const suggestionPatterns = [
|
|
790
|
+
/did\s+you\s+mean[:\s]+([^?.]+)/i,
|
|
791
|
+
/perhaps\s+you\s+meant[:\s]+([^?.]+)/i,
|
|
792
|
+
/similar\s+to[:\s]+([^?.]+)/i,
|
|
793
|
+
/suggestions?[:\s]+([^?.]+)/i,
|
|
794
|
+
/valid\s+(options?|values?)[:\s]+([^?.]+)/i,
|
|
795
|
+
/available[:\s]+([^?.]+)/i,
|
|
796
|
+
/\btry[:\s]+([^?.]+)/i,
|
|
797
|
+
/expected\s+one\s+of[:\s]+([^?.]+)/i,
|
|
798
|
+
];
|
|
799
|
+
for (const pattern of suggestionPatterns) {
|
|
800
|
+
const match = truncatedText.match(pattern);
|
|
801
|
+
if (match) {
|
|
802
|
+
// Get the captured group (last non-undefined group)
|
|
803
|
+
const suggestionText = match[match.length - 1] || match[1] || "";
|
|
804
|
+
const suggestions = suggestionText
|
|
805
|
+
.split(/[,;]/)
|
|
806
|
+
.map((s) => s.trim())
|
|
807
|
+
.filter((s) => s.length > 0 && s.length < 50);
|
|
808
|
+
if (suggestions.length > 0) {
|
|
809
|
+
return { hasSuggestions: true, suggestions };
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return { hasSuggestions: false, suggestions: [] };
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Issue #173: Check for neutral/graceful responses on optional parameters
|
|
817
|
+
* These indicate the tool handled empty/missing optional input appropriately
|
|
818
|
+
*/
|
|
819
|
+
isNeutralGracefulResponse(responseText) {
|
|
820
|
+
// Issue #173: ReDoS protection - limit input length before regex matching
|
|
821
|
+
const truncatedText = responseText.slice(0, 2000);
|
|
822
|
+
const gracefulPatterns = [
|
|
823
|
+
/^\s*\[\s*\]\s*$/, // Empty JSON array (standalone)
|
|
824
|
+
/^\s*\{\s*\}\s*$/, // Empty JSON object (standalone)
|
|
825
|
+
/^\s*$/, // Empty/whitespace only response
|
|
826
|
+
/no\s+results?\s*(found)?/i, // "No results" / "No results found"
|
|
827
|
+
/^results?:\s*\[\s*\]/i, // "results: []"
|
|
828
|
+
/returned\s+0\s+/i, // "returned 0 items"
|
|
829
|
+
/found\s+0\s+/i, // "found 0 matches"
|
|
830
|
+
/empty\s+list/i, // "empty list"
|
|
831
|
+
/no\s+matching/i, // "no matching items"
|
|
832
|
+
/default\s+value/i, // "using default value"
|
|
833
|
+
/^null$/i, // Explicit null
|
|
834
|
+
/no\s+data/i, // "no data"
|
|
835
|
+
/"results"\s*:\s*\[\s*\]/, // JSON with empty results array
|
|
836
|
+
/"items"\s*:\s*\[\s*\]/, // JSON with empty items array
|
|
837
|
+
/"data"\s*:\s*\[\s*\]/, // JSON with empty data array
|
|
838
|
+
];
|
|
839
|
+
return gracefulPatterns.some((pattern) => pattern.test(truncatedText));
|
|
840
|
+
}
|
|
718
841
|
calculateMetrics(tests, _passed) {
|
|
719
842
|
// Calculate enhanced score with bonus points for quality
|
|
720
843
|
let enhancedScore = 0;
|
|
721
844
|
let maxPossibleScore = 0;
|
|
845
|
+
// Issue #173: Track graceful degradation and suggestion metrics
|
|
846
|
+
let gracefulDegradationCount = 0;
|
|
847
|
+
let suggestionCount = 0;
|
|
848
|
+
let suggestionBonusPoints = 0;
|
|
722
849
|
tests.forEach((test) => {
|
|
723
850
|
// Issue #99: Contextual scoring for invalid_values tests
|
|
724
851
|
// Instead of blanket exclusion, analyze response patterns to determine if
|
|
@@ -726,9 +853,23 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
726
853
|
// or if it executed without validation (security concern).
|
|
727
854
|
if (test.testType === "invalid_values") {
|
|
728
855
|
const analysis = this.analyzeInvalidValuesResponse(test);
|
|
856
|
+
// Issue #173: Track graceful degradation
|
|
857
|
+
if (analysis.classification === "graceful_degradation") {
|
|
858
|
+
gracefulDegradationCount++;
|
|
859
|
+
}
|
|
860
|
+
// Issue #173: Track suggestions
|
|
861
|
+
if (test.hasSuggestions) {
|
|
862
|
+
suggestionCount++;
|
|
863
|
+
}
|
|
864
|
+
// Issue #173: Apply bonus points for graceful handling and suggestions
|
|
865
|
+
if (analysis.bonusPoints > 0) {
|
|
866
|
+
enhancedScore += analysis.bonusPoints;
|
|
867
|
+
maxPossibleScore += analysis.bonusPoints;
|
|
868
|
+
suggestionBonusPoints += analysis.bonusPoints;
|
|
869
|
+
}
|
|
729
870
|
if (!analysis.shouldPenalize) {
|
|
730
|
-
// Safe response (rejection, reflection,
|
|
731
|
-
// Skip scoring to preserve backward compatibility for well-behaved tools
|
|
871
|
+
// Safe response (rejection, reflection, defensive programming, graceful degradation)
|
|
872
|
+
// Skip base scoring to preserve backward compatibility for well-behaved tools
|
|
732
873
|
return;
|
|
733
874
|
}
|
|
734
875
|
// Execution detected or unknown - include in scoring with penalty
|
|
@@ -756,6 +897,13 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
756
897
|
enhancedScore += 5;
|
|
757
898
|
maxPossibleScore += 5;
|
|
758
899
|
}
|
|
900
|
+
// Issue #173: Extra points for suggestions in other test types
|
|
901
|
+
if (test.hasSuggestions) {
|
|
902
|
+
suggestionCount++;
|
|
903
|
+
enhancedScore += 10;
|
|
904
|
+
maxPossibleScore += 10;
|
|
905
|
+
suggestionBonusPoints += 10;
|
|
906
|
+
}
|
|
759
907
|
}
|
|
760
908
|
});
|
|
761
909
|
const score = maxPossibleScore > 0 ? (enhancedScore / maxPossibleScore) * 100 : 0;
|
|
@@ -796,6 +944,10 @@ export class ErrorHandlingAssessor extends BaseAssessor {
|
|
|
796
944
|
hasDescriptiveMessages,
|
|
797
945
|
validatesInputs,
|
|
798
946
|
testDetails: tests,
|
|
947
|
+
// Issue #173: Graceful degradation and suggestion metrics
|
|
948
|
+
gracefulDegradationCount,
|
|
949
|
+
suggestionCount,
|
|
950
|
+
suggestionBonusPoints,
|
|
799
951
|
};
|
|
800
952
|
}
|
|
801
953
|
determineErrorHandlingStatus(metrics, testCount) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProtocolComplianceAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/ProtocolComplianceAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,2BAA2B,EAM3B,uBAAuB,EAMxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAOpE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAmB9D;;;GAGG;AACH,MAAM,WAAW,4BAA6B,SAAQ,2BAA2B;IAC/E,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE;QAClB,mBAAmB,EAAE,aAAa,CAAC;QACnC,kBAAkB,EAAE,aAAa,CAAC;QAClC,uBAAuB,EAAE,aAAa,CAAC;KACxC,CAAC;CACH;AAED,qBAAa,0BAA2B,SAAQ,YAAY,CAAC,4BAA4B,CAAC;IACxF,OAAO,CAAC,GAAG,CAAc;gBAEb,MAAM,EAAE,uBAAuB;IAK3C;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;;OAGG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,4BAA4B,CAAC;IAyIxC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAyB9B;;OAEG;YACW,sBAAsB;IAuBpC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAsB/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAwC7B;;OAEG;YACW,mBAAmB;IAiCjC;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAiDnC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAkEnC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAS7B;;OAEG;YACW,wBAAwB;IA4GtC;;OAEG;YACW,uBAAuB;IA2FrC;;OAEG;YACW,4BAA4B;IAoD1C,OAAO,CAAC,yBAAyB;
|
|
1
|
+
{"version":3,"file":"ProtocolComplianceAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/ProtocolComplianceAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,2BAA2B,EAM3B,uBAAuB,EAMxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAOpE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAmB9D;;;GAGG;AACH,MAAM,WAAW,4BAA6B,SAAQ,2BAA2B;IAC/E,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE;QAClB,mBAAmB,EAAE,aAAa,CAAC;QACnC,kBAAkB,EAAE,aAAa,CAAC;QAClC,uBAAuB,EAAE,aAAa,CAAC;KACxC,CAAC;CACH;AAED,qBAAa,0BAA2B,SAAQ,YAAY,CAAC,4BAA4B,CAAC;IACxF,OAAO,CAAC,GAAG,CAAc;gBAEb,MAAM,EAAE,uBAAuB;IAK3C;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;;OAGG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,4BAA4B,CAAC;IAyIxC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAyB9B;;OAEG;YACW,sBAAsB;IAuBpC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAsB/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAwC7B;;OAEG;YACW,mBAAmB;IAiCjC;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAiDnC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAkEnC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAS7B;;OAEG;YACW,wBAAwB;IA4GtC;;OAEG;YACW,uBAAuB;IA2FrC;;OAEG;YACW,4BAA4B;IAoD1C,OAAO,CAAC,yBAAyB;IAwGjC,OAAO,CAAC,uBAAuB;IAqB/B,OAAO,CAAC,sBAAsB;IA0B9B,OAAO,CAAC,qBAAqB;IAgC7B,OAAO,CAAC,oBAAoB;IA8E5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoC3B;;OAEG;IACH,OAAO,CAAC,uBAAuB;CAqEhC"}
|
|
@@ -587,6 +587,36 @@ export class ProtocolComplianceAssessor extends BaseAssessor {
|
|
|
587
587
|
// Legacy compatibility methods (from MCPSpecComplianceAssessor)
|
|
588
588
|
// ============================================================================
|
|
589
589
|
assessTransportCompliance(context) {
|
|
590
|
+
// Issue #172: Check source-based transport detection first
|
|
591
|
+
// This fixes incorrect FAIL for valid stdio servers without serverInfo metadata
|
|
592
|
+
if (context.transportDetection?.supportsStdio) {
|
|
593
|
+
return {
|
|
594
|
+
supportsStreamableHTTP: context.transportDetection.supportsHTTP,
|
|
595
|
+
deprecatedSSE: context.transportDetection.supportsSSE,
|
|
596
|
+
transportValidation: "passed",
|
|
597
|
+
supportsStdio: true,
|
|
598
|
+
supportsSSE: context.transportDetection.supportsSSE,
|
|
599
|
+
confidence: context.transportDetection.confidence,
|
|
600
|
+
detectionMethod: "source-code-analysis",
|
|
601
|
+
requiresManualCheck: false,
|
|
602
|
+
transportEvidence: context.transportDetection.evidence.map((e) => `${e.source}: ${e.detail}`),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
// Also check HTTP-only detection (no serverInfo but HTTP transport detected)
|
|
606
|
+
if (context.transportDetection?.supportsHTTP &&
|
|
607
|
+
!context.transportDetection?.supportsStdio) {
|
|
608
|
+
return {
|
|
609
|
+
supportsStreamableHTTP: true,
|
|
610
|
+
deprecatedSSE: context.transportDetection.supportsSSE,
|
|
611
|
+
transportValidation: "passed",
|
|
612
|
+
supportsStdio: false,
|
|
613
|
+
supportsSSE: context.transportDetection.supportsSSE,
|
|
614
|
+
confidence: context.transportDetection.confidence,
|
|
615
|
+
detectionMethod: "source-code-analysis",
|
|
616
|
+
requiresManualCheck: false,
|
|
617
|
+
transportEvidence: context.transportDetection.evidence.map((e) => `${e.source}: ${e.detail}`),
|
|
618
|
+
};
|
|
619
|
+
}
|
|
590
620
|
if (!context.serverInfo) {
|
|
591
621
|
return {
|
|
592
622
|
supportsStreamableHTTP: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/SecurityAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,kBAAkB,EAInB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAiB9D,OAAO,EACL,gBAAgB,EAGjB,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,YAAY,CAAiC;IAErD;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI;IAStD;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAOjC;;;OAGG;YACW,0BAA0B;gBAwBtC,MAAM,EAAE,OAAO,8BAA8B,EAAE,uBAAuB;IAwClE,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"SecurityAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/SecurityAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,kBAAkB,EAInB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAiB9D,OAAO,EACL,gBAAgB,EAGjB,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,YAAY,CAAiC;IAErD;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI;IAStD;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAOjC;;;OAGG;YACW,0BAA0B;gBAwBtC,MAAM,EAAE,OAAO,8BAA8B,EAAE,uBAAuB;IAwClE,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAyQrE;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoC7B;;OAEG;YACW,+BAA+B;IAiC7C;;;OAGG;YACW,yBAAyB;IA0CvC;;;;;;;OAOG;YACW,yBAAyB;IAmFvC;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAYjC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA0B/B;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAkEnC;;;OAGG;IACH,OAAO,CAAC,0BAA0B;CAgDnC"}
|
|
@@ -101,6 +101,12 @@ export class SecurityAssessor extends BaseAssessor {
|
|
|
101
101
|
async assess(context) {
|
|
102
102
|
// Select tools for testing first
|
|
103
103
|
const toolsToTest = this.selectToolsForTesting(context.tools);
|
|
104
|
+
// Issue #170: Set tool annotations context for severity adjustment
|
|
105
|
+
// This enables annotation-aware false positive reduction for read-only servers
|
|
106
|
+
if (!context.toolAnnotationsContext) {
|
|
107
|
+
this.logger.warn("No tool annotations context provided - severity adjustment disabled");
|
|
108
|
+
}
|
|
109
|
+
this.payloadTester.setToolAnnotationsContext(context.toolAnnotationsContext);
|
|
104
110
|
// Run universal security testing via extracted payload tester
|
|
105
111
|
const allTests = await this.payloadTester.runUniversalSecurityTests(toolsToTest, context.callTool, context.onProgress);
|
|
106
112
|
// Separate connection errors from valid tests
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annotation-Aware Severity Adjustment
|
|
3
|
+
*
|
|
4
|
+
* Reduces false positives by considering tool annotations when scoring
|
|
5
|
+
* vulnerability severity.
|
|
6
|
+
*
|
|
7
|
+
* Issue #170: Security module should consider tool annotations to reduce
|
|
8
|
+
* false positives for read-only servers.
|
|
9
|
+
*
|
|
10
|
+
* @module securityTests/AnnotationAwareSeverity
|
|
11
|
+
*/
|
|
12
|
+
import type { SecurityAnnotations, SecurityRiskLevel } from "../../../../lib/assessment/coreTypes.js";
|
|
13
|
+
/**
|
|
14
|
+
* Result of annotation-aware severity adjustment.
|
|
15
|
+
*/
|
|
16
|
+
export interface SeverityAdjustment {
|
|
17
|
+
/** Adjusted risk level after considering annotations */
|
|
18
|
+
adjustedRiskLevel: SecurityRiskLevel;
|
|
19
|
+
/** Whether an adjustment was made */
|
|
20
|
+
wasAdjusted: boolean;
|
|
21
|
+
/** Reason for adjustment (human-readable) */
|
|
22
|
+
adjustmentReason?: string;
|
|
23
|
+
/** Original risk level before adjustment */
|
|
24
|
+
originalRiskLevel: SecurityRiskLevel;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Adjust vulnerability severity based on tool annotations.
|
|
28
|
+
*
|
|
29
|
+
* This function implements the false positive reduction logic from Issue #170.
|
|
30
|
+
* Read-only tools (readOnlyHint=true) have execution-type vulnerabilities
|
|
31
|
+
* downgraded to LOW, and closed-world tools (openWorldHint=false) have
|
|
32
|
+
* exfiltration-type vulnerabilities downgraded to LOW.
|
|
33
|
+
*
|
|
34
|
+
* @param attackName - Name of the attack pattern (e.g., "Command Injection")
|
|
35
|
+
* @param originalRiskLevel - Original risk level from payload definition
|
|
36
|
+
* @param toolAnnotations - Extracted annotations for this specific tool
|
|
37
|
+
* @param serverIsReadOnly - Whether ALL server tools are read-only
|
|
38
|
+
* @param serverIsClosed - Whether ALL server tools are closed-world
|
|
39
|
+
* @returns SeverityAdjustment with potentially adjusted risk level
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const adjustment = adjustSeverityForAnnotations(
|
|
44
|
+
* "Command Injection",
|
|
45
|
+
* "HIGH",
|
|
46
|
+
* { readOnlyHint: true, source: "mcp" },
|
|
47
|
+
* true,
|
|
48
|
+
* false
|
|
49
|
+
* );
|
|
50
|
+
* // adjustment.wasAdjusted === true
|
|
51
|
+
* // adjustment.adjustedRiskLevel === "LOW"
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function adjustSeverityForAnnotations(attackName: string, originalRiskLevel: SecurityRiskLevel, toolAnnotations: SecurityAnnotations | undefined, serverIsReadOnly: boolean, serverIsClosed: boolean): SeverityAdjustment;
|
|
55
|
+
//# sourceMappingURL=AnnotationAwareSeverity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnnotationAwareSeverity.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/securityTests/AnnotationAwareSeverity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,4BAA4B,CAAC;AA+BpC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,wDAAwD;IACxD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,qCAAqC;IACrC,WAAW,EAAE,OAAO,CAAC;IACrB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,iBAAiB,EAAE,iBAAiB,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,iBAAiB,EACpC,eAAe,EAAE,mBAAmB,GAAG,SAAS,EAChD,gBAAgB,EAAE,OAAO,EACzB,cAAc,EAAE,OAAO,GACtB,kBAAkB,CA0DpB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annotation-Aware Severity Adjustment
|
|
3
|
+
*
|
|
4
|
+
* Reduces false positives by considering tool annotations when scoring
|
|
5
|
+
* vulnerability severity.
|
|
6
|
+
*
|
|
7
|
+
* Issue #170: Security module should consider tool annotations to reduce
|
|
8
|
+
* false positives for read-only servers.
|
|
9
|
+
*
|
|
10
|
+
* @module securityTests/AnnotationAwareSeverity
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Attack patterns that should be downgraded for read-only tools.
|
|
14
|
+
* These involve code/command execution which read-only tools cannot perform.
|
|
15
|
+
*/
|
|
16
|
+
const EXECUTION_TYPE_ATTACKS = [
|
|
17
|
+
"Command Injection", // RCE via shell commands
|
|
18
|
+
"Calculator Injection", // Code evaluation via calculator
|
|
19
|
+
"Code Execution", // Direct code execution
|
|
20
|
+
"Path Traversal", // File system modification
|
|
21
|
+
"Cross-Tool State Bypass", // State manipulation attacks
|
|
22
|
+
"Chained Exploitation", // Multi-tool execution chains
|
|
23
|
+
"Tool Output Injection", // Output tampering
|
|
24
|
+
"Nested Injection", // Recursive injection attacks
|
|
25
|
+
"Auth Bypass", // Authentication manipulation
|
|
26
|
+
"Session Management", // Session state modification
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Attack patterns that should be downgraded for closed-world tools.
|
|
30
|
+
* These involve external network access which closed-world tools don't have.
|
|
31
|
+
*/
|
|
32
|
+
const EXFILTRATION_TYPE_ATTACKS = [
|
|
33
|
+
"Indirect Prompt Injection", // External content injection
|
|
34
|
+
"Data Exfiltration", // Data leakage to external services
|
|
35
|
+
"Token Theft", // Credential exfiltration
|
|
36
|
+
"Secret Leakage", // Sensitive data exposure
|
|
37
|
+
"SSRF", // Server-side request forgery
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Adjust vulnerability severity based on tool annotations.
|
|
41
|
+
*
|
|
42
|
+
* This function implements the false positive reduction logic from Issue #170.
|
|
43
|
+
* Read-only tools (readOnlyHint=true) have execution-type vulnerabilities
|
|
44
|
+
* downgraded to LOW, and closed-world tools (openWorldHint=false) have
|
|
45
|
+
* exfiltration-type vulnerabilities downgraded to LOW.
|
|
46
|
+
*
|
|
47
|
+
* @param attackName - Name of the attack pattern (e.g., "Command Injection")
|
|
48
|
+
* @param originalRiskLevel - Original risk level from payload definition
|
|
49
|
+
* @param toolAnnotations - Extracted annotations for this specific tool
|
|
50
|
+
* @param serverIsReadOnly - Whether ALL server tools are read-only
|
|
51
|
+
* @param serverIsClosed - Whether ALL server tools are closed-world
|
|
52
|
+
* @returns SeverityAdjustment with potentially adjusted risk level
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const adjustment = adjustSeverityForAnnotations(
|
|
57
|
+
* "Command Injection",
|
|
58
|
+
* "HIGH",
|
|
59
|
+
* { readOnlyHint: true, source: "mcp" },
|
|
60
|
+
* true,
|
|
61
|
+
* false
|
|
62
|
+
* );
|
|
63
|
+
* // adjustment.wasAdjusted === true
|
|
64
|
+
* // adjustment.adjustedRiskLevel === "LOW"
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function adjustSeverityForAnnotations(attackName, originalRiskLevel, toolAnnotations, serverIsReadOnly, serverIsClosed) {
|
|
68
|
+
// Check if we have valid per-tool annotations
|
|
69
|
+
const hasValidAnnotations = toolAnnotations && toolAnnotations.source !== "none";
|
|
70
|
+
// Check 1: Per-tool read-only for execution-type attacks
|
|
71
|
+
// If tool declares readOnlyHint=true, it cannot execute commands
|
|
72
|
+
if (hasValidAnnotations && toolAnnotations.readOnlyHint === true) {
|
|
73
|
+
if (isExecutionTypeAttack(attackName)) {
|
|
74
|
+
return {
|
|
75
|
+
adjustedRiskLevel: "LOW",
|
|
76
|
+
wasAdjusted: true,
|
|
77
|
+
adjustmentReason: `Tool has readOnlyHint=true; ${attackName} downgraded from ${originalRiskLevel} to LOW (cannot execute)`,
|
|
78
|
+
originalRiskLevel,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Check 2: Per-tool closed-world for exfiltration-type attacks
|
|
83
|
+
// If tool declares openWorldHint=false, it cannot access external resources
|
|
84
|
+
if (hasValidAnnotations && toolAnnotations.openWorldHint === false) {
|
|
85
|
+
if (isExfiltrationType(attackName)) {
|
|
86
|
+
return {
|
|
87
|
+
adjustedRiskLevel: "LOW",
|
|
88
|
+
wasAdjusted: true,
|
|
89
|
+
adjustmentReason: `Tool has openWorldHint=false; ${attackName} downgraded from ${originalRiskLevel} to LOW (no external access)`,
|
|
90
|
+
originalRiskLevel,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Check 3: Server-level read-only flag provides additional context
|
|
95
|
+
// Even if specific tool annotation is missing, server-level flag applies
|
|
96
|
+
if (serverIsReadOnly && isExecutionTypeAttack(attackName)) {
|
|
97
|
+
return {
|
|
98
|
+
adjustedRiskLevel: "LOW",
|
|
99
|
+
wasAdjusted: true,
|
|
100
|
+
adjustmentReason: `Server is 100% read-only; ${attackName} downgraded from ${originalRiskLevel} to LOW`,
|
|
101
|
+
originalRiskLevel,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Check 4: Server-level closed flag
|
|
105
|
+
if (serverIsClosed && isExfiltrationType(attackName)) {
|
|
106
|
+
return {
|
|
107
|
+
adjustedRiskLevel: "LOW",
|
|
108
|
+
wasAdjusted: true,
|
|
109
|
+
adjustmentReason: `Server is 100% closed-world; ${attackName} downgraded from ${originalRiskLevel} to LOW`,
|
|
110
|
+
originalRiskLevel,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// No adjustment needed
|
|
114
|
+
return {
|
|
115
|
+
adjustedRiskLevel: originalRiskLevel,
|
|
116
|
+
wasAdjusted: false,
|
|
117
|
+
originalRiskLevel,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check if attack name matches execution-type patterns.
|
|
122
|
+
* Only checks if attackName contains the pattern (not bidirectional)
|
|
123
|
+
* to prevent security bypass (e.g., "command" matching "Command Injection").
|
|
124
|
+
*/
|
|
125
|
+
function isExecutionTypeAttack(attackName) {
|
|
126
|
+
return EXECUTION_TYPE_ATTACKS.some((pattern) => attackName.toLowerCase().includes(pattern.toLowerCase()));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if attack name matches exfiltration-type patterns.
|
|
130
|
+
* Only checks if attackName contains the pattern (not bidirectional)
|
|
131
|
+
* to prevent security bypass.
|
|
132
|
+
*/
|
|
133
|
+
function isExfiltrationType(attackName) {
|
|
134
|
+
return EXFILTRATION_TYPE_ATTACKS.some((pattern) => attackName.toLowerCase().includes(pattern.toLowerCase()));
|
|
135
|
+
}
|
|
@@ -34,6 +34,12 @@ export declare class SafeResponseDetector {
|
|
|
34
34
|
* Check if response is an HTTP error (Issue #26)
|
|
35
35
|
*/
|
|
36
36
|
isHttpErrorResponse(responseText: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Check if response is an AppleScript syntax error (Issue #175)
|
|
39
|
+
* These errors should not be flagged as XXE vulnerabilities even when
|
|
40
|
+
* the XXE payload is echoed back in the error message.
|
|
41
|
+
*/
|
|
42
|
+
isAppleScriptSyntaxError(responseText: string): boolean;
|
|
37
43
|
/**
|
|
38
44
|
* Check if response is just reflection (safe)
|
|
39
45
|
* Two-layer defense: Match reflection patterns, verify NO execution evidence
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SafeResponseDetector.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/securityTests/SafeResponseDetector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"SafeResponseDetector.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/securityTests/SafeResponseDetector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AAiBjF;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,iBAAiB,CAA4B;;IAMrD;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAQzE;;OAEG;IACH,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIlD;;;;OAIG;IACH,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIvD;;;;;;OAMG;IACH,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IA8EnD;;OAEG;IACH,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIrD;;OAEG;IACH,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAIjD;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,OAAO;IA0CrE;;OAEG;IACH,sBAAsB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,MAAM;CAUtE"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Extracted from SecurityResponseAnalyzer.ts (Issue #53)
|
|
6
6
|
* Handles: MCP validation, HTTP errors, reflection detection, validation rejection
|
|
7
7
|
*/
|
|
8
|
-
import { VALIDATION_ERROR_PATTERNS, STATUS_PATTERNS, REFLECTION_PATTERNS, SEARCH_RESULT_PATTERNS, CREATION_PATTERNS, TEXT_REJECTION_PATTERNS, RESULT_REJECTION_PATTERNS, isHttpError, matchesAny, hasLLMInjectionMarkers, hasOutputInjectionVulnerability, } from "./SecurityPatternLibrary.js";
|
|
8
|
+
import { VALIDATION_ERROR_PATTERNS, STATUS_PATTERNS, REFLECTION_PATTERNS, SEARCH_RESULT_PATTERNS, CREATION_PATTERNS, TEXT_REJECTION_PATTERNS, RESULT_REJECTION_PATTERNS, isHttpError, matchesAny, hasLLMInjectionMarkers, hasOutputInjectionVulnerability, isAppleScriptSyntaxError as isAppleScriptSyntaxErrorPattern, } from "./SecurityPatternLibrary.js";
|
|
9
9
|
import { ExecutionArtifactDetector } from "./ExecutionArtifactDetector.js";
|
|
10
10
|
/**
|
|
11
11
|
* Detects safe response patterns indicating proper tool behavior
|
|
@@ -30,6 +30,14 @@ export class SafeResponseDetector {
|
|
|
30
30
|
isHttpErrorResponse(responseText) {
|
|
31
31
|
return isHttpError(responseText);
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if response is an AppleScript syntax error (Issue #175)
|
|
35
|
+
* These errors should not be flagged as XXE vulnerabilities even when
|
|
36
|
+
* the XXE payload is echoed back in the error message.
|
|
37
|
+
*/
|
|
38
|
+
isAppleScriptSyntaxError(responseText) {
|
|
39
|
+
return isAppleScriptSyntaxErrorPattern(responseText);
|
|
40
|
+
}
|
|
33
41
|
/**
|
|
34
42
|
* Check if response is just reflection (safe)
|
|
35
43
|
* Two-layer defense: Match reflection patterns, verify NO execution evidence
|
|
@@ -133,6 +133,26 @@ export declare const PERMANENT_ERROR_PATTERNS: readonly [RegExp, RegExp, RegExp,
|
|
|
133
133
|
* @returns true if error is transient and should be retried
|
|
134
134
|
*/
|
|
135
135
|
export declare function isTransientErrorPattern(text: string): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Issue #175: AppleScript syntax error patterns to exclude from XXE detection
|
|
138
|
+
*
|
|
139
|
+
* AppleScript errors can trigger false positives when:
|
|
140
|
+
* 1. The tool returns an AppleScript syntax error (e.g., -2750 duplicate parameter)
|
|
141
|
+
* 2. The XXE payload is echoed back in the error message
|
|
142
|
+
* 3. XXE evidence patterns match "parameter" + "entity" combination
|
|
143
|
+
*
|
|
144
|
+
* These patterns detect AppleScript-specific errors by:
|
|
145
|
+
* - Error code ranges (-27xx, -25xx are AppleScript domain)
|
|
146
|
+
* - AppleScript-specific syntax error messages
|
|
147
|
+
* - Common AppleScript error patterns
|
|
148
|
+
*/
|
|
149
|
+
export declare const APPLESCRIPT_SYNTAX_ERROR_PATTERNS: RegExp[];
|
|
150
|
+
/**
|
|
151
|
+
* Check if error text indicates an AppleScript syntax error (Issue #175)
|
|
152
|
+
* @param text Error message or response text
|
|
153
|
+
* @returns true if error is an AppleScript syntax error
|
|
154
|
+
*/
|
|
155
|
+
export declare function isAppleScriptSyntaxError(text: string): boolean;
|
|
136
156
|
/**
|
|
137
157
|
* Status patterns indicating safe response handling
|
|
138
158
|
* Used by: isReflectionResponse()
|