@bryan-thompson/inspector-assessment-client 1.35.2 → 1.35.3
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/lib/lib/assessment/coreTypes.d.ts +23 -0
- package/lib/lib/assessment/coreTypes.d.ts.map +1 -1
- package/lib/lib/assessment/extendedTypes.d.ts +45 -2
- package/lib/lib/assessment/extendedTypes.d.ts.map +1 -1
- package/lib/lib/assessment/jsonlEventSchemas.d.ts +4 -4
- package/lib/lib/assessment/resultTypes.d.ts +12 -1
- package/lib/lib/assessment/resultTypes.d.ts.map +1 -1
- package/lib/lib/aupPatterns.d.ts +50 -0
- package/lib/lib/aupPatterns.d.ts.map +1 -1
- package/lib/lib/aupPatterns.js +140 -0
- package/lib/lib/securityPatterns.d.ts.map +1 -1
- package/lib/lib/securityPatterns.js +92 -0
- package/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts +26 -1
- package/lib/services/assessment/modules/DeveloperExperienceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/DeveloperExperienceAssessor.js +160 -1
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts +32 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +218 -20
- package/lib/services/assessment/modules/securityTests/ConfidenceScorer.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/ConfidenceScorer.js +28 -0
- package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts +95 -0
- package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SecurityPatternLibrary.js +174 -0
- package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SecurityPayloadTester.js +15 -0
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts +40 -0
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.d.ts.map +1 -1
- package/lib/services/assessment/modules/securityTests/SecurityResponseAnalyzer.js +143 -131
- package/package.json +1 -1
|
@@ -1809,6 +1809,98 @@ export const SECURITY_ATTACK_PATTERNS = [
|
|
|
1809
1809
|
},
|
|
1810
1810
|
],
|
|
1811
1811
|
},
|
|
1812
|
+
// ==========================================
|
|
1813
|
+
// EXCESSIVE PERMISSIONS SCOPE TESTS (Issue #144, Challenge #22)
|
|
1814
|
+
// ==========================================
|
|
1815
|
+
// 32. EXCESSIVE PERMISSIONS SCOPE
|
|
1816
|
+
{
|
|
1817
|
+
attackName: "Excessive Permissions Scope",
|
|
1818
|
+
description: "Test if tools with restrictive annotations (readOnlyHint=True) allow privileged operations via specific inputs (CWE-250, CWE-269)",
|
|
1819
|
+
payloads: [
|
|
1820
|
+
// Privileged action payloads for tools that claim readOnlyHint=True
|
|
1821
|
+
{
|
|
1822
|
+
payload: "write_file",
|
|
1823
|
+
evidence: /"scope_violation"\s*:\s*true|"actual_scope"\s*:\s*"write"/i,
|
|
1824
|
+
riskLevel: "HIGH",
|
|
1825
|
+
description: "Write operation bypassing readOnlyHint=True annotation (CWE-250)",
|
|
1826
|
+
payloadType: "scope_violation",
|
|
1827
|
+
parameterTypes: ["action", "operation", "command", "cmd"],
|
|
1828
|
+
},
|
|
1829
|
+
{
|
|
1830
|
+
payload: "delete_data",
|
|
1831
|
+
evidence: /"scope_violation"\s*:\s*true|"actual_scope"\s*:\s*"delete"/i,
|
|
1832
|
+
riskLevel: "HIGH",
|
|
1833
|
+
description: "Delete operation bypassing destructiveHint=False annotation (CWE-250)",
|
|
1834
|
+
payloadType: "scope_violation",
|
|
1835
|
+
parameterTypes: ["action", "operation", "command", "cmd"],
|
|
1836
|
+
},
|
|
1837
|
+
{
|
|
1838
|
+
payload: "execute_command",
|
|
1839
|
+
evidence: /"scope_violation"\s*:\s*true|"actual_scope"\s*:\s*"execute"/i,
|
|
1840
|
+
riskLevel: "HIGH",
|
|
1841
|
+
description: "Execute operation bypassing readOnlyHint=True annotation (CWE-250)",
|
|
1842
|
+
payloadType: "scope_violation",
|
|
1843
|
+
parameterTypes: ["action", "operation", "command", "cmd"],
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
payload: "network_request",
|
|
1847
|
+
evidence: /"scope_violation"\s*:\s*true|"actual_scope"\s*:\s*"network"/i,
|
|
1848
|
+
riskLevel: "HIGH",
|
|
1849
|
+
description: "Network operation bypassing openWorldHint=False annotation (CWE-250)",
|
|
1850
|
+
payloadType: "scope_violation",
|
|
1851
|
+
parameterTypes: ["action", "operation", "command", "cmd"],
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
payload: "modify_config",
|
|
1855
|
+
evidence: /"scope_violation"\s*:\s*true|"actual_scope"\s*:\s*"modify"/i,
|
|
1856
|
+
riskLevel: "HIGH",
|
|
1857
|
+
description: "Config modification bypassing readOnlyHint=True annotation (CWE-250)",
|
|
1858
|
+
payloadType: "scope_violation",
|
|
1859
|
+
parameterTypes: ["action", "operation", "command", "cmd"],
|
|
1860
|
+
},
|
|
1861
|
+
{
|
|
1862
|
+
payload: "environment_access",
|
|
1863
|
+
evidence: /"scope_violation"\s*:\s*true|"actual_scope"\s*:\s*"environment"/i,
|
|
1864
|
+
riskLevel: "HIGH",
|
|
1865
|
+
description: "Environment access bypassing annotation restrictions (CWE-250)",
|
|
1866
|
+
payloadType: "scope_violation",
|
|
1867
|
+
parameterTypes: ["action", "operation", "command", "cmd"],
|
|
1868
|
+
},
|
|
1869
|
+
// Escalation keyword payloads for tools that claim readOnlyHint=True
|
|
1870
|
+
{
|
|
1871
|
+
payload: "show admin users",
|
|
1872
|
+
evidence: /"scope_escalation"\s*:\s*true|"privileged_data"\s*:|admin[_-]?mode[_-]?activated/i,
|
|
1873
|
+
riskLevel: "HIGH",
|
|
1874
|
+
description: "Keyword-triggered privilege escalation via 'admin' (CWE-269)",
|
|
1875
|
+
payloadType: "scope_escalation",
|
|
1876
|
+
parameterTypes: ["request", "query", "input", "text", "message"],
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
payload: "sudo access logs",
|
|
1880
|
+
evidence: /"scope_escalation"\s*:\s*true|"system_secrets"\s*:|"trigger_keyword"\s*:\s*"sudo"/i,
|
|
1881
|
+
riskLevel: "HIGH",
|
|
1882
|
+
description: "Keyword-triggered privilege escalation via 'sudo' (CWE-269)",
|
|
1883
|
+
payloadType: "scope_escalation",
|
|
1884
|
+
parameterTypes: ["request", "query", "input", "text", "message"],
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
payload: "elevate to root",
|
|
1888
|
+
evidence: /"scope_escalation"\s*:\s*true|"trigger_keyword"\s*:\s*"(elevate|root)"/i,
|
|
1889
|
+
riskLevel: "HIGH",
|
|
1890
|
+
description: "Keyword-triggered privilege escalation via 'elevate' or 'root' (CWE-269)",
|
|
1891
|
+
payloadType: "scope_escalation",
|
|
1892
|
+
parameterTypes: ["request", "query", "input", "text", "message"],
|
|
1893
|
+
},
|
|
1894
|
+
{
|
|
1895
|
+
payload: "get superuser privileges",
|
|
1896
|
+
evidence: /"scope_escalation"\s*:\s*true|"trigger_keyword"\s*:\s*"(superuser|privilege)"/i,
|
|
1897
|
+
riskLevel: "HIGH",
|
|
1898
|
+
description: "Keyword-triggered privilege escalation via 'superuser' or 'privilege' (CWE-269)",
|
|
1899
|
+
payloadType: "scope_escalation",
|
|
1900
|
+
parameterTypes: ["request", "query", "input", "text", "message"],
|
|
1901
|
+
},
|
|
1902
|
+
],
|
|
1903
|
+
},
|
|
1812
1904
|
];
|
|
1813
1905
|
/**
|
|
1814
1906
|
* Get all payloads for an attack type
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* @module assessment/modules/DeveloperExperienceAssessor
|
|
14
14
|
*/
|
|
15
|
-
import { DocumentationMetrics, UsabilityMetrics, AssessmentStatus } from "../../../lib/assessmentTypes.js";
|
|
15
|
+
import { DocumentationMetrics, UsabilityMetrics, AssessmentStatus, NamespaceDetectionResult } from "../../../lib/assessmentTypes.js";
|
|
16
16
|
import { BaseAssessor } from "./BaseAssessor.js";
|
|
17
17
|
import { AssessmentContext } from "../AssessmentOrchestrator.js";
|
|
18
18
|
/**
|
|
@@ -35,6 +35,8 @@ export interface DeveloperExperienceAssessment {
|
|
|
35
35
|
usability: number;
|
|
36
36
|
overall: number;
|
|
37
37
|
};
|
|
38
|
+
/** Namespace detection results (Issue #142) */
|
|
39
|
+
namespaceDetection?: NamespaceDetectionResult;
|
|
38
40
|
}
|
|
39
41
|
export declare class DeveloperExperienceAssessor extends BaseAssessor<DeveloperExperienceAssessment> {
|
|
40
42
|
assess(context: AssessmentContext): Promise<DeveloperExperienceAssessment>;
|
|
@@ -97,6 +99,29 @@ export declare class DeveloperExperienceAssessor extends BaseAssessor<DeveloperE
|
|
|
97
99
|
private isDescriptiveName;
|
|
98
100
|
private getToolSchema;
|
|
99
101
|
private calculateUsabilityScore;
|
|
102
|
+
/**
|
|
103
|
+
* Detect namespace/prefix patterns in tool names.
|
|
104
|
+
* This helps identify intentional naming conventions like:
|
|
105
|
+
* - calc_add, calc_subtract -> namespace "calc"
|
|
106
|
+
* - fileRead, fileWrite -> namespace "file"
|
|
107
|
+
* - myserver_tool1, myserver_tool2 -> matches server name
|
|
108
|
+
*/
|
|
109
|
+
private detectNamespace;
|
|
110
|
+
/**
|
|
111
|
+
* Find common prefix among tool names.
|
|
112
|
+
* Handles both snake_case (calc_add) and camelCase (calcAdd) conventions.
|
|
113
|
+
*/
|
|
114
|
+
private findCommonPrefix;
|
|
115
|
+
/**
|
|
116
|
+
* Check if tool names use the server name as a prefix.
|
|
117
|
+
* Normalizes server name to match common patterns.
|
|
118
|
+
*/
|
|
119
|
+
private checkServerNamePrefix;
|
|
120
|
+
/**
|
|
121
|
+
* Normalize server name for prefix matching.
|
|
122
|
+
* Converts "My-Server" -> "myserver", "my_server" -> "myserver"
|
|
123
|
+
*/
|
|
124
|
+
private normalizeServerName;
|
|
100
125
|
private determineOverallStatus;
|
|
101
126
|
private generateExplanation;
|
|
102
127
|
private generateRecommendations;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeveloperExperienceAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/DeveloperExperienceAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAEhB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"DeveloperExperienceAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/DeveloperExperienceAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAEhB,gBAAgB,EAKhB,wBAAwB,EACzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAG9D;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC5C,yCAAyC;IACzC,aAAa,EAAE,oBAAoB,CAAC;IACpC,qCAAqC;IACrC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,gDAAgD;IAChD,MAAM,EAAE,gBAAgB,CAAC;IACzB,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iDAAiD;IACjD,MAAM,EAAE;QACN,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,wBAAwB,CAAC;CAC/C;AAED,qBAAa,2BAA4B,SAAQ,YAAY,CAAC,6BAA6B,CAAC;IACpF,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,6BAA6B,CAAC;IA6EzC,OAAO,CAAC,oBAAoB;IAmH5B,OAAO,CAAC,yBAAyB;IA4CjC,OAAO,CAAC,wBAAwB;IAiBhC,OAAO,CAAC,sBAAsB;IA8B9B,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,mBAAmB;IAwC3B,OAAO,CAAC,wBAAwB;IAahC,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,sBAAsB;IAY9B,OAAO,CAAC,mBAAmB;IA6B3B;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAkBlC;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,qBAAqB;IAgC7B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAgBjC;;;OAGG;IACH,OAAO,CAAC,aAAa;IAoBrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAsEzB,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,uBAAuB;IAsC/B,OAAO,CAAC,uBAAuB;IAwC/B,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,kBAAkB;IAsC1B,OAAO,CAAC,iBAAiB;IAiCzB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,uBAAuB;IAgB/B;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAsEvB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA0DxB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAsC7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,mBAAmB;IAyC3B,OAAO,CAAC,uBAAuB;CAmDhC"}
|
|
@@ -30,6 +30,8 @@ export class DeveloperExperienceAssessor extends BaseAssessor {
|
|
|
30
30
|
// Assess usability
|
|
31
31
|
const usabilityMetrics = this.analyzeUsability(context.tools);
|
|
32
32
|
const usabilityScore = this.calculateUsabilityScore(usabilityMetrics);
|
|
33
|
+
// Issue #142: Detect namespace/prefix patterns in tool names
|
|
34
|
+
const namespaceDetection = this.detectNamespace(context.tools, context.serverInfo?.name);
|
|
33
35
|
// Calculate overall score (weighted average: 60% docs, 40% usability)
|
|
34
36
|
const overallScore = Math.round(documentationScore * 0.6 + usabilityScore * 0.4);
|
|
35
37
|
// Determine status using Issue #55 thresholds
|
|
@@ -37,7 +39,7 @@ export class DeveloperExperienceAssessor extends BaseAssessor {
|
|
|
37
39
|
// Generate explanation and recommendations
|
|
38
40
|
const explanation = this.generateExplanation(documentationMetrics, usabilityMetrics, context.tools);
|
|
39
41
|
const recommendations = this.generateRecommendations(documentationMetrics, usabilityMetrics);
|
|
40
|
-
this.testCount =
|
|
42
|
+
this.testCount = 16; // Documentation (5) + Quality (6) + Usability (4) + Namespace (1) checks
|
|
41
43
|
return {
|
|
42
44
|
documentation: documentationMetrics,
|
|
43
45
|
usability: usabilityMetrics,
|
|
@@ -49,6 +51,7 @@ export class DeveloperExperienceAssessor extends BaseAssessor {
|
|
|
49
51
|
usability: usabilityScore,
|
|
50
52
|
overall: overallScore,
|
|
51
53
|
},
|
|
54
|
+
namespaceDetection,
|
|
52
55
|
};
|
|
53
56
|
}
|
|
54
57
|
// ============================================================================
|
|
@@ -673,6 +676,162 @@ export class DeveloperExperienceAssessor extends BaseAssessor {
|
|
|
673
676
|
return Math.round((score / maxScore) * 100);
|
|
674
677
|
}
|
|
675
678
|
// ============================================================================
|
|
679
|
+
// Namespace Detection (Issue #142)
|
|
680
|
+
// ============================================================================
|
|
681
|
+
/**
|
|
682
|
+
* Detect namespace/prefix patterns in tool names.
|
|
683
|
+
* This helps identify intentional naming conventions like:
|
|
684
|
+
* - calc_add, calc_subtract -> namespace "calc"
|
|
685
|
+
* - fileRead, fileWrite -> namespace "file"
|
|
686
|
+
* - myserver_tool1, myserver_tool2 -> matches server name
|
|
687
|
+
*/
|
|
688
|
+
detectNamespace(tools, serverName) {
|
|
689
|
+
const toolNames = tools.map((t) => t.name);
|
|
690
|
+
const totalTools = toolNames.length;
|
|
691
|
+
// Need at least 2 tools to detect a namespace
|
|
692
|
+
if (totalTools < 2) {
|
|
693
|
+
return {
|
|
694
|
+
detected: false,
|
|
695
|
+
confidence: "low",
|
|
696
|
+
toolsCovered: 0,
|
|
697
|
+
totalTools,
|
|
698
|
+
matchPattern: "none",
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
// Try server name prefix first (highest priority)
|
|
702
|
+
if (serverName) {
|
|
703
|
+
const serverNameResult = this.checkServerNamePrefix(toolNames, serverName);
|
|
704
|
+
if (serverNameResult.matches >= 2 &&
|
|
705
|
+
serverNameResult.matches / totalTools >= 0.5) {
|
|
706
|
+
return {
|
|
707
|
+
detected: true,
|
|
708
|
+
namespace: this.normalizeServerName(serverName),
|
|
709
|
+
confidence: "high",
|
|
710
|
+
toolsCovered: serverNameResult.matches,
|
|
711
|
+
totalTools,
|
|
712
|
+
matchPattern: "serverName",
|
|
713
|
+
evidence: serverNameResult.matchedTools.slice(0, 5),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// Try common prefix detection
|
|
718
|
+
const prefixResult = this.findCommonPrefix(toolNames);
|
|
719
|
+
if (prefixResult) {
|
|
720
|
+
const coverageRatio = prefixResult.count / totalTools;
|
|
721
|
+
const confidence = coverageRatio >= 0.5 ? "high" : coverageRatio >= 0.3 ? "medium" : "low";
|
|
722
|
+
if (prefixResult.count >= 2 && coverageRatio >= 0.3) {
|
|
723
|
+
return {
|
|
724
|
+
detected: true,
|
|
725
|
+
namespace: prefixResult.prefix,
|
|
726
|
+
confidence,
|
|
727
|
+
toolsCovered: prefixResult.count,
|
|
728
|
+
totalTools,
|
|
729
|
+
matchPattern: "prefix",
|
|
730
|
+
evidence: prefixResult.matchedTools.slice(0, 5),
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
// No namespace detected
|
|
735
|
+
return {
|
|
736
|
+
detected: false,
|
|
737
|
+
confidence: "low",
|
|
738
|
+
toolsCovered: 0,
|
|
739
|
+
totalTools,
|
|
740
|
+
matchPattern: "none",
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Find common prefix among tool names.
|
|
745
|
+
* Handles both snake_case (calc_add) and camelCase (calcAdd) conventions.
|
|
746
|
+
*/
|
|
747
|
+
findCommonPrefix(toolNames) {
|
|
748
|
+
if (toolNames.length < 2)
|
|
749
|
+
return null;
|
|
750
|
+
// Build prefix frequency map
|
|
751
|
+
const prefixCounts = new Map();
|
|
752
|
+
for (const name of toolNames) {
|
|
753
|
+
// Extract prefix using snake_case separator
|
|
754
|
+
const snakeMatch = name.match(/^([a-z]+)_/);
|
|
755
|
+
if (snakeMatch && snakeMatch[1].length >= 3) {
|
|
756
|
+
const prefix = snakeMatch[1];
|
|
757
|
+
if (!prefixCounts.has(prefix)) {
|
|
758
|
+
prefixCounts.set(prefix, []);
|
|
759
|
+
}
|
|
760
|
+
prefixCounts.get(prefix).push(name);
|
|
761
|
+
}
|
|
762
|
+
// Extract prefix using camelCase pattern
|
|
763
|
+
const camelMatch = name.match(/^([a-z]+)[A-Z]/);
|
|
764
|
+
if (camelMatch && camelMatch[1].length >= 3) {
|
|
765
|
+
const prefix = camelMatch[1];
|
|
766
|
+
if (!prefixCounts.has(prefix)) {
|
|
767
|
+
prefixCounts.set(prefix, []);
|
|
768
|
+
}
|
|
769
|
+
// Avoid double-counting if snake_case already found this name
|
|
770
|
+
const existing = prefixCounts.get(prefix);
|
|
771
|
+
if (!existing.includes(name)) {
|
|
772
|
+
existing.push(name);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
// Find the prefix with the most matches
|
|
777
|
+
let bestPrefix = null;
|
|
778
|
+
let bestCount = 0;
|
|
779
|
+
let bestTools = [];
|
|
780
|
+
for (const [prefix, tools] of prefixCounts) {
|
|
781
|
+
if (tools.length > bestCount) {
|
|
782
|
+
bestPrefix = prefix;
|
|
783
|
+
bestCount = tools.length;
|
|
784
|
+
bestTools = tools;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (bestPrefix && bestCount >= 2) {
|
|
788
|
+
return {
|
|
789
|
+
prefix: bestPrefix,
|
|
790
|
+
count: bestCount,
|
|
791
|
+
matchedTools: bestTools,
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Check if tool names use the server name as a prefix.
|
|
798
|
+
* Normalizes server name to match common patterns.
|
|
799
|
+
*/
|
|
800
|
+
checkServerNamePrefix(toolNames, serverName) {
|
|
801
|
+
const normalizedServerName = this.normalizeServerName(serverName);
|
|
802
|
+
const matchedTools = [];
|
|
803
|
+
for (const name of toolNames) {
|
|
804
|
+
const nameLower = name.toLowerCase();
|
|
805
|
+
// Check snake_case prefix (e.g., myserver_tool)
|
|
806
|
+
if (nameLower.startsWith(normalizedServerName + "_")) {
|
|
807
|
+
matchedTools.push(name);
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
// Check camelCase prefix (e.g., myserverTool)
|
|
811
|
+
if (nameLower.startsWith(normalizedServerName) &&
|
|
812
|
+
name.length > normalizedServerName.length &&
|
|
813
|
+
/[A-Z]/.test(name[normalizedServerName.length])) {
|
|
814
|
+
matchedTools.push(name);
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
// Check kebab-case prefix (e.g., myserver-tool)
|
|
818
|
+
if (nameLower.startsWith(normalizedServerName + "-")) {
|
|
819
|
+
matchedTools.push(name);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
matches: matchedTools.length,
|
|
824
|
+
matchedTools,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Normalize server name for prefix matching.
|
|
829
|
+
* Converts "My-Server" -> "myserver", "my_server" -> "myserver"
|
|
830
|
+
*/
|
|
831
|
+
normalizeServerName(serverName) {
|
|
832
|
+
return serverName.toLowerCase().replace(/[-_\s]/g, "");
|
|
833
|
+
}
|
|
834
|
+
// ============================================================================
|
|
676
835
|
// Combined Status, Explanation, and Recommendations
|
|
677
836
|
// ============================================================================
|
|
678
837
|
determineOverallStatus(overallScore) {
|
|
@@ -13,6 +13,13 @@
|
|
|
13
13
|
import { BaseAssessor } from "./BaseAssessor.js";
|
|
14
14
|
import { AssessmentContext } from "../AssessmentOrchestrator.js";
|
|
15
15
|
import type { ManifestValidationAssessment } from "../../../lib/assessmentTypes.js";
|
|
16
|
+
/**
|
|
17
|
+
* Calculate Levenshtein distance between two strings
|
|
18
|
+
* Uses space-optimized two-row algorithm for O(min(n,m)) memory
|
|
19
|
+
* Used for "did you mean?" suggestions on mismatched tool names (Issue #140)
|
|
20
|
+
* Exported for testing (Issue #141 - ISSUE-002)
|
|
21
|
+
*/
|
|
22
|
+
export declare function levenshteinDistance(a: string, b: string, maxDist?: number): number;
|
|
16
23
|
export declare class ManifestValidationAssessor extends BaseAssessor {
|
|
17
24
|
/**
|
|
18
25
|
* Get mcp_config from manifest (supports both root and nested v0.3 format)
|
|
@@ -22,6 +29,21 @@ export declare class ManifestValidationAssessor extends BaseAssessor {
|
|
|
22
29
|
* @returns The mcp_config object or undefined if not found in either location
|
|
23
30
|
*/
|
|
24
31
|
private getMcpConfig;
|
|
32
|
+
/**
|
|
33
|
+
* Extract contact information from manifest (Issue #141 - D4 check)
|
|
34
|
+
* Supports: author object, author string (email parsing), repository fallback
|
|
35
|
+
*
|
|
36
|
+
* @param manifest - The parsed manifest JSON
|
|
37
|
+
* @returns Extracted contact info or undefined if no contact info found
|
|
38
|
+
*/
|
|
39
|
+
private extractContactInfo;
|
|
40
|
+
/**
|
|
41
|
+
* Extract version information from manifest (Issue #141 - D5 check)
|
|
42
|
+
*
|
|
43
|
+
* @param manifest - The parsed manifest JSON
|
|
44
|
+
* @returns Extracted version info or undefined if no version found
|
|
45
|
+
*/
|
|
46
|
+
private extractVersionInfo;
|
|
25
47
|
/**
|
|
26
48
|
* Run manifest validation assessment
|
|
27
49
|
*/
|
|
@@ -62,6 +84,16 @@ export declare class ManifestValidationAssessor extends BaseAssessor {
|
|
|
62
84
|
* Validate version format (semver)
|
|
63
85
|
*/
|
|
64
86
|
private validateVersionFormat;
|
|
87
|
+
/**
|
|
88
|
+
* Validate manifest tool declarations against actual server tools (Issue #140)
|
|
89
|
+
* Compares tool names in manifest.tools against context.tools from tools/list
|
|
90
|
+
* Uses Levenshtein distance for "did you mean?" suggestions
|
|
91
|
+
*/
|
|
92
|
+
private validateToolNamesMatch;
|
|
93
|
+
/**
|
|
94
|
+
* Fetch with retry logic for transient network failures
|
|
95
|
+
*/
|
|
96
|
+
private fetchWithRetry;
|
|
65
97
|
/**
|
|
66
98
|
* Validate privacy policy URLs are accessible
|
|
67
99
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ManifestValidationAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/ManifestValidationAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"ManifestValidationAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/ManifestValidationAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,KAAK,EACV,4BAA4B,EAS7B,MAAM,uBAAuB,CAAC;AAO/B;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAwCR;AA+BD,qBAAa,0BAA2B,SAAQ,YAAY;IAC1D;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAcpB;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAqC1B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,4BAA4B,CAAC;IAiMxC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAyB9B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAmB/B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAgC/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAiC7B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAiChC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA+CzB;;OAEG;IACH,OAAO,CAAC,YAAY;IAqCpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA+B1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA4B7B;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAyE9B;;OAEG;YACW,cAAc;IAuC5B;;OAEG;YACW,yBAAyB;IAmEvC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAsB/B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA0C3B;;OAEG;IACH,OAAO,CAAC,uBAAuB;CA+ChC"}
|