tasq/node_modules/agentic-flow/dist/utils/safe-exec.js

205 lines
7.3 KiB
JavaScript

/**
* Safe Command Execution Utility
*
* Prevents command injection by:
* 1. Using execFileSync instead of execSync (no shell interpretation)
* 2. Validating all inputs against strict patterns
* 3. Never passing user input through shell
*/
import { execFileSync } from 'child_process';
/**
* Validation patterns for common input types
*/
export const VALIDATION_PATTERNS = {
// Alphanumeric with dashes, underscores, dots, colons
key: /^[a-zA-Z0-9_\-.:]+$/,
// Namespace: alphanumeric with dashes and underscores only
namespace: /^[a-zA-Z0-9_-]+$/,
// Pattern: alphanumeric with wildcards
pattern: /^[a-zA-Z0-9_\-.*?]+$/,
// Swarm ID: alphanumeric with dashes
swarmId: /^[a-zA-Z0-9_-]+$/,
// Agent name: kebab-case
agentName: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
// Task ID: UUID or simple ID
taskId: /^[a-zA-Z0-9_-]+$/,
// Category: alphanumeric with dashes and underscores
category: /^[a-zA-Z0-9_-]+$/,
// Topology: specific allowed values
topology: /^(hierarchical|mesh|ring|star)$/,
// Agent type: specific allowed values
agentType: /^(coordinator|analyst|optimizer|documenter|monitor|specialist|architect|task-orchestrator|code-analyzer|perf-analyzer|api-docs|performance-benchmarker|system-architect|researcher|coder|tester|reviewer)$/,
// Strategy: specific allowed values
strategy: /^(parallel|sequential|adaptive|balanced)$/,
// Priority: specific allowed values
priority: /^(low|medium|high|critical)$/,
};
/**
* Validate a string against a pattern
*/
export function validateInput(value, pattern, fieldName) {
if (!pattern.test(value)) {
throw new Error(`Invalid ${fieldName}: contains disallowed characters. ` +
`Only alphanumeric characters and limited special characters are allowed.`);
}
return value;
}
/**
* Validate and sanitize a key parameter
*/
export function validateKey(key) {
if (key.length > 256) {
throw new Error('Key too long (max 256 characters)');
}
return validateInput(key, VALIDATION_PATTERNS.key, 'key');
}
/**
* Validate and sanitize a namespace parameter
*/
export function validateNamespace(namespace) {
if (namespace.length > 64) {
throw new Error('Namespace too long (max 64 characters)');
}
return validateInput(namespace, VALIDATION_PATTERNS.namespace, 'namespace');
}
/**
* Validate and sanitize a pattern parameter
*/
export function validatePattern(pattern) {
if (pattern.length > 256) {
throw new Error('Pattern too long (max 256 characters)');
}
return validateInput(pattern, VALIDATION_PATTERNS.pattern, 'pattern');
}
/**
* Validate a value - allow any content but limit size
*/
export function validateValue(value, maxLength = 1048576) {
if (value.length > maxLength) {
throw new Error(`Value too long (max ${maxLength} characters)`);
}
// Values can contain any content - they're passed as array args, not shell-interpreted
return value;
}
/**
* Safe execution of npx commands
* Uses execFileSync with array arguments to prevent shell injection
*/
export function safeExecNpx(pkg, args, options) {
const defaultOptions = {
encoding: 'utf-8',
maxBuffer: 10 * 1024 * 1024,
timeout: 30000,
...options,
};
try {
// Use npx with --yes to auto-confirm
const result = execFileSync('npx', ['--yes', pkg, ...args], defaultOptions);
return typeof result === 'string' ? result : result.toString();
}
catch (error) {
// Sanitize error message to not leak system info
const message = error.message || 'Command execution failed';
throw new Error(`Execution failed: ${message.substring(0, 200)}`);
}
}
/**
* Execute claude-flow memory commands safely
*/
export function execMemoryStore(key, value, namespace = 'default', ttl) {
// Validate inputs
const safeKey = validateKey(key);
const safeValue = validateValue(value);
const safeNamespace = validateNamespace(namespace);
const args = ['memory', 'store', safeKey, safeValue, '--namespace', safeNamespace];
if (ttl !== undefined && ttl > 0) {
args.push('--ttl', String(ttl));
}
return safeExecNpx('claude-flow@alpha', args);
}
/**
* Execute claude-flow memory retrieve safely
*/
export function execMemoryRetrieve(key, namespace = 'default') {
const safeKey = validateKey(key);
const safeNamespace = validateNamespace(namespace);
return safeExecNpx('claude-flow@alpha', [
'memory', 'retrieve', safeKey, '--namespace', safeNamespace
]);
}
/**
* Execute claude-flow memory search safely
*/
export function execMemorySearch(pattern, namespace, limit = 10) {
const safePattern = validatePattern(pattern);
const safeLimit = Math.min(Math.max(1, limit), 100);
const args = ['memory', 'search', safePattern, '--limit', String(safeLimit)];
if (namespace) {
args.push('--namespace', validateNamespace(namespace));
}
return safeExecNpx('claude-flow@alpha', args);
}
/**
* Execute claude-flow swarm init safely
*/
export function execSwarmInit(swarmId, topology, maxAgents = 8) {
const safeSwarmId = validateInput(swarmId, VALIDATION_PATTERNS.swarmId, 'swarmId');
const safeTopology = validateInput(topology, VALIDATION_PATTERNS.topology, 'topology');
const safeMaxAgents = Math.min(Math.max(1, maxAgents), 100);
return safeExecNpx('claude-flow@alpha', [
'swarm', 'init',
'--id', safeSwarmId,
'--topology', safeTopology,
'--max-agents', String(safeMaxAgents)
]);
}
/**
* Execute claude-flow agent spawn safely
*/
export function execAgentSpawn(name, type, swarmId, capabilities) {
const safeName = validateInput(name, VALIDATION_PATTERNS.agentName, 'name');
const safeType = validateInput(type, VALIDATION_PATTERNS.agentType, 'type');
const args = ['agent', 'spawn', '--name', safeName, '--type', safeType];
if (swarmId) {
args.push('--swarm', validateInput(swarmId, VALIDATION_PATTERNS.swarmId, 'swarmId'));
}
if (capabilities && capabilities.length > 0) {
// Validate each capability
const safeCapabilities = capabilities.map(cap => validateInput(cap, VALIDATION_PATTERNS.key, 'capability'));
args.push('--capabilities', safeCapabilities.join(','));
}
return safeExecNpx('claude-flow@alpha', args);
}
/**
* Execute claude-flow task orchestrate safely
*/
export function execTaskOrchestrate(task, strategy = 'auto', priority = 'medium') {
// Task description can contain most text, but validate length
if (task.length > 10000) {
throw new Error('Task description too long (max 10000 characters)');
}
const safeStrategy = validateInput(strategy, VALIDATION_PATTERNS.strategy, 'strategy');
const safePriority = validateInput(priority, VALIDATION_PATTERNS.priority, 'priority');
return safeExecNpx('claude-flow@alpha', [
'task', 'orchestrate',
'--task', task,
'--strategy', safeStrategy,
'--priority', safePriority
]);
}
export default {
validateInput,
validateKey,
validateNamespace,
validatePattern,
validateValue,
safeExecNpx,
execMemoryStore,
execMemoryRetrieve,
execMemorySearch,
execSwarmInit,
execAgentSpawn,
execTaskOrchestrate,
VALIDATION_PATTERNS,
};
//# sourceMappingURL=safe-exec.js.map