@eladtest/mcp
Version:
MCP server for shellfirm - provides interactive command validation with captcha
314 lines (313 loc) • 11.4 kB
JavaScript
;
/**
* TypeScript wrapper for Shellfirm WASM module
*
* This module provides a clean TypeScript interface to the Rust-based
* command validation engine compiled to WASM.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.initShellfirmWasm = initShellfirmWasm;
exports.validateCommand = validateCommand;
exports.validateCommandSimple = validateCommandSimple;
exports.validateSplitCommand = validateSplitCommand;
exports.validateSplitCommandWithOptions = validateSplitCommandWithOptions;
exports.getAllPatterns = getAllPatterns;
exports.getPatternGroups = getPatternGroups;
exports.getPatternsForGroup = getPatternsForGroup;
exports.createFileExistenceCache = createFileExistenceCache;
exports.testWasmModule = testWasmModule;
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const logger_js_1 = require("./logger.js");
// Import WASM module - we'll handle the import dynamically
let wasmModule = null;
/**
* Initialize the WASM module
*/
async function initShellfirmWasm() {
if (wasmModule) {
return; // Already initialized
}
try {
// Try to load from the pkg directory (relative to compiled lib folder)
const pkgPath = path.resolve(__dirname, '..', 'pkg', 'shellfirm_core.js');
if (fs.existsSync(pkgPath)) {
// In CommonJS, prefer require with a filesystem path (no file:// URL)
// @ts-ignore - require is available in CommonJS builds
wasmModule = require(pkgPath);
}
else {
await (0, logger_js_1.error)('wasm', { message: 'Pkg directory not found, trying node_modules' });
// Fallback: try from node_modules if installed as a dependency
// @ts-ignore - require is available in CommonJS builds
wasmModule = require('shellfirm_core');
await (0, logger_js_1.error)('wasm', { message: 'Initialized from node_modules', level: 'info' });
}
// Initialize the WASM module
if (wasmModule.init) {
wasmModule.init();
}
}
catch (error) {
await (0, logger_js_1.error)('wasm', { message: 'Failed to load module', error: String(error) });
throw new Error(`Failed to initialize Shellfirm WASM module: ${error}`);
}
}
/**
* Validate a command using the WASM module
*/
async function validateCommand(command, options = {}) {
await initShellfirmWasm();
if (!wasmModule) {
throw new Error('WASM module not initialized');
}
try {
// Create WASM validation options
const wasmOptions = new wasmModule.WasmValidationOptions();
if (options.deny_pattern_ids && options.deny_pattern_ids.length > 0) {
wasmOptions.set_deny_pattern_ids(JSON.stringify(options.deny_pattern_ids));
}
if (options.allowed_severities && options.allowed_severities.length > 0) {
wasmOptions.set_allowed_severities(JSON.stringify(options.allowed_severities));
}
// Validate the command
const result = wasmModule.validate_command_wasm(command, wasmOptions);
// Parse the matches from JSON and extract properties before freeing
const matches = JSON.parse(result.matches);
const should_challenge = result.should_challenge;
const should_deny = result.should_deny;
// Clean up WASM objects
wasmOptions.free();
result.free();
return {
matches,
should_challenge,
should_deny,
};
}
catch (error) {
await (0, logger_js_1.error)('wasm', { message: 'Validation error', error: String(error) });
throw new Error(`Command validation failed: ${error}`);
}
}
/**
* Simple command validation without options (for backward compatibility)
*/
async function validateCommandSimple(command) {
await initShellfirmWasm();
if (!wasmModule) {
throw new Error('WASM module not initialized');
}
try {
const result = wasmModule.validate_command_simple_wasm(command);
const matches = JSON.parse(result.matches);
const should_challenge = result.should_challenge;
const should_deny = result.should_deny;
// Clean up WASM objects
result.free();
return {
matches,
should_challenge,
should_deny,
};
}
catch (error) {
await (0, logger_js_1.error)('wasm', { message: 'Simple validation error', error: String(error) });
throw new Error(`Command validation failed: ${error}`);
}
}
/**
* Validate a command by parsing, splitting, and checking each part
* This is the recommended function for command validation as it handles
* complex shell commands with operators like &, |, &&, ||
*/
async function validateSplitCommand(command) {
await initShellfirmWasm();
if (!wasmModule) {
throw new Error('WASM module not initialized');
}
try {
const result = wasmModule.validate_command_with_split_wasm(command);
const matches = JSON.parse(result.matches);
const should_challenge = result.should_challenge;
const should_deny = result.should_deny;
// Clean up WASM objects
result.free();
return {
matches,
should_challenge,
should_deny,
};
}
catch (error) {
await (0, logger_js_1.error)('wasm', { message: 'Split command validation error', error: String(error) });
throw new Error(`Command validation failed: ${error}`);
}
}
/**
* Validate a split command with options
*/
async function validateSplitCommandWithOptions(command, options = {}) {
await initShellfirmWasm();
if (!wasmModule) {
throw new Error('WASM module not initialized');
}
let wasmOptions = null;
let result = null;
try {
// Create WASM validation options
wasmOptions = new wasmModule.WasmValidationOptions();
if (options.deny_pattern_ids && options.deny_pattern_ids.length > 0) {
wasmOptions.set_deny_pattern_ids(JSON.stringify(options.deny_pattern_ids));
}
if (options.allowed_severities && options.allowed_severities.length > 0) {
wasmOptions.set_allowed_severities(JSON.stringify(options.allowed_severities));
}
// Validate the command using the split command function
result = wasmModule.validate_command_with_options_wasm(command, wasmOptions);
// At this point, both wasmOptions and result are guaranteed to be non-null
if (!wasmOptions || !result) {
throw new Error('Failed to create WASM objects');
}
// Parse the matches from JSON and extract properties before freeing
const matches = JSON.parse(result.matches);
const should_challenge = result.should_challenge;
const should_deny = result.should_deny;
return {
matches,
should_challenge,
should_deny,
};
}
catch (error) {
await (0, logger_js_1.error)('wasm', { message: 'Split command validation with options error', error: String(error) });
throw new Error(`Command validation failed: ${error}`);
}
finally {
// Clean up WASM objects safely
try {
if (result && result.ptr && typeof result.ptr === 'number' && result.ptr !== 0) {
result.free();
result.ptr = 0;
}
}
catch { }
try {
if (wasmOptions && wasmOptions.ptr && typeof wasmOptions.ptr === 'number' && wasmOptions.ptr !== 0) {
wasmOptions.free();
wasmOptions.ptr = 0;
}
}
catch { }
}
}
/**
* Get all available patterns
*/
async function getAllPatterns() {
await initShellfirmWasm();
if (!wasmModule) {
throw new Error('WASM module not initialized');
}
try {
const patternsJson = wasmModule.get_all_patterns_wasm();
return JSON.parse(patternsJson);
}
catch (error) {
await (0, logger_js_1.error)('wasm', { message: 'Get patterns error', error: String(error) });
throw new Error(`Failed to get patterns: ${error}`);
}
}
/**
* Get pattern groups/categories
*/
async function getPatternGroups() {
await initShellfirmWasm();
if (!wasmModule) {
throw new Error('WASM module not initialized');
}
try {
const groupsJson = wasmModule.get_pattern_groups_wasm();
return JSON.parse(groupsJson);
}
catch (error) {
await (0, logger_js_1.error)('wasm', { message: 'Get groups error', error: String(error) });
throw new Error(`Failed to get pattern groups: ${error}`);
}
}
/**
* Get patterns for a specific group
*/
async function getPatternsForGroup(group) {
await initShellfirmWasm();
if (!wasmModule) {
throw new Error('WASM module not initialized');
}
try {
const patternsJson = wasmModule.get_patterns_for_group_wasm(group);
return JSON.parse(patternsJson);
}
catch (error) {
await (0, logger_js_1.error)('wasm', { message: 'Get group patterns error', error: String(error) });
throw new Error(`Failed to get patterns for group ${group}: ${error}`);
}
}
/**
* Create file existence cache from file system paths
* This is a utility function to help with file existence checking in WASM
*/
function createFileExistenceCache(filePaths) {
const cache = {};
for (const filePath of filePaths) {
try {
cache[filePath] = fs.existsSync(filePath);
}
catch {
cache[filePath] = false;
}
}
return cache;
}
/**
* Test function to verify WASM module is working
*/
async function testWasmModule() {
await initShellfirmWasm();
if (!wasmModule) {
throw new Error('WASM module not initialized');
}
return wasmModule.test_wasm_module();
}