@bryan-thompson/inspector-assessment-client 1.15.1 → 1.16.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-tZBHqkSF.js → OAuthCallback-KwMiy-L3.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-D73S8G8X.js → OAuthDebugCallback-hckdJlo3.js} +1 -1
- package/dist/assets/{index-BAbFakRL.js → index-C89umkGV.js} +745 -4350
- package/dist/index.html +1 -1
- package/lib/lib/assessmentTypes.d.ts +123 -0
- package/lib/lib/assessmentTypes.d.ts.map +1 -1
- package/lib/lib/assessmentTypes.js +20 -0
- package/lib/lib/securityPatterns.d.ts +2 -2
- package/lib/lib/securityPatterns.d.ts.map +1 -1
- package/lib/lib/securityPatterns.js +290 -15
- package/lib/services/assessment/AssessmentOrchestrator.d.ts +67 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -1
- package/lib/services/assessment/AssessmentOrchestrator.js +91 -1
- package/lib/services/assessment/ResponseValidator.d.ts +7 -34
- package/lib/services/assessment/ResponseValidator.d.ts.map +1 -1
- package/lib/services/assessment/ResponseValidator.js +100 -704
- package/lib/services/assessment/config/annotationPatterns.js +1 -1
- package/lib/services/assessment/lib/RequestHistoryAnalyzer.d.ts +67 -0
- package/lib/services/assessment/lib/RequestHistoryAnalyzer.d.ts.map +1 -0
- package/lib/services/assessment/lib/RequestHistoryAnalyzer.js +191 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts +1 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts.map +1 -1
- package/lib/services/assessment/lib/claudeCodeBridge.js +5 -4
- package/lib/services/assessment/modules/AuthenticationAssessor.d.ts +4 -0
- package/lib/services/assessment/modules/AuthenticationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/AuthenticationAssessor.js +97 -1
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.d.ts +39 -0
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.js +330 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/FunctionalityAssessor.js +46 -13
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts +5 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +81 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +1 -1
- package/lib/services/assessment/modules/PromptAssessor.d.ts +30 -0
- package/lib/services/assessment/modules/PromptAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/PromptAssessor.js +367 -0
- package/lib/services/assessment/modules/ResourceAssessor.d.ts +28 -0
- package/lib/services/assessment/modules/ResourceAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ResourceAssessor.js +296 -0
- package/lib/services/assessment/modules/SecurityAssessor.d.ts +4 -2
- package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/SecurityAssessor.js +10 -41
- package/lib/utils/jsonUtils.d.ts +68 -0
- package/lib/utils/jsonUtils.d.ts.map +1 -0
- package/lib/utils/jsonUtils.js +141 -0
- package/lib/utils/paramUtils.d.ts +11 -0
- package/lib/utils/paramUtils.d.ts.map +1 -0
- package/lib/utils/paramUtils.js +37 -0
- package/lib/utils/schemaUtils.d.ts +74 -0
- package/lib/utils/schemaUtils.d.ts.map +1 -0
- package/lib/utils/schemaUtils.js +268 -0
- package/package.json +1 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Assessor Module
|
|
3
|
+
* Tests MCP server resources for accessibility, security, and compliance
|
|
4
|
+
*
|
|
5
|
+
* Tests include:
|
|
6
|
+
* - Resource accessibility (can read declared resources)
|
|
7
|
+
* - Path traversal vulnerabilities in resource URIs
|
|
8
|
+
* - Sensitive data exposure detection
|
|
9
|
+
* - URI validation and format compliance
|
|
10
|
+
*/
|
|
11
|
+
import { BaseAssessor } from "./BaseAssessor.js";
|
|
12
|
+
// Sensitive file patterns that shouldn't be exposed
|
|
13
|
+
const SENSITIVE_PATTERNS = [
|
|
14
|
+
/\.env$/i,
|
|
15
|
+
/\.pem$/i,
|
|
16
|
+
/\.key$/i,
|
|
17
|
+
/id_rsa/i,
|
|
18
|
+
/id_dsa/i,
|
|
19
|
+
/\.crt$/i,
|
|
20
|
+
/password/i,
|
|
21
|
+
/secret/i,
|
|
22
|
+
/credential/i,
|
|
23
|
+
/\/etc\/passwd/i,
|
|
24
|
+
/\/etc\/shadow/i,
|
|
25
|
+
/\.ssh\//i,
|
|
26
|
+
/aws_access_key/i,
|
|
27
|
+
/api[_-]?key/i,
|
|
28
|
+
/\.htpasswd/i,
|
|
29
|
+
/\.git\/config/i,
|
|
30
|
+
];
|
|
31
|
+
// Path traversal patterns to test
|
|
32
|
+
const PATH_TRAVERSAL_PAYLOADS = [
|
|
33
|
+
"../../../etc/passwd",
|
|
34
|
+
"..\\..\\..\\windows\\system32\\config\\sam",
|
|
35
|
+
"....//....//....//etc/passwd",
|
|
36
|
+
"%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd",
|
|
37
|
+
"..%252f..%252f..%252fetc/passwd",
|
|
38
|
+
];
|
|
39
|
+
// Sensitive content patterns in resource content
|
|
40
|
+
const SENSITIVE_CONTENT_PATTERNS = [
|
|
41
|
+
/-----BEGIN.*PRIVATE KEY-----/i,
|
|
42
|
+
/-----BEGIN RSA PRIVATE KEY-----/i,
|
|
43
|
+
/sk-[a-zA-Z0-9]{32,}/i, // OpenAI-style API keys
|
|
44
|
+
/ghp_[a-zA-Z0-9]{36}/i, // GitHub tokens
|
|
45
|
+
/glpat-[a-zA-Z0-9-_]{20}/i, // GitLab tokens
|
|
46
|
+
/xox[baprs]-[a-zA-Z0-9-]+/i, // Slack tokens
|
|
47
|
+
/AKIA[A-Z0-9]{16}/i, // AWS access keys
|
|
48
|
+
/password\s*[:=]\s*['"][^'"]+['"]/i,
|
|
49
|
+
/secret\s*[:=]\s*['"][^'"]+['"]/i,
|
|
50
|
+
];
|
|
51
|
+
export class ResourceAssessor extends BaseAssessor {
|
|
52
|
+
async assess(context) {
|
|
53
|
+
const results = [];
|
|
54
|
+
// Check if resources are provided
|
|
55
|
+
if (!context.resources && !context.resourceTemplates) {
|
|
56
|
+
return this.createNoResourcesResponse();
|
|
57
|
+
}
|
|
58
|
+
const resources = context.resources || [];
|
|
59
|
+
const templates = context.resourceTemplates || [];
|
|
60
|
+
this.log(`Testing ${resources.length} resources and ${templates.length} resource templates`);
|
|
61
|
+
// Test each resource
|
|
62
|
+
for (const resource of resources) {
|
|
63
|
+
this.testCount++;
|
|
64
|
+
const result = await this.testResource(resource, context);
|
|
65
|
+
results.push(result);
|
|
66
|
+
}
|
|
67
|
+
// Test resource templates with path traversal payloads
|
|
68
|
+
for (const template of templates) {
|
|
69
|
+
this.testCount++;
|
|
70
|
+
const templateResults = await this.testResourceTemplate(template, context);
|
|
71
|
+
results.push(...templateResults);
|
|
72
|
+
}
|
|
73
|
+
// Calculate metrics
|
|
74
|
+
const accessibleResources = results.filter((r) => r.accessible).length;
|
|
75
|
+
const securityIssuesFound = results.filter((r) => r.securityIssues.length > 0).length;
|
|
76
|
+
const pathTraversalVulnerabilities = results.filter((r) => r.pathTraversalVulnerable).length;
|
|
77
|
+
const sensitiveDataExposures = results.filter((r) => r.sensitiveDataExposed).length;
|
|
78
|
+
// Determine status
|
|
79
|
+
const status = this.determineResourceStatus(pathTraversalVulnerabilities, sensitiveDataExposures, securityIssuesFound, results.length);
|
|
80
|
+
// Generate explanation and recommendations
|
|
81
|
+
const explanation = this.generateExplanation(results, pathTraversalVulnerabilities, sensitiveDataExposures);
|
|
82
|
+
const recommendations = this.generateRecommendations(results);
|
|
83
|
+
return {
|
|
84
|
+
resourcesTested: resources.length,
|
|
85
|
+
resourceTemplatesTested: templates.length,
|
|
86
|
+
accessibleResources,
|
|
87
|
+
securityIssuesFound,
|
|
88
|
+
pathTraversalVulnerabilities,
|
|
89
|
+
sensitiveDataExposures,
|
|
90
|
+
results,
|
|
91
|
+
status,
|
|
92
|
+
explanation,
|
|
93
|
+
recommendations,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
createNoResourcesResponse() {
|
|
97
|
+
return {
|
|
98
|
+
resourcesTested: 0,
|
|
99
|
+
resourceTemplatesTested: 0,
|
|
100
|
+
accessibleResources: 0,
|
|
101
|
+
securityIssuesFound: 0,
|
|
102
|
+
pathTraversalVulnerabilities: 0,
|
|
103
|
+
sensitiveDataExposures: 0,
|
|
104
|
+
results: [],
|
|
105
|
+
status: "PASS",
|
|
106
|
+
explanation: "No resources declared by server. Resource assessment skipped.",
|
|
107
|
+
recommendations: [],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async testResource(resource, context) {
|
|
111
|
+
const result = {
|
|
112
|
+
resourceUri: resource.uri,
|
|
113
|
+
resourceName: resource.name,
|
|
114
|
+
mimeType: resource.mimeType,
|
|
115
|
+
tested: true,
|
|
116
|
+
accessible: false,
|
|
117
|
+
securityIssues: [],
|
|
118
|
+
pathTraversalVulnerable: false,
|
|
119
|
+
sensitiveDataExposed: false,
|
|
120
|
+
validUri: this.isValidUri(resource.uri),
|
|
121
|
+
};
|
|
122
|
+
// Check URI for sensitive patterns
|
|
123
|
+
if (this.isSensitiveUri(resource.uri)) {
|
|
124
|
+
result.securityIssues.push(`Resource URI matches sensitive file pattern: ${resource.uri}`);
|
|
125
|
+
result.sensitiveDataExposed = true;
|
|
126
|
+
}
|
|
127
|
+
// Try to read the resource if readResource function is provided
|
|
128
|
+
if (context.readResource) {
|
|
129
|
+
try {
|
|
130
|
+
const startTime = Date.now();
|
|
131
|
+
const content = await this.executeWithTimeout(context.readResource(resource.uri), 5000);
|
|
132
|
+
result.readTime = Date.now() - startTime;
|
|
133
|
+
result.accessible = true;
|
|
134
|
+
result.contentSizeBytes = content?.length || 0;
|
|
135
|
+
// Check content for sensitive data
|
|
136
|
+
if (content && this.containsSensitiveContent(content)) {
|
|
137
|
+
result.securityIssues.push("Resource content contains sensitive data patterns (credentials, keys, etc.)");
|
|
138
|
+
result.sensitiveDataExposed = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
result.error = this.extractErrorMessage(error);
|
|
143
|
+
result.accessible = false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
result.tested = false;
|
|
148
|
+
result.error = "readResource function not provided - skipping read test";
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
async testResourceTemplate(template, context) {
|
|
153
|
+
const results = [];
|
|
154
|
+
// Test the template itself
|
|
155
|
+
const templateResult = {
|
|
156
|
+
resourceUri: template.uriTemplate,
|
|
157
|
+
resourceName: template.name,
|
|
158
|
+
mimeType: template.mimeType,
|
|
159
|
+
tested: true,
|
|
160
|
+
accessible: false,
|
|
161
|
+
securityIssues: [],
|
|
162
|
+
pathTraversalVulnerable: false,
|
|
163
|
+
sensitiveDataExposed: false,
|
|
164
|
+
validUri: this.isValidUriTemplate(template.uriTemplate),
|
|
165
|
+
};
|
|
166
|
+
// Check template for sensitive patterns
|
|
167
|
+
if (this.isSensitiveUri(template.uriTemplate)) {
|
|
168
|
+
templateResult.securityIssues.push(`Resource template matches sensitive file pattern: ${template.uriTemplate}`);
|
|
169
|
+
templateResult.sensitiveDataExposed = true;
|
|
170
|
+
}
|
|
171
|
+
results.push(templateResult);
|
|
172
|
+
// Test path traversal vulnerabilities if readResource is available
|
|
173
|
+
if (context.readResource) {
|
|
174
|
+
for (const payload of PATH_TRAVERSAL_PAYLOADS) {
|
|
175
|
+
this.testCount++;
|
|
176
|
+
const testUri = this.injectPayloadIntoTemplate(template.uriTemplate, payload);
|
|
177
|
+
const traversalResult = {
|
|
178
|
+
resourceUri: testUri,
|
|
179
|
+
resourceName: `${template.name} (path traversal test)`,
|
|
180
|
+
tested: true,
|
|
181
|
+
accessible: false,
|
|
182
|
+
securityIssues: [],
|
|
183
|
+
pathTraversalVulnerable: false,
|
|
184
|
+
sensitiveDataExposed: false,
|
|
185
|
+
validUri: false,
|
|
186
|
+
};
|
|
187
|
+
try {
|
|
188
|
+
const content = await this.executeWithTimeout(context.readResource(testUri), 3000);
|
|
189
|
+
// If we got content with a path traversal payload, it's vulnerable
|
|
190
|
+
if (content &&
|
|
191
|
+
(content.includes("root:") || content.includes("[fonts]"))) {
|
|
192
|
+
traversalResult.pathTraversalVulnerable = true;
|
|
193
|
+
traversalResult.accessible = true;
|
|
194
|
+
traversalResult.securityIssues.push(`Path traversal vulnerability: successfully accessed ${testUri}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Expected - path traversal should be rejected
|
|
199
|
+
traversalResult.accessible = false;
|
|
200
|
+
}
|
|
201
|
+
results.push(traversalResult);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return results;
|
|
205
|
+
}
|
|
206
|
+
isValidUri(uri) {
|
|
207
|
+
try {
|
|
208
|
+
// Check for common URI schemes
|
|
209
|
+
if (uri.startsWith("file://") ||
|
|
210
|
+
uri.startsWith("http://") ||
|
|
211
|
+
uri.startsWith("https://") ||
|
|
212
|
+
uri.startsWith("resource://") ||
|
|
213
|
+
uri.match(/^[a-z][a-z0-9+.-]*:/i)) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
// Allow relative paths
|
|
217
|
+
return !uri.includes("..") || uri.startsWith("/");
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
isValidUriTemplate(template) {
|
|
224
|
+
// URI templates can contain {variable} placeholders
|
|
225
|
+
const withoutPlaceholders = template.replace(/\{[^}]+\}/g, "placeholder");
|
|
226
|
+
return this.isValidUri(withoutPlaceholders);
|
|
227
|
+
}
|
|
228
|
+
isSensitiveUri(uri) {
|
|
229
|
+
return SENSITIVE_PATTERNS.some((pattern) => pattern.test(uri));
|
|
230
|
+
}
|
|
231
|
+
containsSensitiveContent(content) {
|
|
232
|
+
return SENSITIVE_CONTENT_PATTERNS.some((pattern) => pattern.test(content));
|
|
233
|
+
}
|
|
234
|
+
injectPayloadIntoTemplate(template, payload) {
|
|
235
|
+
// Replace template variables with payload
|
|
236
|
+
const result = template.replace(/\{[^}]+\}/g, payload);
|
|
237
|
+
// If no variables, append payload
|
|
238
|
+
if (result === template) {
|
|
239
|
+
return template + "/" + payload;
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
determineResourceStatus(pathTraversalVulnerabilities, sensitiveDataExposures, securityIssuesFound, totalResources) {
|
|
244
|
+
// Critical failures
|
|
245
|
+
if (pathTraversalVulnerabilities > 0)
|
|
246
|
+
return "FAIL";
|
|
247
|
+
if (sensitiveDataExposures > 0)
|
|
248
|
+
return "FAIL";
|
|
249
|
+
// Moderate issues
|
|
250
|
+
if (securityIssuesFound > 0)
|
|
251
|
+
return "NEED_MORE_INFO";
|
|
252
|
+
// No resources tested
|
|
253
|
+
if (totalResources === 0)
|
|
254
|
+
return "PASS";
|
|
255
|
+
return "PASS";
|
|
256
|
+
}
|
|
257
|
+
generateExplanation(results, pathTraversalVulnerabilities, sensitiveDataExposures) {
|
|
258
|
+
const parts = [];
|
|
259
|
+
parts.push(`Tested ${results.length} resource(s).`);
|
|
260
|
+
if (pathTraversalVulnerabilities > 0) {
|
|
261
|
+
parts.push(`CRITICAL: ${pathTraversalVulnerabilities} path traversal vulnerability(ies) detected.`);
|
|
262
|
+
}
|
|
263
|
+
if (sensitiveDataExposures > 0) {
|
|
264
|
+
parts.push(`WARNING: ${sensitiveDataExposures} resource(s) may expose sensitive data.`);
|
|
265
|
+
}
|
|
266
|
+
const accessibleCount = results.filter((r) => r.accessible).length;
|
|
267
|
+
if (accessibleCount > 0) {
|
|
268
|
+
parts.push(`${accessibleCount} resource(s) are accessible.`);
|
|
269
|
+
}
|
|
270
|
+
return parts.join(" ");
|
|
271
|
+
}
|
|
272
|
+
generateRecommendations(results) {
|
|
273
|
+
const recommendations = [];
|
|
274
|
+
// Path traversal recommendations
|
|
275
|
+
const pathTraversalResults = results.filter((r) => r.pathTraversalVulnerable);
|
|
276
|
+
if (pathTraversalResults.length > 0) {
|
|
277
|
+
recommendations.push("CRITICAL: Implement path validation to prevent path traversal attacks. Normalize paths and validate against allowed directories.");
|
|
278
|
+
}
|
|
279
|
+
// Sensitive data recommendations
|
|
280
|
+
const sensitiveResults = results.filter((r) => r.sensitiveDataExposed);
|
|
281
|
+
if (sensitiveResults.length > 0) {
|
|
282
|
+
recommendations.push("Review resources for sensitive data exposure. Remove or restrict access to resources containing credentials, keys, or sensitive configuration.");
|
|
283
|
+
}
|
|
284
|
+
// Invalid URI recommendations
|
|
285
|
+
const invalidUriResults = results.filter((r) => !r.validUri);
|
|
286
|
+
if (invalidUriResults.length > 0) {
|
|
287
|
+
recommendations.push("Fix invalid resource URIs to ensure proper URI format compliance.");
|
|
288
|
+
}
|
|
289
|
+
// Inaccessible resource recommendations
|
|
290
|
+
const inaccessibleResults = results.filter((r) => r.tested && !r.accessible && !r.pathTraversalVulnerable);
|
|
291
|
+
if (inaccessibleResults.length > 0) {
|
|
292
|
+
recommendations.push(`${inaccessibleResults.length} declared resource(s) are not accessible. Verify resource paths and permissions.`);
|
|
293
|
+
}
|
|
294
|
+
return recommendations;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Security Assessor Module
|
|
3
|
-
* Tests for backend API security vulnerabilities using
|
|
4
|
-
* - Critical Injection (
|
|
3
|
+
* Tests for backend API security vulnerabilities using 18 focused patterns
|
|
4
|
+
* - Critical Injection (6): Command, Calculator, SQL, Path Traversal, XXE, NoSQL
|
|
5
5
|
* - Input Validation (3): Type Safety, Boundary Testing, Required Fields
|
|
6
6
|
* - Protocol Compliance (2): MCP Error Format, Timeout Handling
|
|
7
|
+
* - Tool-Specific (7): SSRF, Unicode Bypass, Nested Injection, Package Squatting,
|
|
8
|
+
* Data Exfiltration, Configuration Drift, Tool Shadowing
|
|
7
9
|
*/
|
|
8
10
|
import { SecurityAssessment } from "../../../lib/assessmentTypes.js";
|
|
9
11
|
import { BaseAssessor } from "./BaseAssessor.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/SecurityAssessor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"SecurityAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/SecurityAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;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;AAa9D,qBAAa,gBAAiB,SAAQ,YAAY;IAC1C,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAuFrE;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAkC7B;;;;OAIG;YACW,yBAAyB;IAuKvC;;;;OAIG;YACW,qBAAqB;IA2JnC;;OAEG;YACW,WAAW;IA2HzB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAkDzB;;;OAGG;IACH,OAAO,CAAC,8BAA8B;IAmDtC;;OAEG;IACH,OAAO,CAAC,aAAa;IA+BrB;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAgClC;;;OAGG;IACH,OAAO,CAAC,eAAe;IA6HvB;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB;IAiE7B;;;;;;;;;OASG;IACH,OAAO,CAAC,oBAAoB;IAqC5B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAsB3B;;;;;;;OAOG;IACH,OAAO,CAAC,oBAAoB;IAkC5B;;OAEG;YACW,+BAA+B;IAiC7C;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAYjC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA0B/B;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAkEnC;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAuI3B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAsB5B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,oBAAoB;IAgK5B;;;;;;OAMG;IACH,OAAO,CAAC,wBAAwB;IA8BhC;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IA8BhC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,oBAAoB;IAoE5B;;OAEG;IACH,OAAO,CAAC,YAAY;IASpB;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAiB9B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;CAmB3B"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Security Assessor Module
|
|
3
|
-
* Tests for backend API security vulnerabilities using
|
|
4
|
-
* - Critical Injection (
|
|
3
|
+
* Tests for backend API security vulnerabilities using 18 focused patterns
|
|
4
|
+
* - Critical Injection (6): Command, Calculator, SQL, Path Traversal, XXE, NoSQL
|
|
5
5
|
* - Input Validation (3): Type Safety, Boundary Testing, Required Fields
|
|
6
6
|
* - Protocol Compliance (2): MCP Error Format, Timeout Handling
|
|
7
|
+
* - Tool-Specific (7): SSRF, Unicode Bypass, Nested Injection, Package Squatting,
|
|
8
|
+
* Data Exfiltration, Configuration Drift, Tool Shadowing
|
|
7
9
|
*/
|
|
8
10
|
import { BaseAssessor } from "./BaseAssessor.js";
|
|
9
11
|
import { getAllAttackPatterns, getPayloadsForAttack, } from "../../../lib/securityPatterns.js";
|
|
@@ -1071,8 +1073,6 @@ export class SecurityAssessor extends BaseAssessor {
|
|
|
1071
1073
|
* Layer 2: Verify NO execution evidence (defense-in-depth)
|
|
1072
1074
|
*/
|
|
1073
1075
|
isReflectionResponse(responseText) {
|
|
1074
|
-
console.log("[DIAG] isReflectionResponse called");
|
|
1075
|
-
console.log("[DIAG] Response preview:", responseText.substring(0, 200));
|
|
1076
1076
|
// Status message patterns (NEW)
|
|
1077
1077
|
const statusPatterns = [
|
|
1078
1078
|
// "Action executed successfully: <anything>" (generic status message)
|
|
@@ -1152,18 +1152,7 @@ export class SecurityAssessor extends BaseAssessor {
|
|
|
1152
1152
|
/error:.*too (long|short|large)/i,
|
|
1153
1153
|
];
|
|
1154
1154
|
// LAYER 1: Check for reflection/status patterns
|
|
1155
|
-
const
|
|
1156
|
-
const hasReflection = reflectionPatterns.some((pattern) => {
|
|
1157
|
-
const matches = pattern.test(responseText);
|
|
1158
|
-
if (matches) {
|
|
1159
|
-
matchedPatterns.push(pattern.source.substring(0, 50));
|
|
1160
|
-
}
|
|
1161
|
-
return matches;
|
|
1162
|
-
});
|
|
1163
|
-
console.log("[DIAG] Has reflection:", hasReflection);
|
|
1164
|
-
if (matchedPatterns.length > 0) {
|
|
1165
|
-
console.log("[DIAG] Matched reflection patterns:", matchedPatterns.join(", "));
|
|
1166
|
-
}
|
|
1155
|
+
const hasReflection = reflectionPatterns.some((pattern) => pattern.test(responseText));
|
|
1167
1156
|
if (hasReflection) {
|
|
1168
1157
|
// LAYER 2: Defense-in-depth - verify NO execution evidence
|
|
1169
1158
|
// For JSON responses, check execution artifacts only in result/output fields
|
|
@@ -1187,25 +1176,18 @@ export class SecurityAssessor extends BaseAssessor {
|
|
|
1187
1176
|
? this.detectExecutionArtifacts(outputFields) // Skip result, check only output fields
|
|
1188
1177
|
: this.detectExecutionArtifacts(resultText) ||
|
|
1189
1178
|
this.detectExecutionArtifacts(outputFields);
|
|
1190
|
-
console.log("[DIAG] JSON mode - checking execution in result/output fields only");
|
|
1191
|
-
console.log("[DIAG] Has execution in output:", hasExecutionInOutput);
|
|
1192
1179
|
if (hasExecutionInOutput) {
|
|
1193
|
-
|
|
1194
|
-
return false;
|
|
1180
|
+
return false; // Reflection + Execution in output = VULNERABLE
|
|
1195
1181
|
}
|
|
1196
|
-
|
|
1197
|
-
return true;
|
|
1182
|
+
return true; // Reflection + clean output = SAFE
|
|
1198
1183
|
}
|
|
1199
1184
|
catch {
|
|
1200
1185
|
// Not JSON, check entire response for execution
|
|
1201
1186
|
const hasExecution = this.detectExecutionArtifacts(responseText);
|
|
1202
|
-
console.log("[DIAG] Text mode - Has execution artifacts:", hasExecution);
|
|
1203
1187
|
if (hasExecution) {
|
|
1204
|
-
|
|
1205
|
-
return false;
|
|
1188
|
+
return false; // Reflection + Execution = VULNERABLE
|
|
1206
1189
|
}
|
|
1207
|
-
|
|
1208
|
-
return true;
|
|
1190
|
+
return true; // Reflection only = SAFE
|
|
1209
1191
|
}
|
|
1210
1192
|
}
|
|
1211
1193
|
// JSON Structural Analysis with execution verification
|
|
@@ -1238,7 +1220,6 @@ export class SecurityAssessor extends BaseAssessor {
|
|
|
1238
1220
|
* MEDIUM confidence: Contextual patterns (root alone, paths)
|
|
1239
1221
|
*/
|
|
1240
1222
|
detectExecutionArtifacts(responseText) {
|
|
1241
|
-
console.log("[DIAG] detectExecutionArtifacts called");
|
|
1242
1223
|
const executionIndicators = [
|
|
1243
1224
|
// HIGH CONFIDENCE - System files (requires format)
|
|
1244
1225
|
/[a-z]+:x:\d+:\d+:/i, // passwd: "root:x:0:0:"
|
|
@@ -1259,19 +1240,7 @@ export class SecurityAssessor extends BaseAssessor {
|
|
|
1259
1240
|
// MEDIUM CONFIDENCE - Process info
|
|
1260
1241
|
/PID:\s*\d{3,}/i, // Process ID
|
|
1261
1242
|
];
|
|
1262
|
-
|
|
1263
|
-
const found = executionIndicators.some((pattern) => {
|
|
1264
|
-
const matches = pattern.test(responseText);
|
|
1265
|
-
if (matches) {
|
|
1266
|
-
matchedExecutionPatterns.push(pattern.source.substring(0, 50));
|
|
1267
|
-
}
|
|
1268
|
-
return matches;
|
|
1269
|
-
});
|
|
1270
|
-
if (matchedExecutionPatterns.length > 0) {
|
|
1271
|
-
console.log("[DIAG] Matched execution patterns:", matchedExecutionPatterns.join(", "));
|
|
1272
|
-
}
|
|
1273
|
-
console.log("[DIAG] Execution artifacts found:", found);
|
|
1274
|
-
return found;
|
|
1243
|
+
return executionIndicators.some((pattern) => pattern.test(responseText));
|
|
1275
1244
|
}
|
|
1276
1245
|
/**
|
|
1277
1246
|
* Analyze injection response (existing logic)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export type JsonValue = string | number | boolean | null | undefined | JsonValue[] | {
|
|
2
|
+
[key: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
export type JsonSchemaConst = {
|
|
5
|
+
const: JsonValue;
|
|
6
|
+
title?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
};
|
|
9
|
+
export type JsonSchemaType = {
|
|
10
|
+
type?: "string" | "number" | "integer" | "boolean" | "array" | "object" | "null" | ("string" | "number" | "integer" | "boolean" | "array" | "object" | "null")[];
|
|
11
|
+
title?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
required?: string[];
|
|
14
|
+
default?: JsonValue;
|
|
15
|
+
properties?: Record<string, JsonSchemaType>;
|
|
16
|
+
items?: JsonSchemaType;
|
|
17
|
+
minItems?: number;
|
|
18
|
+
maxItems?: number;
|
|
19
|
+
minimum?: number;
|
|
20
|
+
maximum?: number;
|
|
21
|
+
minLength?: number;
|
|
22
|
+
maxLength?: number;
|
|
23
|
+
nullable?: boolean;
|
|
24
|
+
pattern?: string;
|
|
25
|
+
format?: string;
|
|
26
|
+
enum?: string[];
|
|
27
|
+
enumNames?: string[];
|
|
28
|
+
const?: JsonValue;
|
|
29
|
+
oneOf?: (JsonSchemaType | JsonSchemaConst)[];
|
|
30
|
+
anyOf?: (JsonSchemaType | JsonSchemaConst)[];
|
|
31
|
+
$ref?: string;
|
|
32
|
+
};
|
|
33
|
+
export type JsonObject = {
|
|
34
|
+
[key: string]: JsonValue;
|
|
35
|
+
};
|
|
36
|
+
export type DataType = "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | "array" | "null";
|
|
37
|
+
/**
|
|
38
|
+
* Determines the specific data type of a JSON value
|
|
39
|
+
* @param value The JSON value to analyze
|
|
40
|
+
* @returns The specific data type including "array" and "null" as distinct types
|
|
41
|
+
*/
|
|
42
|
+
export declare function getDataType(value: JsonValue): DataType;
|
|
43
|
+
/**
|
|
44
|
+
* Attempts to parse a string as JSON, only for objects and arrays
|
|
45
|
+
* @param str The string to parse
|
|
46
|
+
* @returns Object with success boolean and either parsed data or original string
|
|
47
|
+
*/
|
|
48
|
+
export declare function tryParseJson(str: string): {
|
|
49
|
+
success: boolean;
|
|
50
|
+
data: JsonValue;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Updates a value at a specific path in a nested JSON structure
|
|
54
|
+
* @param obj The original JSON value
|
|
55
|
+
* @param path Array of keys/indices representing the path to the value
|
|
56
|
+
* @param value The new value to set
|
|
57
|
+
* @returns A new JSON value with the updated path
|
|
58
|
+
*/
|
|
59
|
+
export declare function updateValueAtPath(obj: JsonValue, path: string[], value: JsonValue): JsonValue;
|
|
60
|
+
/**
|
|
61
|
+
* Gets a value at a specific path in a nested JSON structure
|
|
62
|
+
* @param obj The JSON value to traverse
|
|
63
|
+
* @param path Array of keys/indices representing the path to the value
|
|
64
|
+
* @param defaultValue Value to return if path doesn't exist
|
|
65
|
+
* @returns The value at the path, or defaultValue if not found
|
|
66
|
+
*/
|
|
67
|
+
export declare function getValueAtPath(obj: JsonValue, path: string[], defaultValue?: JsonValue): JsonValue;
|
|
68
|
+
//# sourceMappingURL=jsonUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonUtils.d.ts","sourceRoot":"","sources":["../../src/utils/jsonUtils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EACD,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,SAAS,GACT,OAAO,GACP,QAAQ,GACR,MAAM,GACN,CACI,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,SAAS,GACT,OAAO,GACP,QAAQ,GACR,MAAM,CACT,EAAE,CAAC;IACR,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC5C,KAAK,CAAC,EAAE,cAAc,CAAC;IAEvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,cAAc,GAAG,eAAe,CAAC,EAAE,CAAC;IAC7C,KAAK,CAAC,EAAE,CAAC,cAAc,GAAG,eAAe,CAAC,EAAE,CAAC;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,QAAQ,GAChB,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,UAAU,GACV,OAAO,GACP,MAAM,CAAC;AAEX;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,QAAQ,CAItD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;CACjB,CAcA;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,SAAS,GACf,SAAS,CAkBX;AA+ED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EAAE,EACd,YAAY,GAAE,SAAgB,GAC7B,SAAS,CAyBX"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines the specific data type of a JSON value
|
|
3
|
+
* @param value The JSON value to analyze
|
|
4
|
+
* @returns The specific data type including "array" and "null" as distinct types
|
|
5
|
+
*/
|
|
6
|
+
export function getDataType(value) {
|
|
7
|
+
if (Array.isArray(value))
|
|
8
|
+
return "array";
|
|
9
|
+
if (value === null)
|
|
10
|
+
return "null";
|
|
11
|
+
return typeof value;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Attempts to parse a string as JSON, only for objects and arrays
|
|
15
|
+
* @param str The string to parse
|
|
16
|
+
* @returns Object with success boolean and either parsed data or original string
|
|
17
|
+
*/
|
|
18
|
+
export function tryParseJson(str) {
|
|
19
|
+
const trimmed = str?.trim();
|
|
20
|
+
if (trimmed &&
|
|
21
|
+
!(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
|
|
22
|
+
!(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
23
|
+
return { success: false, data: str };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return { success: true, data: JSON.parse(str) };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { success: false, data: str };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Updates a value at a specific path in a nested JSON structure
|
|
34
|
+
* @param obj The original JSON value
|
|
35
|
+
* @param path Array of keys/indices representing the path to the value
|
|
36
|
+
* @param value The new value to set
|
|
37
|
+
* @returns A new JSON value with the updated path
|
|
38
|
+
*/
|
|
39
|
+
export function updateValueAtPath(obj, path, value) {
|
|
40
|
+
if (path.length === 0)
|
|
41
|
+
return value;
|
|
42
|
+
if (obj === null || obj === undefined) {
|
|
43
|
+
obj = !isNaN(Number(path[0])) ? [] : {};
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(obj)) {
|
|
46
|
+
return updateArray(obj, path, value);
|
|
47
|
+
}
|
|
48
|
+
else if (typeof obj === "object" && obj !== null) {
|
|
49
|
+
return updateObject(obj, path, value);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.error(`Cannot update path ${path.join(".")} in non-object/array value:`, obj);
|
|
53
|
+
return obj;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Updates an array at a specific path
|
|
58
|
+
*/
|
|
59
|
+
function updateArray(array, path, value) {
|
|
60
|
+
const [index, ...restPath] = path;
|
|
61
|
+
const arrayIndex = Number(index);
|
|
62
|
+
if (isNaN(arrayIndex)) {
|
|
63
|
+
console.error(`Invalid array index: ${index}`);
|
|
64
|
+
return array;
|
|
65
|
+
}
|
|
66
|
+
if (arrayIndex < 0) {
|
|
67
|
+
console.error(`Array index out of bounds: ${arrayIndex} < 0`);
|
|
68
|
+
return array;
|
|
69
|
+
}
|
|
70
|
+
let newArray = [];
|
|
71
|
+
for (let i = 0; i < array.length; i++) {
|
|
72
|
+
newArray[i] = i in array ? array[i] : null;
|
|
73
|
+
}
|
|
74
|
+
if (arrayIndex >= newArray.length) {
|
|
75
|
+
const extendedArray = new Array(arrayIndex).fill(null);
|
|
76
|
+
// Copy over the existing elements (now guaranteed to be dense)
|
|
77
|
+
for (let i = 0; i < newArray.length; i++) {
|
|
78
|
+
extendedArray[i] = newArray[i];
|
|
79
|
+
}
|
|
80
|
+
newArray = extendedArray;
|
|
81
|
+
}
|
|
82
|
+
if (restPath.length === 0) {
|
|
83
|
+
newArray[arrayIndex] = value;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
newArray[arrayIndex] = updateValueAtPath(newArray[arrayIndex], restPath, value);
|
|
87
|
+
}
|
|
88
|
+
return newArray;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Updates an object at a specific path
|
|
92
|
+
*/
|
|
93
|
+
function updateObject(obj, path, value) {
|
|
94
|
+
const [key, ...restPath] = path;
|
|
95
|
+
// Validate object key
|
|
96
|
+
if (typeof key !== "string") {
|
|
97
|
+
console.error(`Invalid object key: ${key}`);
|
|
98
|
+
return obj;
|
|
99
|
+
}
|
|
100
|
+
const newObj = { ...obj };
|
|
101
|
+
if (restPath.length === 0) {
|
|
102
|
+
newObj[key] = value;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Ensure key exists
|
|
106
|
+
if (!(key in newObj)) {
|
|
107
|
+
newObj[key] = {};
|
|
108
|
+
}
|
|
109
|
+
newObj[key] = updateValueAtPath(newObj[key], restPath, value);
|
|
110
|
+
}
|
|
111
|
+
return newObj;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Gets a value at a specific path in a nested JSON structure
|
|
115
|
+
* @param obj The JSON value to traverse
|
|
116
|
+
* @param path Array of keys/indices representing the path to the value
|
|
117
|
+
* @param defaultValue Value to return if path doesn't exist
|
|
118
|
+
* @returns The value at the path, or defaultValue if not found
|
|
119
|
+
*/
|
|
120
|
+
export function getValueAtPath(obj, path, defaultValue = null) {
|
|
121
|
+
if (path.length === 0)
|
|
122
|
+
return obj;
|
|
123
|
+
const [first, ...rest] = path;
|
|
124
|
+
if (obj === null || obj === undefined) {
|
|
125
|
+
return defaultValue;
|
|
126
|
+
}
|
|
127
|
+
if (Array.isArray(obj)) {
|
|
128
|
+
const index = Number(first);
|
|
129
|
+
if (isNaN(index) || index < 0 || index >= obj.length) {
|
|
130
|
+
return defaultValue;
|
|
131
|
+
}
|
|
132
|
+
return getValueAtPath(obj[index], rest, defaultValue);
|
|
133
|
+
}
|
|
134
|
+
if (typeof obj === "object" && obj !== null) {
|
|
135
|
+
if (!(first in obj)) {
|
|
136
|
+
return defaultValue;
|
|
137
|
+
}
|
|
138
|
+
return getValueAtPath(obj[first], rest, defaultValue);
|
|
139
|
+
}
|
|
140
|
+
return defaultValue;
|
|
141
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JsonSchemaType } from "./jsonUtils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Cleans parameters by removing undefined, null, and empty string values for optional fields
|
|
4
|
+
* while preserving all values for required fields and fields with explicit default values.
|
|
5
|
+
*
|
|
6
|
+
* @param params - The parameters object to clean
|
|
7
|
+
* @param schema - The JSON schema defining which fields are required
|
|
8
|
+
* @returns Cleaned parameters object with optional empty fields omitted
|
|
9
|
+
*/
|
|
10
|
+
export declare function cleanParams(params: Record<string, unknown>, schema: JsonSchemaType): Record<string, unknown>;
|
|
11
|
+
//# sourceMappingURL=paramUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paramUtils.d.ts","sourceRoot":"","sources":["../../src/utils/paramUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8BzB"}
|