ruv-swarm
Version:
High-performance neural network swarm orchestration in WebAssembly
218 lines (186 loc) • 5.92 kB
JavaScript
/**
* Security module for ruv-swarm
* Provides integrity verification and security controls
*/
import crypto from 'crypto';
import fs from 'fs/promises';
import path from 'path';
/**
* WASM module integrity verification
*/
export class WasmIntegrityVerifier {
constructor(hashesPath = path.join(new URL('.', import.meta.url).pathname, '..', 'wasm', 'checksums.json')) {
this.hashesPath = hashesPath;
this.knownHashes = new Map();
this.initialized = false;
}
async loadKnownHashes() {
try {
const data = await fs.readFile(this.hashesPath, 'utf8');
const hashes = JSON.parse(data);
Object.entries(hashes).forEach(([file, hash]) => {
this.knownHashes.set(file, hash);
});
} catch (error) {
// Silently initialize with empty checksums if file doesn't exist
// This is expected on first run or if checksums.json is gitignored
}
}
async saveKnownHashes() {
try {
// Ensure directory exists
const dir = path.dirname(this.hashesPath);
await fs.mkdir(dir, { recursive: true });
const hashes = Object.fromEntries(this.knownHashes);
await fs.writeFile(this.hashesPath, JSON.stringify(hashes, null, 2));
} catch (error) {
// Silently fail if unable to save checksums
// This prevents errors when checksums.json is gitignored
}
}
/**
* Verify WASM module integrity before loading
* @param {string} wasmPath - Path to WASM module
* @param {boolean} updateHash - Update the known hash if verification fails
* @returns {Promise<boolean>} - True if verification passes
*/
async verifyWasmModule(wasmPath, updateHash = false) {
// Ensure hashes are loaded
if (!this.initialized) {
await this.loadKnownHashes();
this.initialized = true;
}
try {
const wasmData = await fs.readFile(wasmPath);
const hash = crypto.createHash('sha256').update(wasmData).digest('hex');
const filename = path.basename(wasmPath);
const knownHash = this.knownHashes.get(filename);
if (!knownHash) {
if (updateHash) {
this.knownHashes.set(filename, hash);
await this.saveKnownHashes();
return true;
}
throw new SecurityError(`Unknown WASM module: ${filename}`);
}
if (hash !== knownHash) {
throw new SecurityError(`WASM module integrity check failed for ${filename}`);
}
return true;
} catch (error) {
if (error instanceof SecurityError) {
throw error;
}
throw new SecurityError(`Failed to verify WASM module: ${error.message}`);
}
}
/**
* Load and verify WASM module
* @param {string} wasmPath - Path to WASM module
* @returns {Promise<ArrayBuffer>} - Verified WASM data
*/
async loadVerifiedWasm(wasmPath) {
// Ensure hashes are loaded
if (!this.initialized) {
await this.loadKnownHashes();
this.initialized = true;
}
await this.verifyWasmModule(wasmPath);
return await fs.readFile(wasmPath);
}
}
/**
* Command injection prevention
*/
export class CommandSanitizer {
static validateArgument(arg) {
// Only allow alphanumeric, dash, underscore, and common safe characters
const safePattern = /^[a-zA-Z0-9\-_./=:\s]+$/;
if (!safePattern.test(arg)) {
throw new SecurityError(`Invalid argument contains unsafe characters: ${arg}`);
}
// Check for command injection patterns
const dangerousPatterns = [
/[;&|`$(){}[\]<>]/, // Shell metacharacters
/\.\./, // Path traversal
/^-/, // Argument injection
];
for (const pattern of dangerousPatterns) {
if (pattern.test(arg)) {
throw new SecurityError(`Potentially dangerous pattern detected in argument: ${arg}`);
}
}
return arg;
}
static sanitizeCommand(command, args = []) {
// Validate command is from allowed list
const allowedCommands = ['claude', 'npm', 'node', 'npx'];
if (!allowedCommands.includes(command)) {
throw new SecurityError(`Command not in allowlist: ${command}`);
}
// Validate all arguments
const sanitizedArgs = args.map(arg => this.validateArgument(arg));
return { command, args: sanitizedArgs };
}
}
/**
* Dependency integrity verification
*/
export class DependencyVerifier {
static async verifyNpmPackage(packageName, expectedVersion) {
try {
const packageJsonPath = path.join(
new URL('.', import.meta.url).pathname,
'..',
'node_modules',
packageName,
'package.json',
);
const packageData = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
if (packageData.version !== expectedVersion) {
throw new SecurityError(
`Package version mismatch for ${packageName}: expected ${expectedVersion}, got ${packageData.version}`,
);
}
return true;
} catch (error) {
if (error instanceof SecurityError) {
throw error;
}
throw new SecurityError(`Failed to verify package ${packageName}: ${error.message}`);
}
}
}
/**
* Security error class
*/
export class SecurityError extends Error {
constructor(message) {
super(message);
this.name = 'SecurityError';
}
}
/**
* Security middleware for MCP server
*/
export function createSecurityMiddleware() {
const wasmVerifier = new WasmIntegrityVerifier();
return {
async verifyWasmBeforeLoad(wasmPath) {
return await wasmVerifier.verifyWasmModule(wasmPath);
},
sanitizeCommand(command, args) {
return CommandSanitizer.sanitizeCommand(command, args);
},
async verifyDependency(packageName, version) {
return await DependencyVerifier.verifyNpmPackage(packageName, version);
},
};
}
export default {
WasmIntegrityVerifier,
CommandSanitizer,
DependencyVerifier,
SecurityError,
createSecurityMiddleware,
};