midnight-mcp 0.0.2 → 0.0.4
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/README.md +40 -20
- package/dist/pipeline/github.d.ts +18 -1
- package/dist/pipeline/github.js +132 -20
- package/dist/server.js +29 -7
- package/dist/tools/health.d.ts +91 -0
- package/dist/tools/health.js +91 -0
- package/dist/tools/index.d.ts +30 -4
- package/dist/tools/index.js +10 -3
- package/dist/tools/search.d.ts +3 -45
- package/dist/tools/search.js +101 -13
- package/dist/utils/cache.d.ts +77 -0
- package/dist/utils/cache.js +172 -0
- package/dist/utils/errors.d.ts +45 -0
- package/dist/utils/errors.js +95 -0
- package/dist/utils/health.d.ts +29 -0
- package/dist/utils/health.js +132 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/logger.d.ts +30 -1
- package/dist/utils/logger.js +68 -3
- package/dist/utils/rate-limit.d.ts +61 -0
- package/dist/utils/rate-limit.js +148 -0
- package/dist/utils/validation.d.ts +52 -0
- package/dist/utils/validation.js +255 -0
- package/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation and sanitization utilities
|
|
3
|
+
* Protects against injection attacks and malformed inputs
|
|
4
|
+
*/
|
|
5
|
+
// Maximum allowed lengths for different input types
|
|
6
|
+
const MAX_LENGTHS = {
|
|
7
|
+
query: 1000,
|
|
8
|
+
path: 500,
|
|
9
|
+
repository: 100,
|
|
10
|
+
ref: 100,
|
|
11
|
+
generic: 500,
|
|
12
|
+
};
|
|
13
|
+
// Patterns that could indicate injection attempts
|
|
14
|
+
const DANGEROUS_PATTERNS = [
|
|
15
|
+
/[<>]/g, // HTML/XML injection
|
|
16
|
+
/javascript:/gi, // JS protocol
|
|
17
|
+
/data:/gi, // Data URLs
|
|
18
|
+
/\0/g, // Null bytes
|
|
19
|
+
/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, // Control characters (except newline, tab)
|
|
20
|
+
];
|
|
21
|
+
// Valid characters for different input types
|
|
22
|
+
const VALID_PATTERNS = {
|
|
23
|
+
// Repository names: alphanumeric, hyphens, underscores, slashes
|
|
24
|
+
repository: /^[a-zA-Z0-9_\-./]+$/,
|
|
25
|
+
// Git refs: alphanumeric, hyphens, underscores, dots, slashes
|
|
26
|
+
ref: /^[a-zA-Z0-9_\-./]+$/,
|
|
27
|
+
// File paths: most characters except dangerous ones
|
|
28
|
+
path: /^[a-zA-Z0-9_\-./\s]+$/,
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Sanitize a string by removing dangerous patterns
|
|
32
|
+
*/
|
|
33
|
+
export function sanitizeString(input, maxLength = MAX_LENGTHS.generic) {
|
|
34
|
+
if (!input || typeof input !== "string") {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
let sanitized = input;
|
|
38
|
+
// Remove dangerous patterns
|
|
39
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
40
|
+
sanitized = sanitized.replace(pattern, "");
|
|
41
|
+
}
|
|
42
|
+
// Trim whitespace
|
|
43
|
+
sanitized = sanitized.trim();
|
|
44
|
+
// Truncate to max length
|
|
45
|
+
if (sanitized.length > maxLength) {
|
|
46
|
+
sanitized = sanitized.substring(0, maxLength);
|
|
47
|
+
}
|
|
48
|
+
return sanitized;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate and sanitize a search query
|
|
52
|
+
*/
|
|
53
|
+
export function validateQuery(query) {
|
|
54
|
+
const warnings = [];
|
|
55
|
+
const errors = [];
|
|
56
|
+
if (query === null || query === undefined) {
|
|
57
|
+
return {
|
|
58
|
+
isValid: false,
|
|
59
|
+
sanitized: "",
|
|
60
|
+
warnings,
|
|
61
|
+
errors: ["Query is required"],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (typeof query !== "string") {
|
|
65
|
+
return {
|
|
66
|
+
isValid: false,
|
|
67
|
+
sanitized: "",
|
|
68
|
+
warnings,
|
|
69
|
+
errors: ["Query must be a string"],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const sanitized = sanitizeString(query, MAX_LENGTHS.query);
|
|
73
|
+
if (sanitized.length === 0) {
|
|
74
|
+
errors.push("Query cannot be empty after sanitization");
|
|
75
|
+
}
|
|
76
|
+
if (sanitized.length < 2) {
|
|
77
|
+
warnings.push("Query is very short, results may be limited");
|
|
78
|
+
}
|
|
79
|
+
if (query.length !== sanitized.length) {
|
|
80
|
+
warnings.push("Query was sanitized to remove potentially dangerous characters");
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
isValid: errors.length === 0,
|
|
84
|
+
sanitized,
|
|
85
|
+
warnings,
|
|
86
|
+
errors,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validate and sanitize a repository name
|
|
91
|
+
*/
|
|
92
|
+
export function validateRepository(repo) {
|
|
93
|
+
const warnings = [];
|
|
94
|
+
const errors = [];
|
|
95
|
+
if (repo === null || repo === undefined) {
|
|
96
|
+
return {
|
|
97
|
+
isValid: false,
|
|
98
|
+
sanitized: "",
|
|
99
|
+
warnings,
|
|
100
|
+
errors: ["Repository name is required"],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (typeof repo !== "string") {
|
|
104
|
+
return {
|
|
105
|
+
isValid: false,
|
|
106
|
+
sanitized: "",
|
|
107
|
+
warnings,
|
|
108
|
+
errors: ["Repository name must be a string"],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const sanitized = sanitizeString(repo, MAX_LENGTHS.repository);
|
|
112
|
+
if (!VALID_PATTERNS.repository.test(sanitized)) {
|
|
113
|
+
errors.push("Repository name contains invalid characters");
|
|
114
|
+
}
|
|
115
|
+
// Check for path traversal attempts
|
|
116
|
+
if (sanitized.includes("..")) {
|
|
117
|
+
errors.push("Repository name cannot contain path traversal sequences");
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
isValid: errors.length === 0,
|
|
121
|
+
sanitized,
|
|
122
|
+
warnings,
|
|
123
|
+
errors,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Validate and sanitize a file path
|
|
128
|
+
*/
|
|
129
|
+
export function validatePath(path) {
|
|
130
|
+
const warnings = [];
|
|
131
|
+
const errors = [];
|
|
132
|
+
if (path === null || path === undefined) {
|
|
133
|
+
return {
|
|
134
|
+
isValid: false,
|
|
135
|
+
sanitized: "",
|
|
136
|
+
warnings,
|
|
137
|
+
errors: ["Path is required"],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (typeof path !== "string") {
|
|
141
|
+
return {
|
|
142
|
+
isValid: false,
|
|
143
|
+
sanitized: "",
|
|
144
|
+
warnings,
|
|
145
|
+
errors: ["Path must be a string"],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
let sanitized = sanitizeString(path, MAX_LENGTHS.path);
|
|
149
|
+
// Normalize path separators
|
|
150
|
+
sanitized = sanitized.replace(/\\/g, "/");
|
|
151
|
+
// Remove leading slashes
|
|
152
|
+
sanitized = sanitized.replace(/^\/+/, "");
|
|
153
|
+
// Check for path traversal attempts
|
|
154
|
+
if (sanitized.includes("..")) {
|
|
155
|
+
errors.push("Path cannot contain traversal sequences (..)");
|
|
156
|
+
}
|
|
157
|
+
// Check for absolute paths
|
|
158
|
+
if (path.startsWith("/") || /^[a-zA-Z]:/.test(path)) {
|
|
159
|
+
warnings.push("Absolute paths are converted to relative paths");
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
isValid: errors.length === 0,
|
|
163
|
+
sanitized,
|
|
164
|
+
warnings,
|
|
165
|
+
errors,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Validate and sanitize a git ref (branch, tag, or commit)
|
|
170
|
+
*/
|
|
171
|
+
export function validateRef(ref) {
|
|
172
|
+
const warnings = [];
|
|
173
|
+
const errors = [];
|
|
174
|
+
// Ref is optional, so null/undefined is valid
|
|
175
|
+
if (ref === null || ref === undefined) {
|
|
176
|
+
return {
|
|
177
|
+
isValid: true,
|
|
178
|
+
sanitized: "",
|
|
179
|
+
warnings,
|
|
180
|
+
errors,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (typeof ref !== "string") {
|
|
184
|
+
return {
|
|
185
|
+
isValid: false,
|
|
186
|
+
sanitized: "",
|
|
187
|
+
warnings,
|
|
188
|
+
errors: ["Ref must be a string"],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const sanitized = sanitizeString(ref, MAX_LENGTHS.ref);
|
|
192
|
+
if (!VALID_PATTERNS.ref.test(sanitized)) {
|
|
193
|
+
errors.push("Ref contains invalid characters");
|
|
194
|
+
}
|
|
195
|
+
// Check for path traversal
|
|
196
|
+
if (sanitized.includes("..")) {
|
|
197
|
+
errors.push("Ref cannot contain path traversal sequences");
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
isValid: errors.length === 0,
|
|
201
|
+
sanitized,
|
|
202
|
+
warnings,
|
|
203
|
+
errors,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Validate a numeric input within bounds
|
|
208
|
+
*/
|
|
209
|
+
export function validateNumber(value, options) {
|
|
210
|
+
const { min = 1, max = 100, defaultValue } = options;
|
|
211
|
+
if (value === null || value === undefined) {
|
|
212
|
+
return { isValid: true, value: defaultValue };
|
|
213
|
+
}
|
|
214
|
+
const num = typeof value === "string" ? parseInt(value, 10) : value;
|
|
215
|
+
if (typeof num !== "number" || isNaN(num)) {
|
|
216
|
+
return {
|
|
217
|
+
isValid: false,
|
|
218
|
+
value: defaultValue,
|
|
219
|
+
error: "Must be a valid number",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (num < min) {
|
|
223
|
+
return { isValid: true, value: min };
|
|
224
|
+
}
|
|
225
|
+
if (num > max) {
|
|
226
|
+
return { isValid: true, value: max };
|
|
227
|
+
}
|
|
228
|
+
return { isValid: true, value: num };
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Validate tool arguments with automatic sanitization
|
|
232
|
+
*/
|
|
233
|
+
export function validateToolArgs(args, validators) {
|
|
234
|
+
const errors = [];
|
|
235
|
+
const warnings = [];
|
|
236
|
+
const sanitized = { ...args };
|
|
237
|
+
for (const [key, validator] of Object.entries(validators)) {
|
|
238
|
+
if (validator && key in args) {
|
|
239
|
+
const result = validator(args[key]);
|
|
240
|
+
if (!result.isValid) {
|
|
241
|
+
errors.push(`${key}: ${result.errors.join(", ")}`);
|
|
242
|
+
}
|
|
243
|
+
warnings.push(...result.warnings.map((w) => `${key}: ${w}`));
|
|
244
|
+
sanitized[key] =
|
|
245
|
+
result.sanitized || args[key];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
isValid: errors.length === 0,
|
|
250
|
+
sanitized,
|
|
251
|
+
errors,
|
|
252
|
+
warnings,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=validation.js.map
|