@bryan-thompson/inspector-assessment-client 1.13.0 → 1.14.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/assets/{OAuthCallback-D8KW6pFf.js → OAuthCallback-DaKwjxdn.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-D15nNAOl.js → OAuthDebugCallback-HiI2IPgB.js} +1 -1
- package/dist/assets/{index-cVkEgqCc.js → index-BAJG90Yd.js} +20 -8
- package/dist/assets/{index-Cuc9GxjD.css → index-BdXNC65t.css} +3 -0
- package/dist/index.html +2 -2
- package/lib/lib/assessmentDiffer.d.ts +79 -0
- package/lib/lib/assessmentDiffer.d.ts.map +1 -0
- package/lib/lib/assessmentDiffer.js +289 -0
- package/lib/lib/assessmentTypes.d.ts +69 -0
- package/lib/lib/assessmentTypes.d.ts.map +1 -1
- package/lib/lib/assessmentTypes.js +10 -0
- package/lib/lib/reportFormatters/DiffReportFormatter.d.ts +10 -0
- package/lib/lib/reportFormatters/DiffReportFormatter.d.ts.map +1 -0
- package/lib/lib/reportFormatters/DiffReportFormatter.js +177 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts +1 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -1
- package/lib/services/assessment/AssessmentOrchestrator.js +22 -1
- package/lib/services/assessment/modules/AuthenticationAssessor.d.ts +48 -0
- package/lib/services/assessment/modules/AuthenticationAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/AuthenticationAssessor.js +270 -0
- package/lib/services/assessment/modules/ExternalAPIScannerAssessor.d.ts +58 -0
- package/lib/services/assessment/modules/ExternalAPIScannerAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ExternalAPIScannerAssessor.js +248 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/FunctionalityAssessor.js +7 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts +4 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +118 -4
- package/lib/services/assessment/modules/index.d.ts +1 -0
- package/lib/services/assessment/modules/index.d.ts.map +1 -1
- package/lib/services/assessment/modules/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Assessor
|
|
3
|
+
* Evaluates if OAuth is appropriate for the deployment model (local vs remote).
|
|
4
|
+
*
|
|
5
|
+
* Detection Logic:
|
|
6
|
+
* 1. Check if server uses OAuth (serverInfo/manifest)
|
|
7
|
+
* 2. Analyze if tools access local resources (files, apps, OS features)
|
|
8
|
+
* 3. If OAuth + no local deps = recommend cloud deployment
|
|
9
|
+
* 4. If OAuth + local deps = warn about mixed model
|
|
10
|
+
*/
|
|
11
|
+
import { BaseAssessor } from "./BaseAssessor.js";
|
|
12
|
+
// Patterns that indicate OAuth usage
|
|
13
|
+
const OAUTH_PATTERNS = [
|
|
14
|
+
/oauth/i,
|
|
15
|
+
/authorization\s*code/i,
|
|
16
|
+
/access_token/i,
|
|
17
|
+
/refresh_token/i,
|
|
18
|
+
/client_id/i,
|
|
19
|
+
/client_secret/i,
|
|
20
|
+
/pkce/i,
|
|
21
|
+
/code_verifier/i,
|
|
22
|
+
/code_challenge/i,
|
|
23
|
+
/authorize\s*url/i,
|
|
24
|
+
/token\s*endpoint/i,
|
|
25
|
+
];
|
|
26
|
+
// Patterns that indicate API key usage
|
|
27
|
+
const API_KEY_PATTERNS = [
|
|
28
|
+
/api[_-]?key/i,
|
|
29
|
+
/x-api-key/i,
|
|
30
|
+
/bearer\s+token/i,
|
|
31
|
+
/authorization:\s*bearer/i,
|
|
32
|
+
/secret[_-]?key/i,
|
|
33
|
+
];
|
|
34
|
+
// Patterns that indicate local resource dependencies
|
|
35
|
+
const LOCAL_RESOURCE_PATTERNS = [
|
|
36
|
+
/process\.cwd/i,
|
|
37
|
+
/fs\.(read|write|exists|mkdir|rmdir)/i,
|
|
38
|
+
/child_process/i,
|
|
39
|
+
/execSync|spawnSync/i,
|
|
40
|
+
/os\.(homedir|tmpdir|platform)/i,
|
|
41
|
+
/path\.(join|resolve|dirname)/i,
|
|
42
|
+
/__dirname|__filename/i,
|
|
43
|
+
/\.local|\.config|\.cache/i,
|
|
44
|
+
/localhost|127\.0\.0\.1/i,
|
|
45
|
+
/file:\/\//i,
|
|
46
|
+
];
|
|
47
|
+
export class AuthenticationAssessor extends BaseAssessor {
|
|
48
|
+
/**
|
|
49
|
+
* Run authentication assessment
|
|
50
|
+
*/
|
|
51
|
+
async assess(context) {
|
|
52
|
+
this.log("Starting authentication assessment");
|
|
53
|
+
this.testCount = 0;
|
|
54
|
+
const oauthIndicators = [];
|
|
55
|
+
const localResourceIndicators = [];
|
|
56
|
+
const apiKeyIndicators = [];
|
|
57
|
+
// Analyze source code for patterns
|
|
58
|
+
if (context.sourceCodeFiles) {
|
|
59
|
+
for (const [filePath, content] of context.sourceCodeFiles) {
|
|
60
|
+
this.testCount++;
|
|
61
|
+
// Check for OAuth patterns
|
|
62
|
+
for (const pattern of OAUTH_PATTERNS) {
|
|
63
|
+
if (pattern.test(content)) {
|
|
64
|
+
const indicator = `${filePath}: ${pattern.source}`;
|
|
65
|
+
if (!oauthIndicators.includes(indicator)) {
|
|
66
|
+
oauthIndicators.push(indicator);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Check for API key patterns
|
|
71
|
+
for (const pattern of API_KEY_PATTERNS) {
|
|
72
|
+
if (pattern.test(content)) {
|
|
73
|
+
const indicator = `${filePath}: ${pattern.source}`;
|
|
74
|
+
if (!apiKeyIndicators.includes(indicator)) {
|
|
75
|
+
apiKeyIndicators.push(indicator);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Check for local resource patterns
|
|
80
|
+
for (const pattern of LOCAL_RESOURCE_PATTERNS) {
|
|
81
|
+
if (pattern.test(content)) {
|
|
82
|
+
const indicator = `${filePath}: ${pattern.source}`;
|
|
83
|
+
if (!localResourceIndicators.includes(indicator)) {
|
|
84
|
+
localResourceIndicators.push(indicator);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Determine auth method
|
|
91
|
+
const authMethod = this.detectAuthMethod(oauthIndicators, apiKeyIndicators, context);
|
|
92
|
+
// Determine if there are local dependencies
|
|
93
|
+
const hasLocalDependencies = localResourceIndicators.length > 0;
|
|
94
|
+
// Get transport type
|
|
95
|
+
const transportType = this.detectTransportType(context);
|
|
96
|
+
// Evaluate appropriateness
|
|
97
|
+
const appropriateness = this.evaluateAppropriateness({
|
|
98
|
+
authMethod,
|
|
99
|
+
hasLocalDependencies,
|
|
100
|
+
transportType,
|
|
101
|
+
oauthIndicators,
|
|
102
|
+
});
|
|
103
|
+
const recommendation = this.generateRecommendation(authMethod, hasLocalDependencies, transportType, appropriateness);
|
|
104
|
+
const status = this.evaluateStatus(appropriateness);
|
|
105
|
+
const explanation = this.generateExplanation(authMethod, hasLocalDependencies, transportType, appropriateness);
|
|
106
|
+
const recommendations = this.generateRecommendations(authMethod, hasLocalDependencies, appropriateness);
|
|
107
|
+
this.log(`Assessment complete: auth=${authMethod}, localDeps=${hasLocalDependencies}`);
|
|
108
|
+
return {
|
|
109
|
+
authMethod,
|
|
110
|
+
hasLocalDependencies,
|
|
111
|
+
transportType,
|
|
112
|
+
appropriateness,
|
|
113
|
+
recommendation,
|
|
114
|
+
detectedPatterns: {
|
|
115
|
+
oauthIndicators,
|
|
116
|
+
localResourceIndicators,
|
|
117
|
+
apiKeyIndicators,
|
|
118
|
+
},
|
|
119
|
+
status,
|
|
120
|
+
explanation,
|
|
121
|
+
recommendations,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Detect authentication method
|
|
126
|
+
*/
|
|
127
|
+
detectAuthMethod(oauthIndicators, apiKeyIndicators, context) {
|
|
128
|
+
// Check manifest for OAuth configuration
|
|
129
|
+
if (context.manifestJson) {
|
|
130
|
+
const manifestStr = JSON.stringify(context.manifestJson);
|
|
131
|
+
if (/oauth/i.test(manifestStr)) {
|
|
132
|
+
return "oauth";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Check for OAuth patterns in code
|
|
136
|
+
if (oauthIndicators.length >= 3) {
|
|
137
|
+
return "oauth";
|
|
138
|
+
}
|
|
139
|
+
// Check for API key patterns
|
|
140
|
+
if (apiKeyIndicators.length >= 2) {
|
|
141
|
+
return "api_key";
|
|
142
|
+
}
|
|
143
|
+
// If we found some indicators but not enough to be confident
|
|
144
|
+
if (oauthIndicators.length > 0 || apiKeyIndicators.length > 0) {
|
|
145
|
+
return "unknown";
|
|
146
|
+
}
|
|
147
|
+
return "none";
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Detect transport type from context
|
|
151
|
+
*/
|
|
152
|
+
detectTransportType(context) {
|
|
153
|
+
// Check config for transport
|
|
154
|
+
if (context.config) {
|
|
155
|
+
const config = context.config;
|
|
156
|
+
if (config.transport) {
|
|
157
|
+
return String(config.transport);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Default to stdio for local servers
|
|
161
|
+
return "stdio";
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Evaluate if authentication setup is appropriate
|
|
165
|
+
*/
|
|
166
|
+
evaluateAppropriateness(params) {
|
|
167
|
+
const { authMethod, hasLocalDependencies, transportType } = params;
|
|
168
|
+
const concerns = [];
|
|
169
|
+
let isAppropriate = true;
|
|
170
|
+
// OAuth on local stdio server with local dependencies is concerning
|
|
171
|
+
if (authMethod === "oauth" &&
|
|
172
|
+
hasLocalDependencies &&
|
|
173
|
+
transportType === "stdio") {
|
|
174
|
+
concerns.push("OAuth authentication detected on local stdio server with file system dependencies");
|
|
175
|
+
concerns.push("Consider: Is OAuth necessary for a local-only server that accesses local resources?");
|
|
176
|
+
isAppropriate = false;
|
|
177
|
+
}
|
|
178
|
+
// OAuth on stdio without local deps might suggest it should be remote
|
|
179
|
+
if (authMethod === "oauth" &&
|
|
180
|
+
!hasLocalDependencies &&
|
|
181
|
+
transportType === "stdio") {
|
|
182
|
+
concerns.push("OAuth detected on stdio transport but no local resource dependencies found");
|
|
183
|
+
concerns.push("This server might be better suited for remote deployment");
|
|
184
|
+
}
|
|
185
|
+
// API key on remote server without proper security
|
|
186
|
+
if (authMethod === "api_key" && transportType !== "stdio") {
|
|
187
|
+
concerns.push("API key authentication on remote transport - ensure HTTPS is enforced");
|
|
188
|
+
}
|
|
189
|
+
// No auth on remote server is a concern
|
|
190
|
+
if (authMethod === "none" && transportType !== "stdio") {
|
|
191
|
+
concerns.push("No authentication detected on remote transport");
|
|
192
|
+
concerns.push("Consider adding authentication for production use");
|
|
193
|
+
isAppropriate = false;
|
|
194
|
+
}
|
|
195
|
+
const explanation = concerns.length > 0
|
|
196
|
+
? `Found ${concerns.length} concern(s) with authentication setup`
|
|
197
|
+
: "Authentication configuration appears appropriate for deployment model";
|
|
198
|
+
return { isAppropriate, concerns, explanation };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Generate recommendation based on analysis
|
|
202
|
+
*/
|
|
203
|
+
generateRecommendation(authMethod, hasLocalDependencies, transportType, appropriateness) {
|
|
204
|
+
if (appropriateness.isAppropriate) {
|
|
205
|
+
return "Authentication configuration is appropriate for the deployment model";
|
|
206
|
+
}
|
|
207
|
+
if (authMethod === "oauth" && !hasLocalDependencies) {
|
|
208
|
+
return "Consider deploying as a remote server if OAuth is required - no local dependencies detected";
|
|
209
|
+
}
|
|
210
|
+
if (authMethod === "oauth" && hasLocalDependencies) {
|
|
211
|
+
return "Review if OAuth is necessary - server has local dependencies suggesting local-only usage";
|
|
212
|
+
}
|
|
213
|
+
if (authMethod === "none" && transportType !== "stdio") {
|
|
214
|
+
return "Add authentication for remote deployment to prevent unauthorized access";
|
|
215
|
+
}
|
|
216
|
+
return "Review authentication configuration for production deployment";
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Evaluate overall status based on appropriateness
|
|
220
|
+
*/
|
|
221
|
+
evaluateStatus(appropriateness) {
|
|
222
|
+
if (!appropriateness.isAppropriate) {
|
|
223
|
+
return "NEED_MORE_INFO";
|
|
224
|
+
}
|
|
225
|
+
if (appropriateness.concerns.length > 0) {
|
|
226
|
+
return "NEED_MORE_INFO";
|
|
227
|
+
}
|
|
228
|
+
return "PASS";
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Generate explanation
|
|
232
|
+
*/
|
|
233
|
+
generateExplanation(authMethod, hasLocalDependencies, transportType, appropriateness) {
|
|
234
|
+
const parts = [];
|
|
235
|
+
parts.push(`Detected authentication: ${authMethod}`);
|
|
236
|
+
parts.push(`Transport: ${transportType}`);
|
|
237
|
+
parts.push(`Local dependencies: ${hasLocalDependencies ? "Yes" : "No"}`);
|
|
238
|
+
if (appropriateness.concerns.length > 0) {
|
|
239
|
+
parts.push(`Concerns: ${appropriateness.concerns.length}`);
|
|
240
|
+
}
|
|
241
|
+
return parts.join(". ");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Generate recommendations
|
|
245
|
+
*/
|
|
246
|
+
generateRecommendations(authMethod, hasLocalDependencies, appropriateness) {
|
|
247
|
+
const recommendations = [];
|
|
248
|
+
if (!appropriateness.isAppropriate) {
|
|
249
|
+
recommendations.push("REVIEW - Authentication configuration:");
|
|
250
|
+
for (const concern of appropriateness.concerns.slice(0, 3)) {
|
|
251
|
+
recommendations.push(` - ${concern}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (authMethod === "oauth") {
|
|
255
|
+
recommendations.push("Ensure OAuth configuration follows RFC 8707");
|
|
256
|
+
recommendations.push("Verify PKCE is implemented for public clients");
|
|
257
|
+
}
|
|
258
|
+
if (authMethod === "api_key") {
|
|
259
|
+
recommendations.push("Ensure API keys are not hardcoded");
|
|
260
|
+
recommendations.push("Use environment variables for secrets");
|
|
261
|
+
}
|
|
262
|
+
if (authMethod === "none" && !hasLocalDependencies) {
|
|
263
|
+
recommendations.push("Consider adding authentication if server will be remotely accessible");
|
|
264
|
+
}
|
|
265
|
+
if (recommendations.length === 0) {
|
|
266
|
+
recommendations.push("Authentication configuration appears appropriate");
|
|
267
|
+
}
|
|
268
|
+
return recommendations;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External API Scanner Assessor
|
|
3
|
+
*
|
|
4
|
+
* Scans source code for external API dependencies and checks for affiliation.
|
|
5
|
+
* Helps identify:
|
|
6
|
+
* - External services used (GitHub, Slack, AWS, etc.)
|
|
7
|
+
* - Hardcoded URLs that may need privacy policy disclosure
|
|
8
|
+
* - Server names that suggest affiliation without verification
|
|
9
|
+
*
|
|
10
|
+
* Part of Priority 2 gap-closing features.
|
|
11
|
+
*/
|
|
12
|
+
import { BaseAssessor } from "./BaseAssessor.js";
|
|
13
|
+
import type { AssessmentContext } from "../AssessmentOrchestrator.js";
|
|
14
|
+
import type { ExternalAPIScannerAssessment } from "../../../lib/assessmentTypes.js";
|
|
15
|
+
export declare class ExternalAPIScannerAssessor extends BaseAssessor {
|
|
16
|
+
assess(context: AssessmentContext): Promise<ExternalAPIScannerAssessment>;
|
|
17
|
+
/**
|
|
18
|
+
* Check if file should be skipped
|
|
19
|
+
*/
|
|
20
|
+
private shouldSkipFile;
|
|
21
|
+
/**
|
|
22
|
+
* Scan a file for external API URLs
|
|
23
|
+
*/
|
|
24
|
+
private scanFileForAPIs;
|
|
25
|
+
/**
|
|
26
|
+
* Check if URL should be skipped
|
|
27
|
+
*/
|
|
28
|
+
private shouldSkipUrl;
|
|
29
|
+
/**
|
|
30
|
+
* Clean URL by removing trailing punctuation
|
|
31
|
+
*/
|
|
32
|
+
private cleanUrl;
|
|
33
|
+
/**
|
|
34
|
+
* Identify which service a URL belongs to
|
|
35
|
+
*/
|
|
36
|
+
private identifyService;
|
|
37
|
+
/**
|
|
38
|
+
* Check if server name suggests affiliation that needs verification
|
|
39
|
+
*/
|
|
40
|
+
private checkAffiliation;
|
|
41
|
+
/**
|
|
42
|
+
* Compute assessment status based on scan results
|
|
43
|
+
*/
|
|
44
|
+
private computeStatus;
|
|
45
|
+
/**
|
|
46
|
+
* Generate explanation text
|
|
47
|
+
*/
|
|
48
|
+
private generateExplanation;
|
|
49
|
+
/**
|
|
50
|
+
* Generate recommendations
|
|
51
|
+
*/
|
|
52
|
+
private generateRecommendations;
|
|
53
|
+
/**
|
|
54
|
+
* Create result when source code analysis is not available
|
|
55
|
+
*/
|
|
56
|
+
private createNoSourceResult;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=ExternalAPIScannerAssessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalAPIScannerAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/ExternalAPIScannerAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,KAAK,EAEV,4BAA4B,EAE7B,MAAM,uBAAuB,CAAC;AAmE/B,qBAAa,0BAA2B,SAAQ,YAAY;IACpD,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,4BAA4B,CAAC;IA6DxC;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,eAAe;IA0BvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsBxB;;OAEG;IACH,OAAO,CAAC,aAAa;IAcrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAyB3B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA2B/B;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAa7B"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External API Scanner Assessor
|
|
3
|
+
*
|
|
4
|
+
* Scans source code for external API dependencies and checks for affiliation.
|
|
5
|
+
* Helps identify:
|
|
6
|
+
* - External services used (GitHub, Slack, AWS, etc.)
|
|
7
|
+
* - Hardcoded URLs that may need privacy policy disclosure
|
|
8
|
+
* - Server names that suggest affiliation without verification
|
|
9
|
+
*
|
|
10
|
+
* Part of Priority 2 gap-closing features.
|
|
11
|
+
*/
|
|
12
|
+
import { BaseAssessor } from "./BaseAssessor.js";
|
|
13
|
+
/**
|
|
14
|
+
* Known external services and their URL patterns
|
|
15
|
+
*/
|
|
16
|
+
const KNOWN_SERVICES = {
|
|
17
|
+
github: [/api\.github\.com/i, /github\.com\/[^/]+\/[^/]+/i],
|
|
18
|
+
gitlab: [/gitlab\.com/i, /api\.gitlab\.com/i],
|
|
19
|
+
slack: [/slack\.com\/api/i, /api\.slack\.com/i, /hooks\.slack\.com/i],
|
|
20
|
+
discord: [/discord\.com\/api/i, /discordapp\.com/i],
|
|
21
|
+
aws: [/\.amazonaws\.com/i, /\.aws\.amazon\.com/i],
|
|
22
|
+
azure: [/\.azure\.com/i, /\.microsoft\.com/i],
|
|
23
|
+
gcp: [/\.googleapis\.com/i, /\.google\.com\/.*api/i],
|
|
24
|
+
openai: [/api\.openai\.com/i],
|
|
25
|
+
anthropic: [/api\.anthropic\.com/i],
|
|
26
|
+
huggingface: [/huggingface\.co/i, /api\.huggingface\.co/i],
|
|
27
|
+
stripe: [/api\.stripe\.com/i],
|
|
28
|
+
twilio: [/api\.twilio\.com/i],
|
|
29
|
+
sendgrid: [/api\.sendgrid\.com/i],
|
|
30
|
+
firebase: [/firebaseio\.com/i, /firebase\.google\.com/i],
|
|
31
|
+
supabase: [/supabase\.co/i],
|
|
32
|
+
notion: [/api\.notion\.com/i],
|
|
33
|
+
airtable: [/api\.airtable\.com/i],
|
|
34
|
+
dropbox: [/api\.dropbox\.com/i, /dropboxapi\.com/i],
|
|
35
|
+
jira: [/atlassian\.net/i, /jira\.com/i],
|
|
36
|
+
linear: [/api\.linear\.app/i],
|
|
37
|
+
asana: [/api\.asana\.com/i],
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* URL patterns to skip (not external APIs)
|
|
41
|
+
*/
|
|
42
|
+
const SKIP_URL_PATTERNS = [
|
|
43
|
+
/localhost/i,
|
|
44
|
+
/127\.0\.0\.1/i,
|
|
45
|
+
/0\.0\.0\.0/i,
|
|
46
|
+
/example\.com/i,
|
|
47
|
+
/test\.com/i,
|
|
48
|
+
/\.local\//i,
|
|
49
|
+
/schema\.org/i,
|
|
50
|
+
/w3\.org/i,
|
|
51
|
+
/json-schema\.org/i,
|
|
52
|
+
/npmjs\.com/i,
|
|
53
|
+
/unpkg\.com/i,
|
|
54
|
+
/cdn\./i,
|
|
55
|
+
/fonts\.googleapis\.com/i,
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* File patterns to skip during scanning
|
|
59
|
+
*/
|
|
60
|
+
const SKIP_FILE_PATTERNS = [
|
|
61
|
+
/node_modules/i,
|
|
62
|
+
/\.test\.(ts|js|tsx|jsx)$/i,
|
|
63
|
+
/\.spec\.(ts|js|tsx|jsx)$/i,
|
|
64
|
+
/\.d\.ts$/i,
|
|
65
|
+
/package-lock\.json$/i,
|
|
66
|
+
/yarn\.lock$/i,
|
|
67
|
+
/\.map$/i,
|
|
68
|
+
/README\.md$/i,
|
|
69
|
+
/CHANGELOG\.md$/i,
|
|
70
|
+
/LICENSE/i,
|
|
71
|
+
/\.git\//i,
|
|
72
|
+
/dist\//i,
|
|
73
|
+
/build\//i,
|
|
74
|
+
];
|
|
75
|
+
export class ExternalAPIScannerAssessor extends BaseAssessor {
|
|
76
|
+
async assess(context) {
|
|
77
|
+
this.log("Starting external API scanner assessment");
|
|
78
|
+
this.resetTestCount();
|
|
79
|
+
const detectedAPIs = [];
|
|
80
|
+
let scannedFiles = 0;
|
|
81
|
+
// Check if source code analysis is enabled
|
|
82
|
+
if (!context.sourceCodeFiles || !context.config.enableSourceCodeAnalysis) {
|
|
83
|
+
this.log("Source code analysis not enabled, skipping external API scan");
|
|
84
|
+
return this.createNoSourceResult();
|
|
85
|
+
}
|
|
86
|
+
// Scan each source file
|
|
87
|
+
for (const [filePath, content] of context.sourceCodeFiles) {
|
|
88
|
+
if (this.shouldSkipFile(filePath))
|
|
89
|
+
continue;
|
|
90
|
+
this.testCount++;
|
|
91
|
+
scannedFiles++;
|
|
92
|
+
const fileAPIs = this.scanFileForAPIs(filePath, content);
|
|
93
|
+
detectedAPIs.push(...fileAPIs);
|
|
94
|
+
}
|
|
95
|
+
// Extract unique services
|
|
96
|
+
const uniqueServices = [...new Set(detectedAPIs.map((api) => api.service))];
|
|
97
|
+
// Check for affiliation concerns
|
|
98
|
+
const affiliationWarning = this.checkAffiliation(context.serverName, uniqueServices);
|
|
99
|
+
// Determine status
|
|
100
|
+
const status = this.computeStatus(detectedAPIs, affiliationWarning);
|
|
101
|
+
const explanation = this.generateExplanation(detectedAPIs, uniqueServices, affiliationWarning, scannedFiles);
|
|
102
|
+
const recommendations = this.generateRecommendations(uniqueServices, affiliationWarning);
|
|
103
|
+
this.log(`External API scan complete: ${detectedAPIs.length} APIs found in ${scannedFiles} files`);
|
|
104
|
+
return {
|
|
105
|
+
detectedAPIs,
|
|
106
|
+
uniqueServices,
|
|
107
|
+
affiliationWarning,
|
|
108
|
+
scannedFiles,
|
|
109
|
+
status,
|
|
110
|
+
explanation,
|
|
111
|
+
recommendations,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if file should be skipped
|
|
116
|
+
*/
|
|
117
|
+
shouldSkipFile(filePath) {
|
|
118
|
+
return SKIP_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Scan a file for external API URLs
|
|
122
|
+
*/
|
|
123
|
+
scanFileForAPIs(filePath, content) {
|
|
124
|
+
const apis = [];
|
|
125
|
+
const urlPattern = /https?:\/\/[^\s'"`)}\]><]+/g;
|
|
126
|
+
const matches = content.match(urlPattern) || [];
|
|
127
|
+
for (const url of matches) {
|
|
128
|
+
// Skip non-API URLs
|
|
129
|
+
if (this.shouldSkipUrl(url))
|
|
130
|
+
continue;
|
|
131
|
+
// Identify the service
|
|
132
|
+
const service = this.identifyService(url);
|
|
133
|
+
// Avoid duplicates in the same file
|
|
134
|
+
if (!apis.some((api) => api.url === url && api.filePath === filePath)) {
|
|
135
|
+
apis.push({
|
|
136
|
+
url: this.cleanUrl(url),
|
|
137
|
+
service,
|
|
138
|
+
filePath,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return apis;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check if URL should be skipped
|
|
146
|
+
*/
|
|
147
|
+
shouldSkipUrl(url) {
|
|
148
|
+
return SKIP_URL_PATTERNS.some((pattern) => pattern.test(url));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Clean URL by removing trailing punctuation
|
|
152
|
+
*/
|
|
153
|
+
cleanUrl(url) {
|
|
154
|
+
return url.replace(/[.,;:!?'")\]}>]+$/, "");
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Identify which service a URL belongs to
|
|
158
|
+
*/
|
|
159
|
+
identifyService(url) {
|
|
160
|
+
for (const [service, patterns] of Object.entries(KNOWN_SERVICES)) {
|
|
161
|
+
if (patterns.some((pattern) => pattern.test(url))) {
|
|
162
|
+
return service;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return "unknown";
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Check if server name suggests affiliation that needs verification
|
|
169
|
+
*/
|
|
170
|
+
checkAffiliation(serverName, detectedServices) {
|
|
171
|
+
const nameLower = serverName.toLowerCase();
|
|
172
|
+
const nameParts = nameLower.split(/[-_\s]/);
|
|
173
|
+
// Check if server name contains a known service name
|
|
174
|
+
for (const service of Object.keys(KNOWN_SERVICES)) {
|
|
175
|
+
if (nameParts.includes(service) || nameLower.includes(service)) {
|
|
176
|
+
// Server claims this service - check if actually uses it
|
|
177
|
+
if (detectedServices.includes(service)) {
|
|
178
|
+
return `Server name "${serverName}" suggests affiliation with ${service}. Verify the author is officially affiliated before approving.`;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
return `Server name "${serverName}" suggests affiliation with ${service}, but no ${service} API calls detected. This may be misleading.`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Compute assessment status based on scan results
|
|
189
|
+
*/
|
|
190
|
+
computeStatus(apis, affiliationWarning) {
|
|
191
|
+
if (affiliationWarning) {
|
|
192
|
+
return "NEED_MORE_INFO";
|
|
193
|
+
}
|
|
194
|
+
if (apis.length === 0) {
|
|
195
|
+
return "PASS";
|
|
196
|
+
}
|
|
197
|
+
// Having external APIs isn't a failure, just informational
|
|
198
|
+
return "PASS";
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Generate explanation text
|
|
202
|
+
*/
|
|
203
|
+
generateExplanation(apis, services, affiliationWarning, scannedFiles) {
|
|
204
|
+
const parts = [];
|
|
205
|
+
parts.push(`Scanned ${scannedFiles} source files.`);
|
|
206
|
+
if (apis.length === 0) {
|
|
207
|
+
parts.push("No external API dependencies detected.");
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
parts.push(`Found ${apis.length} external API URL(s) across ${services.length} service(s): ${services.join(", ")}.`);
|
|
211
|
+
}
|
|
212
|
+
if (affiliationWarning) {
|
|
213
|
+
parts.push(`ATTENTION: ${affiliationWarning}`);
|
|
214
|
+
}
|
|
215
|
+
return parts.join(" ");
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Generate recommendations
|
|
219
|
+
*/
|
|
220
|
+
generateRecommendations(services, affiliationWarning) {
|
|
221
|
+
const recommendations = [];
|
|
222
|
+
if (affiliationWarning) {
|
|
223
|
+
recommendations.push("Verify author affiliation with claimed service before directory approval");
|
|
224
|
+
}
|
|
225
|
+
if (services.length > 0 && !services.every((s) => s === "unknown")) {
|
|
226
|
+
recommendations.push(`Ensure privacy policies are declared for external services: ${services.filter((s) => s !== "unknown").join(", ")}`);
|
|
227
|
+
}
|
|
228
|
+
if (services.includes("unknown")) {
|
|
229
|
+
recommendations.push("Review unrecognized external URLs for security and privacy implications");
|
|
230
|
+
}
|
|
231
|
+
return recommendations;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Create result when source code analysis is not available
|
|
235
|
+
*/
|
|
236
|
+
createNoSourceResult() {
|
|
237
|
+
return {
|
|
238
|
+
detectedAPIs: [],
|
|
239
|
+
uniqueServices: [],
|
|
240
|
+
scannedFiles: 0,
|
|
241
|
+
status: "NEED_MORE_INFO",
|
|
242
|
+
explanation: "External API scanning requires source code analysis. Enable with --source flag.",
|
|
243
|
+
recommendations: [
|
|
244
|
+
"Re-run assessment with --source <path> to enable external API scanning",
|
|
245
|
+
],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FunctionalityAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/FunctionalityAssessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,uBAAuB,EAGxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAM9D,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,OAAO,CAAC,cAAc,CAAwB;IAE9C;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoCvB,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"FunctionalityAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/FunctionalityAssessor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,uBAAuB,EAGxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAM9D,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,OAAO,CAAC,cAAc,CAAwB;IAE9C;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoCvB,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,uBAAuB,CAAC;YAkI5D,QAAQ;IAiFtB,OAAO,CAAC,qBAAqB;IA0D7B,OAAO,CAAC,kBAAkB;IAwF1B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAe7C;IAEF;;;OAGG;IACH,OAAO,CAAC,mCAAmC;IAsF3C;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAWlB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO;IAI9C,OAAO,CAAC,mBAAmB;CA+B5B"}
|
|
@@ -108,6 +108,12 @@ export class FunctionalityAssessor extends BaseAssessor {
|
|
|
108
108
|
const coveragePercentage = testedTools > 0 ? (workingTools / testedTools) * 100 : 0;
|
|
109
109
|
const status = this.determineStatus(workingTools, testedTools);
|
|
110
110
|
const explanation = this.generateExplanation(totalTools, testedTools, workingTools, brokenTools);
|
|
111
|
+
// Map tools to include only schema-relevant fields for downstream consumers
|
|
112
|
+
const tools = context.tools.map((t) => ({
|
|
113
|
+
name: t.name,
|
|
114
|
+
description: t.description,
|
|
115
|
+
inputSchema: t.inputSchema,
|
|
116
|
+
}));
|
|
111
117
|
return {
|
|
112
118
|
totalTools,
|
|
113
119
|
testedTools,
|
|
@@ -117,6 +123,7 @@ export class FunctionalityAssessor extends BaseAssessor {
|
|
|
117
123
|
status,
|
|
118
124
|
explanation,
|
|
119
125
|
toolResults,
|
|
126
|
+
tools,
|
|
120
127
|
};
|
|
121
128
|
}
|
|
122
129
|
async testTool(tool, callTool) {
|
|
@@ -54,6 +54,10 @@ export declare class ManifestValidationAssessor extends BaseAssessor {
|
|
|
54
54
|
* Validate version format (semver)
|
|
55
55
|
*/
|
|
56
56
|
private validateVersionFormat;
|
|
57
|
+
/**
|
|
58
|
+
* Validate privacy policy URLs are accessible
|
|
59
|
+
*/
|
|
60
|
+
private validatePrivacyPolicyUrls;
|
|
57
61
|
/**
|
|
58
62
|
* Determine overall status
|
|
59
63
|
*/
|
|
@@ -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;AAC9D,OAAO,KAAK,EACV,4BAA4B,
|
|
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;AAC9D,OAAO,KAAK,EACV,4BAA4B,EAK7B,MAAM,uBAAuB,CAAC;AAM/B,qBAAa,0BAA2B,SAAQ,YAAY;IAC1D;;OAEG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,4BAA4B,CAAC;IA6JxC;;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;IA8B7B;;OAEG;YACW,yBAAyB;IA2EvC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAsB/B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA0C3B;;OAEG;IACH,OAAO,CAAC,uBAAuB;CA+ChC"}
|