@yamo/mcp-server 1.3.9 → 1.3.10
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/index.js +33 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -47,7 +47,7 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
47
47
|
const path_1 = __importDefault(require("path"));
|
|
48
48
|
const pkg = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../package.json'), 'utf8'));
|
|
49
49
|
dotenv.config();
|
|
50
|
-
// Validation
|
|
50
|
+
// Validation helpers
|
|
51
51
|
function validateBytes32(value, fieldName) {
|
|
52
52
|
if (!value.match(/^0x[a-fA-F0-9]{64}$/)) {
|
|
53
53
|
throw new Error(`${fieldName} must be a valid bytes32 hash (0x + 64 hex chars). ` +
|
|
@@ -55,6 +55,21 @@ function validateBytes32(value, fieldName) {
|
|
|
55
55
|
`\nDo NOT include algorithm prefixes like "sha256:"`);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
function validateEthereumAddress(address, fieldName) {
|
|
59
|
+
if (!address || !address.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
60
|
+
throw new Error(`${fieldName} must be a valid Ethereum address (0x + 40 hex characters)`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function validateEnvironment() {
|
|
64
|
+
const requiredEnvVars = ['CONTRACT_ADDRESS', 'RPC_URL', 'PRIVATE_KEY'];
|
|
65
|
+
const missing = requiredEnvVars.filter(v => !process.env[v]);
|
|
66
|
+
if (missing.length > 0) {
|
|
67
|
+
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
|
|
68
|
+
}
|
|
69
|
+
// Validate contract address format
|
|
70
|
+
const contractAddress = process.env.CONTRACT_ADDRESS;
|
|
71
|
+
validateEthereumAddress(contractAddress, 'CONTRACT_ADDRESS');
|
|
72
|
+
}
|
|
58
73
|
const SUBMIT_BLOCK_TOOL = {
|
|
59
74
|
name: "yamo_submit_block",
|
|
60
75
|
description: `Submits a YAMO block to the YAMORegistry smart contract.
|
|
@@ -253,6 +268,8 @@ class YamoMcpServer {
|
|
|
253
268
|
// Cache for chain continuation: latest submitted block's contentHash
|
|
254
269
|
latestContentHash = null;
|
|
255
270
|
constructor() {
|
|
271
|
+
// Validate environment variables at startup
|
|
272
|
+
validateEnvironment();
|
|
256
273
|
this.server = new index_js_1.Server({ name: "yamo", version: pkg.version }, { capabilities: { tools: {} } });
|
|
257
274
|
this.ipfs = new core_1.IpfsManager();
|
|
258
275
|
this.chain = new core_1.YamoChainClient();
|
|
@@ -270,24 +287,29 @@ class YamoMcpServer {
|
|
|
270
287
|
// Process files - auto-read if they're file paths (with security fix)
|
|
271
288
|
let processedFiles = files;
|
|
272
289
|
if (files && Array.isArray(files)) {
|
|
273
|
-
processedFiles = files.map((file) => {
|
|
290
|
+
processedFiles = await Promise.all(files.map(async (file) => {
|
|
274
291
|
// Check if content is a file path that exists
|
|
275
292
|
if (typeof file.content === 'string' && fs_1.default.existsSync(file.content)) {
|
|
276
|
-
// Security: Resolve
|
|
277
|
-
const filePath =
|
|
278
|
-
const allowedDir = process.cwd();
|
|
293
|
+
// Security: Resolve symlinks and restrict to cwd (prevents symlink attacks and TOCTOU)
|
|
294
|
+
const filePath = fs_1.default.realpathSync(file.content);
|
|
295
|
+
const allowedDir = fs_1.default.realpathSync(process.cwd());
|
|
296
|
+
// Check for symlinks
|
|
297
|
+
const stats = fs_1.default.lstatSync(file.content);
|
|
298
|
+
if (stats.isSymbolicLink()) {
|
|
299
|
+
throw new Error(`Symbolic links are not allowed: ${file.content}`);
|
|
300
|
+
}
|
|
279
301
|
if (!filePath.startsWith(allowedDir)) {
|
|
280
302
|
throw new Error(`File path outside allowed directory: ${file.content}`);
|
|
281
303
|
}
|
|
282
304
|
console.error(`[DEBUG] Auto-reading file from path: ${file.content}`);
|
|
283
305
|
return {
|
|
284
306
|
name: file.name,
|
|
285
|
-
content: fs_1.default.
|
|
307
|
+
content: await fs_1.default.promises.readFile(filePath, 'utf8')
|
|
286
308
|
};
|
|
287
309
|
}
|
|
288
310
|
// Otherwise use content as-is
|
|
289
311
|
return file;
|
|
290
|
-
});
|
|
312
|
+
}));
|
|
291
313
|
}
|
|
292
314
|
// Input validation (Part 3: Security Fixes)
|
|
293
315
|
validateBytes32(contentHash, "contentHash");
|
|
@@ -315,7 +337,7 @@ class YamoMcpServer {
|
|
|
315
337
|
}
|
|
316
338
|
}
|
|
317
339
|
}
|
|
318
|
-
else {
|
|
340
|
+
else if (previousBlock) {
|
|
319
341
|
validateBytes32(previousBlock, "previousBlock");
|
|
320
342
|
}
|
|
321
343
|
let ipfsCID = undefined;
|
|
@@ -357,7 +379,7 @@ class YamoMcpServer {
|
|
|
357
379
|
blockId,
|
|
358
380
|
hint: "Verify the blockId or check if the block was submitted"
|
|
359
381
|
}, null, 2) }],
|
|
360
|
-
isError:
|
|
382
|
+
isError: true,
|
|
361
383
|
};
|
|
362
384
|
}
|
|
363
385
|
return {
|
|
@@ -386,7 +408,7 @@ class YamoMcpServer {
|
|
|
386
408
|
error: "No blocks found on-chain",
|
|
387
409
|
hint: "The chain may be empty. Try submitting a genesis block first."
|
|
388
410
|
}, null, 2) }],
|
|
389
|
-
isError:
|
|
411
|
+
isError: true,
|
|
390
412
|
};
|
|
391
413
|
}
|
|
392
414
|
return {
|
|
@@ -418,7 +440,7 @@ class YamoMcpServer {
|
|
|
418
440
|
blockId,
|
|
419
441
|
hint: "Cannot audit non-existent block"
|
|
420
442
|
}, null, 2) }],
|
|
421
|
-
isError:
|
|
443
|
+
isError: true,
|
|
422
444
|
};
|
|
423
445
|
}
|
|
424
446
|
// If no IPFS CID, can't audit content
|