synapse-mcp 1.0.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/README.md +607 -0
- package/dist/constants.d.ts +23 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +58 -0
- package/dist/constants.js.map +1 -0
- package/dist/formatters/index.d.ts +275 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +461 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +178 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/common.d.ts +48 -0
- package/dist/schemas/common.d.ts.map +1 -0
- package/dist/schemas/common.js +69 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/schemas/discriminator.d.ts +20 -0
- package/dist/schemas/discriminator.d.ts.map +1 -0
- package/dist/schemas/discriminator.js +25 -0
- package/dist/schemas/discriminator.js.map +1 -0
- package/dist/schemas/flux/compose.d.ts +93 -0
- package/dist/schemas/flux/compose.d.ts.map +1 -0
- package/dist/schemas/flux/compose.js +112 -0
- package/dist/schemas/flux/compose.js.map +1 -0
- package/dist/schemas/flux/container.d.ts +144 -0
- package/dist/schemas/flux/container.d.ts.map +1 -0
- package/dist/schemas/flux/container.js +163 -0
- package/dist/schemas/flux/container.js.map +1 -0
- package/dist/schemas/flux/docker.d.ts +91 -0
- package/dist/schemas/flux/docker.d.ts.map +1 -0
- package/dist/schemas/flux/docker.js +101 -0
- package/dist/schemas/flux/docker.js.map +1 -0
- package/dist/schemas/flux/host.d.ts +61 -0
- package/dist/schemas/flux/host.d.ts.map +1 -0
- package/dist/schemas/flux/host.js +72 -0
- package/dist/schemas/flux/host.js.map +1 -0
- package/dist/schemas/flux/index.d.ts +20 -0
- package/dist/schemas/flux/index.d.ts.map +1 -0
- package/dist/schemas/flux/index.js +88 -0
- package/dist/schemas/flux/index.js.map +1 -0
- package/dist/schemas/index.d.ts +11 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +11 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/scout/index.d.ts +151 -0
- package/dist/schemas/scout/index.d.ts.map +1 -0
- package/dist/schemas/scout/index.js +41 -0
- package/dist/schemas/scout/index.js.map +1 -0
- package/dist/schemas/scout/logs.d.ts +48 -0
- package/dist/schemas/scout/logs.d.ts.map +1 -0
- package/dist/schemas/scout/logs.js +47 -0
- package/dist/schemas/scout/logs.js.map +1 -0
- package/dist/schemas/scout/simple.d.ts +68 -0
- package/dist/schemas/scout/simple.d.ts.map +1 -0
- package/dist/schemas/scout/simple.js +75 -0
- package/dist/schemas/scout/simple.js.map +1 -0
- package/dist/schemas/scout/zfs.d.ts +37 -0
- package/dist/schemas/scout/zfs.d.ts.map +1 -0
- package/dist/schemas/scout/zfs.js +36 -0
- package/dist/schemas/scout/zfs.js.map +1 -0
- package/dist/schemas/unified.d.ts +674 -0
- package/dist/schemas/unified.d.ts.map +1 -0
- package/dist/schemas/unified.js +453 -0
- package/dist/schemas/unified.js.map +1 -0
- package/dist/services/compose.d.ts +107 -0
- package/dist/services/compose.d.ts.map +1 -0
- package/dist/services/compose.js +308 -0
- package/dist/services/compose.js.map +1 -0
- package/dist/services/container.d.ts +69 -0
- package/dist/services/container.d.ts.map +1 -0
- package/dist/services/container.js +111 -0
- package/dist/services/container.js.map +1 -0
- package/dist/services/docker.d.ts +243 -0
- package/dist/services/docker.d.ts.map +1 -0
- package/dist/services/docker.js +812 -0
- package/dist/services/docker.js.map +1 -0
- package/dist/services/file-service.d.ts +79 -0
- package/dist/services/file-service.d.ts.map +1 -0
- package/dist/services/file-service.js +226 -0
- package/dist/services/file-service.js.map +1 -0
- package/dist/services/interfaces.d.ts +537 -0
- package/dist/services/interfaces.d.ts.map +1 -0
- package/dist/services/interfaces.js +2 -0
- package/dist/services/interfaces.js.map +1 -0
- package/dist/services/ssh-pool-exec.d.ts +10 -0
- package/dist/services/ssh-pool-exec.d.ts.map +1 -0
- package/dist/services/ssh-pool-exec.js +10 -0
- package/dist/services/ssh-pool-exec.js.map +1 -0
- package/dist/services/ssh-pool.d.ts +66 -0
- package/dist/services/ssh-pool.d.ts.map +1 -0
- package/dist/services/ssh-pool.js +253 -0
- package/dist/services/ssh-pool.js.map +1 -0
- package/dist/services/ssh-service.d.ts +39 -0
- package/dist/services/ssh-service.d.ts.map +1 -0
- package/dist/services/ssh-service.js +143 -0
- package/dist/services/ssh-service.js.map +1 -0
- package/dist/services/ssh.d.ts +37 -0
- package/dist/services/ssh.d.ts.map +1 -0
- package/dist/services/ssh.js +50 -0
- package/dist/services/ssh.js.map +1 -0
- package/dist/tools/flux.d.ts +14 -0
- package/dist/tools/flux.d.ts.map +1 -0
- package/dist/tools/flux.js +86 -0
- package/dist/tools/flux.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +43 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/scout.d.ts +14 -0
- package/dist/tools/scout.d.ts.map +1 -0
- package/dist/tools/scout.js +96 -0
- package/dist/tools/scout.js.map +1 -0
- package/dist/tools/unified.d.ts +7 -0
- package/dist/tools/unified.d.ts.map +1 -0
- package/dist/tools/unified.js +827 -0
- package/dist/tools/unified.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/errors.d.ts +60 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +131 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/help.d.ts +69 -0
- package/dist/utils/help.d.ts.map +1 -0
- package/dist/utils/help.js +259 -0
- package/dist/utils/help.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/path-security.d.ts +64 -0
- package/dist/utils/path-security.d.ts.map +1 -0
- package/dist/utils/path-security.js +138 -0
- package/dist/utils/path-security.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Help Handler - Auto-generate documentation from Zod discriminated union schemas
|
|
3
|
+
*
|
|
4
|
+
* Provides schema introspection to generate help text for MCP tools.
|
|
5
|
+
* Supports schemas wrapped in z.preprocess() for backward compatibility
|
|
6
|
+
* with composite discriminator patterns.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: This module accesses Zod internal implementation details (_def, options, etc.)
|
|
9
|
+
* which are not part of the public API. When upgrading Zod:
|
|
10
|
+
* 1. Run full test suite to verify help generation still works
|
|
11
|
+
* 2. Check Zod changelog for changes to internal structure
|
|
12
|
+
* 3. Update this module if internal structure changes
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Safely access _def from a Zod schema
|
|
16
|
+
*/
|
|
17
|
+
function getDef(schema) {
|
|
18
|
+
return schema._def ?? {};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Unwrap z.preprocess wrapper to access inner discriminated union schema
|
|
22
|
+
*
|
|
23
|
+
* In Zod 4.x, z.preprocess() creates a pipe with:
|
|
24
|
+
* - _def.type === 'pipe'
|
|
25
|
+
* - _def.out containing the inner schema
|
|
26
|
+
*/
|
|
27
|
+
function unwrapSchema(schema) {
|
|
28
|
+
const def = getDef(schema);
|
|
29
|
+
// Check if schema is wrapped in z.preprocess (Zod 4.x uses pipe internally)
|
|
30
|
+
if (def.type === "pipe" && def.out) {
|
|
31
|
+
return def.out;
|
|
32
|
+
}
|
|
33
|
+
// Zod 3.x compatibility: check for innerType
|
|
34
|
+
if (def.innerType) {
|
|
35
|
+
return def.innerType;
|
|
36
|
+
}
|
|
37
|
+
return schema;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the base type name from a potentially wrapped Zod schema
|
|
41
|
+
*
|
|
42
|
+
* Handles default(), optional(), nullable() wrapping to get the underlying type.
|
|
43
|
+
*/
|
|
44
|
+
function getBaseTypeName(schema) {
|
|
45
|
+
const def = getDef(schema);
|
|
46
|
+
// Handle wrapped types: default, optional, nullable, etc.
|
|
47
|
+
if (def.innerType) {
|
|
48
|
+
return getBaseTypeName(def.innerType);
|
|
49
|
+
}
|
|
50
|
+
// Handle union type
|
|
51
|
+
if (def.type === "union" && def.options) {
|
|
52
|
+
const types = def.options.map((opt) => getBaseTypeName(opt));
|
|
53
|
+
return types.join(" | ");
|
|
54
|
+
}
|
|
55
|
+
// Handle enum type
|
|
56
|
+
if (def.type === "enum" && def.values) {
|
|
57
|
+
return def.values.map((v) => `"${v}"`).join(" | ");
|
|
58
|
+
}
|
|
59
|
+
// Handle native enum
|
|
60
|
+
if (def.type === "enum" || def.type === "nativeEnum") {
|
|
61
|
+
return "enum";
|
|
62
|
+
}
|
|
63
|
+
// Return the base type
|
|
64
|
+
return def.type ?? "unknown";
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get default value from a schema if present
|
|
68
|
+
*/
|
|
69
|
+
function getDefaultValue(schema) {
|
|
70
|
+
const def = getDef(schema);
|
|
71
|
+
if (def.type === "default" && def.defaultValue !== undefined) {
|
|
72
|
+
try {
|
|
73
|
+
// defaultValue is a getter function in Zod 4.x
|
|
74
|
+
return typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get options from a discriminated union schema
|
|
84
|
+
*/
|
|
85
|
+
function getOptions(schema) {
|
|
86
|
+
// Try direct access (Zod 4.x)
|
|
87
|
+
const directOptions = schema.options;
|
|
88
|
+
if (directOptions) {
|
|
89
|
+
return directOptions;
|
|
90
|
+
}
|
|
91
|
+
// Try via _def
|
|
92
|
+
const def = getDef(schema);
|
|
93
|
+
return def.options;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if a schema is a nested discriminated union (has options but no shape)
|
|
97
|
+
*/
|
|
98
|
+
function isNestedDiscriminatedUnion(schema) {
|
|
99
|
+
const def = getDef(schema);
|
|
100
|
+
const hasOptions = !!getOptions(schema);
|
|
101
|
+
const hasShape = !!schema.shape;
|
|
102
|
+
return hasOptions && !hasShape && (def.type === 'discriminatedUnion' || def.type === 'union');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Extract help entries from a single ZodObject schema
|
|
106
|
+
*/
|
|
107
|
+
function extractObjectHelpEntry(option, prefix) {
|
|
108
|
+
// Access shape from ZodObject
|
|
109
|
+
const shape = option.shape;
|
|
110
|
+
if (!shape) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
// Find literal fields for discriminator
|
|
114
|
+
const literals = [];
|
|
115
|
+
for (const key of Object.keys(shape)) {
|
|
116
|
+
const fieldSchema = shape[key];
|
|
117
|
+
const fieldDef = getDef(fieldSchema);
|
|
118
|
+
// In Zod 4.x, literals use _def.values array
|
|
119
|
+
if (fieldDef.type === "literal" && fieldDef.values) {
|
|
120
|
+
literals.push({ key, value: fieldDef.values[0] });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (literals.length === 0) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
// Build discriminator value: action or action:subaction
|
|
127
|
+
let discriminatorValue;
|
|
128
|
+
const actionLiteral = literals.find(l => l.key === 'action');
|
|
129
|
+
const subactionLiteral = literals.find(l => l.key === 'subaction');
|
|
130
|
+
if (subactionLiteral && actionLiteral) {
|
|
131
|
+
// Nested pattern: action:subaction (e.g., "zfs:pools")
|
|
132
|
+
discriminatorValue = `${actionLiteral.value}:${subactionLiteral.value}`;
|
|
133
|
+
}
|
|
134
|
+
else if (actionLiteral) {
|
|
135
|
+
discriminatorValue = actionLiteral.value;
|
|
136
|
+
}
|
|
137
|
+
else if (subactionLiteral) {
|
|
138
|
+
discriminatorValue = prefix ? `${prefix}:${subactionLiteral.value}` : subactionLiteral.value;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
discriminatorValue = literals[0].value;
|
|
142
|
+
}
|
|
143
|
+
// Extract parameters (excluding discriminator fields)
|
|
144
|
+
const parameters = Object.entries(shape)
|
|
145
|
+
.filter(([key]) =>
|
|
146
|
+
// Skip discriminator-related fields
|
|
147
|
+
key !== "action_subaction" && key !== "action" && key !== "subaction")
|
|
148
|
+
.map(([name, fieldSchema]) => {
|
|
149
|
+
return {
|
|
150
|
+
name,
|
|
151
|
+
type: getBaseTypeName(fieldSchema),
|
|
152
|
+
description: fieldSchema.description,
|
|
153
|
+
required: !fieldSchema.isOptional(),
|
|
154
|
+
default: getDefaultValue(fieldSchema)
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
discriminator: discriminatorValue,
|
|
159
|
+
description: option.description ?? "",
|
|
160
|
+
parameters
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Generate help documentation from discriminated union or union schema
|
|
165
|
+
*
|
|
166
|
+
* Handles:
|
|
167
|
+
* - Schemas wrapped in z.preprocess()
|
|
168
|
+
* - z.discriminatedUnion with composite discriminator (Flux)
|
|
169
|
+
* - z.union with nested z.discriminatedUnion (Scout)
|
|
170
|
+
*
|
|
171
|
+
* @param schema - A Zod discriminated union or union schema
|
|
172
|
+
* @param topic - Optional discriminator value to filter results
|
|
173
|
+
* @returns Array of help entries for matching actions
|
|
174
|
+
*/
|
|
175
|
+
export function generateHelp(schema, topic) {
|
|
176
|
+
// Unwrap z.preprocess if present
|
|
177
|
+
const actualSchema = unwrapSchema(schema);
|
|
178
|
+
// Access options from discriminated union or union
|
|
179
|
+
const options = getOptions(actualSchema);
|
|
180
|
+
if (!options || !Array.isArray(options)) {
|
|
181
|
+
throw new Error("Schema is not a discriminated union or union");
|
|
182
|
+
}
|
|
183
|
+
const entries = [];
|
|
184
|
+
for (const option of options) {
|
|
185
|
+
// Check if this option is a nested discriminated union (like zfs or logs in Scout)
|
|
186
|
+
if (isNestedDiscriminatedUnion(option)) {
|
|
187
|
+
const nestedOptions = getOptions(option);
|
|
188
|
+
if (nestedOptions) {
|
|
189
|
+
for (const nestedOption of nestedOptions) {
|
|
190
|
+
const entry = extractObjectHelpEntry(nestedOption);
|
|
191
|
+
if (entry) {
|
|
192
|
+
entries.push(entry);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
// Regular ZodObject option
|
|
199
|
+
const entry = extractObjectHelpEntry(option);
|
|
200
|
+
if (entry) {
|
|
201
|
+
entries.push(entry);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Filter by topic if provided
|
|
206
|
+
if (topic) {
|
|
207
|
+
return entries.filter((e) => e.discriminator === topic || e.discriminator.startsWith(topic + ':'));
|
|
208
|
+
}
|
|
209
|
+
return entries;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Format help entries as markdown
|
|
213
|
+
*
|
|
214
|
+
* @param entries - Array of help entries from generateHelp
|
|
215
|
+
* @returns Formatted markdown string
|
|
216
|
+
*/
|
|
217
|
+
export function formatHelpMarkdown(entries) {
|
|
218
|
+
if (entries.length === 0) {
|
|
219
|
+
return "No help available for the specified topic.";
|
|
220
|
+
}
|
|
221
|
+
return entries
|
|
222
|
+
.map((entry) => {
|
|
223
|
+
let md = `## ${entry.discriminator}\n\n`;
|
|
224
|
+
if (entry.description) {
|
|
225
|
+
md += `${entry.description}\n\n`;
|
|
226
|
+
}
|
|
227
|
+
if (entry.parameters.length > 0) {
|
|
228
|
+
md += "**Parameters:**\n\n";
|
|
229
|
+
entry.parameters.forEach((param) => {
|
|
230
|
+
const required = param.required ? " (required)" : " (optional)";
|
|
231
|
+
const defaultVal = param.default !== undefined ? `, default: ${JSON.stringify(param.default)}` : "";
|
|
232
|
+
md += `- **${param.name}** (${param.type}${required}${defaultVal})`;
|
|
233
|
+
if (param.description) {
|
|
234
|
+
md += ` - ${param.description}`;
|
|
235
|
+
}
|
|
236
|
+
md += "\n";
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return md;
|
|
240
|
+
})
|
|
241
|
+
.join("\n---\n\n");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Format help entries as JSON
|
|
245
|
+
*
|
|
246
|
+
* Uses 'action' as the key name in output for consistency with tool parameters.
|
|
247
|
+
*
|
|
248
|
+
* @param entries - Array of help entries from generateHelp
|
|
249
|
+
* @returns JSON string
|
|
250
|
+
*/
|
|
251
|
+
export function formatHelpJson(entries) {
|
|
252
|
+
const jsonEntries = entries.map((entry) => ({
|
|
253
|
+
action: entry.discriminator,
|
|
254
|
+
description: entry.description,
|
|
255
|
+
parameters: entry.parameters
|
|
256
|
+
}));
|
|
257
|
+
return JSON.stringify(jsonEntries, null, 2);
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=help.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/utils/help.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { validateSecurePath, HostSecurityError, validateHostFormat, escapeShellArg, isSystemPath } from "./path-security.js";
|
|
2
|
+
export { generateHelp, formatHelpMarkdown, formatHelpJson } from "./help.js";
|
|
3
|
+
export type { HelpEntry, HelpJsonEntry } from "./help.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC7E,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Explicit named exports for better IDE support and tree-shaking
|
|
2
|
+
export { validateSecurePath, HostSecurityError, validateHostFormat, escapeShellArg, isSystemPath } from "./path-security.js";
|
|
3
|
+
export { generateHelp, formatHelpMarkdown, formatHelpJson } from "./help.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Security Utilities
|
|
3
|
+
*
|
|
4
|
+
* SECURITY: Path Traversal Protection (CWE-22) & Command Injection Prevention (CWE-78)
|
|
5
|
+
*
|
|
6
|
+
* This module provides utilities to prevent directory traversal attacks
|
|
7
|
+
* in file path parameters. Used by docker.ts buildImage() to validate
|
|
8
|
+
* build context and Dockerfile paths.
|
|
9
|
+
*
|
|
10
|
+
* Also provides host validation to prevent command injection attacks
|
|
11
|
+
* when hostnames are used in SSH commands.
|
|
12
|
+
*
|
|
13
|
+
* CVSS 7.4 (HIGH) - Prevents attackers from using paths like:
|
|
14
|
+
* - ../../../etc/passwd
|
|
15
|
+
* - /valid/../../../etc/passwd
|
|
16
|
+
* - /path/./to/../../sensitive
|
|
17
|
+
*
|
|
18
|
+
* @see https://cwe.mitre.org/data/definitions/22.html
|
|
19
|
+
* @see https://cwe.mitre.org/data/definitions/78.html
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Security error for invalid host format
|
|
23
|
+
*/
|
|
24
|
+
export declare class HostSecurityError extends Error {
|
|
25
|
+
readonly host: string;
|
|
26
|
+
constructor(message: string, host: string);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validates hostname format to prevent command injection
|
|
30
|
+
*
|
|
31
|
+
* @param host - Hostname to validate
|
|
32
|
+
* @throws HostSecurityError if host contains dangerous characters
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateHostFormat(host: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Escapes a string for safe use as a shell argument.
|
|
37
|
+
* Uses single quotes with proper escaping for embedded single quotes.
|
|
38
|
+
*
|
|
39
|
+
* @param arg - String to escape
|
|
40
|
+
* @returns Safely quoted string
|
|
41
|
+
*/
|
|
42
|
+
export declare function escapeShellArg(arg: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Checks if a path is a system path that should be protected
|
|
45
|
+
*
|
|
46
|
+
* @param path - Path to check
|
|
47
|
+
* @returns true if path is in a system directory
|
|
48
|
+
*/
|
|
49
|
+
export declare function isSystemPath(path: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Validates that a file path is safe from directory traversal attacks
|
|
52
|
+
*
|
|
53
|
+
* Rules:
|
|
54
|
+
* 1. Must be absolute path (starts with /)
|
|
55
|
+
* 2. Cannot contain .. (parent directory)
|
|
56
|
+
* 3. Cannot contain . as a path component (except in filenames)
|
|
57
|
+
* 4. Must contain only allowed characters: a-zA-Z0-9._-/
|
|
58
|
+
*
|
|
59
|
+
* @param path - The file path to validate
|
|
60
|
+
* @param paramName - Name of the parameter (for error messages)
|
|
61
|
+
* @throws Error if path contains directory traversal or is invalid
|
|
62
|
+
*/
|
|
63
|
+
export declare function validateSecurePath(path: string, paramName: string): void;
|
|
64
|
+
//# sourceMappingURL=path-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-security.d.ts","sourceRoot":"","sources":["../../src/utils/path-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;aAGxB,IAAI,EAAE,MAAM;gBAD5B,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,MAAM;CAK/B;AAQD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAYrD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIlD;AAiBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAyCxE"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Security Utilities
|
|
3
|
+
*
|
|
4
|
+
* SECURITY: Path Traversal Protection (CWE-22) & Command Injection Prevention (CWE-78)
|
|
5
|
+
*
|
|
6
|
+
* This module provides utilities to prevent directory traversal attacks
|
|
7
|
+
* in file path parameters. Used by docker.ts buildImage() to validate
|
|
8
|
+
* build context and Dockerfile paths.
|
|
9
|
+
*
|
|
10
|
+
* Also provides host validation to prevent command injection attacks
|
|
11
|
+
* when hostnames are used in SSH commands.
|
|
12
|
+
*
|
|
13
|
+
* CVSS 7.4 (HIGH) - Prevents attackers from using paths like:
|
|
14
|
+
* - ../../../etc/passwd
|
|
15
|
+
* - /valid/../../../etc/passwd
|
|
16
|
+
* - /path/./to/../../sensitive
|
|
17
|
+
*
|
|
18
|
+
* @see https://cwe.mitre.org/data/definitions/22.html
|
|
19
|
+
* @see https://cwe.mitre.org/data/definitions/78.html
|
|
20
|
+
*/
|
|
21
|
+
import { resolve } from "node:path";
|
|
22
|
+
/**
|
|
23
|
+
* Security error for invalid host format
|
|
24
|
+
*/
|
|
25
|
+
export class HostSecurityError extends Error {
|
|
26
|
+
host;
|
|
27
|
+
constructor(message, host) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.host = host;
|
|
30
|
+
this.name = "HostSecurityError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Pattern for valid hostnames: alphanumeric, dots, hyphens, underscores
|
|
34
|
+
const VALID_HOST_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
35
|
+
// Dangerous shell characters that could enable command injection
|
|
36
|
+
const DANGEROUS_HOST_CHARS = /[;|$`&<>(){}[\]'"\\!#*?]/;
|
|
37
|
+
/**
|
|
38
|
+
* Validates hostname format to prevent command injection
|
|
39
|
+
*
|
|
40
|
+
* @param host - Hostname to validate
|
|
41
|
+
* @throws HostSecurityError if host contains dangerous characters
|
|
42
|
+
*/
|
|
43
|
+
export function validateHostFormat(host) {
|
|
44
|
+
if (!host || host.length === 0) {
|
|
45
|
+
throw new HostSecurityError("Host cannot be empty", host);
|
|
46
|
+
}
|
|
47
|
+
if (DANGEROUS_HOST_CHARS.test(host)) {
|
|
48
|
+
throw new HostSecurityError(`Invalid characters in hostname: ${host.substring(0, 50)}`, host);
|
|
49
|
+
}
|
|
50
|
+
if (!VALID_HOST_PATTERN.test(host)) {
|
|
51
|
+
throw new HostSecurityError(`Invalid hostname format: ${host.substring(0, 50)}`, host);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Escapes a string for safe use as a shell argument.
|
|
56
|
+
* Uses single quotes with proper escaping for embedded single quotes.
|
|
57
|
+
*
|
|
58
|
+
* @param arg - String to escape
|
|
59
|
+
* @returns Safely quoted string
|
|
60
|
+
*/
|
|
61
|
+
export function escapeShellArg(arg) {
|
|
62
|
+
// Single quote the entire string, escaping any embedded single quotes
|
|
63
|
+
// by ending the quote, adding an escaped single quote, and starting a new quote
|
|
64
|
+
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* System paths that should trigger warnings when used as transfer targets
|
|
68
|
+
*/
|
|
69
|
+
const SYSTEM_PATH_PREFIXES = [
|
|
70
|
+
"/etc",
|
|
71
|
+
"/bin",
|
|
72
|
+
"/sbin",
|
|
73
|
+
"/usr/bin",
|
|
74
|
+
"/usr/sbin",
|
|
75
|
+
"/lib",
|
|
76
|
+
"/lib64",
|
|
77
|
+
"/boot",
|
|
78
|
+
"/root"
|
|
79
|
+
];
|
|
80
|
+
/**
|
|
81
|
+
* Checks if a path is a system path that should be protected
|
|
82
|
+
*
|
|
83
|
+
* @param path - Path to check
|
|
84
|
+
* @returns true if path is in a system directory
|
|
85
|
+
*/
|
|
86
|
+
export function isSystemPath(path) {
|
|
87
|
+
return SYSTEM_PATH_PREFIXES.some((prefix) => path === prefix || path.startsWith(prefix + "/"));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validates that a file path is safe from directory traversal attacks
|
|
91
|
+
*
|
|
92
|
+
* Rules:
|
|
93
|
+
* 1. Must be absolute path (starts with /)
|
|
94
|
+
* 2. Cannot contain .. (parent directory)
|
|
95
|
+
* 3. Cannot contain . as a path component (except in filenames)
|
|
96
|
+
* 4. Must contain only allowed characters: a-zA-Z0-9._-/
|
|
97
|
+
*
|
|
98
|
+
* @param path - The file path to validate
|
|
99
|
+
* @param paramName - Name of the parameter (for error messages)
|
|
100
|
+
* @throws Error if path contains directory traversal or is invalid
|
|
101
|
+
*/
|
|
102
|
+
export function validateSecurePath(path, paramName) {
|
|
103
|
+
// 1. Check for empty path
|
|
104
|
+
if (!path || path.length === 0) {
|
|
105
|
+
throw new Error(`${paramName}: Path cannot be empty`);
|
|
106
|
+
}
|
|
107
|
+
// 2. Character validation - only allow alphanumeric, dots, hyphens, underscores, forward slashes
|
|
108
|
+
if (!/^[a-zA-Z0-9._\-/]+$/.test(path)) {
|
|
109
|
+
throw new Error(`${paramName}: Invalid characters in path: ${path}`);
|
|
110
|
+
}
|
|
111
|
+
// 3. Split path into components and check for ".." traversal first
|
|
112
|
+
const components = path.split("/").filter((c) => c.length > 0);
|
|
113
|
+
for (const component of components) {
|
|
114
|
+
// Reject ".." (parent directory traversal) - check this first
|
|
115
|
+
if (component === "..") {
|
|
116
|
+
throw new Error(`${paramName}: directory traversal (..) not allowed in path: ${path}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 4. Must be absolute path (starts with /) - checked after .. but before .
|
|
120
|
+
if (!path.startsWith("/")) {
|
|
121
|
+
throw new Error(`${paramName}: absolute path required, got: ${path}`);
|
|
122
|
+
}
|
|
123
|
+
// 5. Check for "." as standalone component (only in absolute paths)
|
|
124
|
+
for (const component of components) {
|
|
125
|
+
// Reject "." as standalone component (current directory)
|
|
126
|
+
// BUT allow dots in filenames like "file.txt" or "config.prod"
|
|
127
|
+
if (component === ".") {
|
|
128
|
+
throw new Error(`${paramName}: directory traversal (.) not allowed in path: ${path}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// 5. Additional safety check: resolve path and verify it doesn't traverse
|
|
132
|
+
// This catches cases like /valid/path/../../etc that might slip through
|
|
133
|
+
const resolved = resolve(path);
|
|
134
|
+
if (!resolved.startsWith(path.split("/")[1] ? `/${path.split("/")[1]}` : "/")) {
|
|
135
|
+
throw new Error(`${paramName}: Path resolution resulted in directory traversal: ${path}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=path-security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-security.js","sourceRoot":"","sources":["../../src/utils/path-security.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "synapse-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server providing Flux (Docker management) and Scout (SSH operations) tools for homelab infrastructure",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"synapse-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"dev": "tsc --watch",
|
|
24
|
+
"clean": "rm -rf dist coverage",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"test:coverage": "vitest run --coverage",
|
|
29
|
+
"test:integration": "vitest run \"**/*.integration.test.ts\"",
|
|
30
|
+
"test:bench": "vitest run src/schemas/unified.bench.test.ts",
|
|
31
|
+
"lint": "eslint src/",
|
|
32
|
+
"lint:fix": "eslint src/ --fix",
|
|
33
|
+
"format": "prettier --write src/",
|
|
34
|
+
"format:check": "prettier --check src/",
|
|
35
|
+
"prepublishOnly": "pnpm run build"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"mcp",
|
|
39
|
+
"synapse",
|
|
40
|
+
"flux",
|
|
41
|
+
"scout",
|
|
42
|
+
"homelab",
|
|
43
|
+
"docker",
|
|
44
|
+
"ssh",
|
|
45
|
+
"infrastructure",
|
|
46
|
+
"zfs",
|
|
47
|
+
"remote-management"
|
|
48
|
+
],
|
|
49
|
+
"author": "Jacob Magar",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/jmagar/synapse-mcp.git"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/jmagar/synapse-mcp/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/jmagar/synapse-mcp#readme",
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=20.0.0"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
64
|
+
"dockerode": "^4.0.9",
|
|
65
|
+
"express": "^5.2.1",
|
|
66
|
+
"express-rate-limit": "^8.2.1",
|
|
67
|
+
"node-ssh": "^13.2.1",
|
|
68
|
+
"zod": "^4.2.1",
|
|
69
|
+
"zod-to-json-schema": "^3.25.1"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@eslint/js": "^9.39.2",
|
|
73
|
+
"@types/dockerode": "^3.3.47",
|
|
74
|
+
"@types/express": "^5.0.6",
|
|
75
|
+
"@types/node": "^25.0.3",
|
|
76
|
+
"@types/ssh2": "^1.15.5",
|
|
77
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
78
|
+
"eslint": "^9.39.2",
|
|
79
|
+
"eslint-config-prettier": "^10.1.8",
|
|
80
|
+
"prettier": "^3.7.4",
|
|
81
|
+
"typescript": "^5.7.0",
|
|
82
|
+
"typescript-eslint": "^8.51.0",
|
|
83
|
+
"vitest": "^4.0.16"
|
|
84
|
+
}
|
|
85
|
+
}
|