UNPKG

@xec-sh/core

Version:

Universal shell execution engine

248 lines 12 kB
import { StreamHandler } from '../utils/stream.js'; import { ProgressReporter } from '../utils/progress.js'; import { TimeoutError, AdapterError } from '../core/error.js'; import { EnhancedEventEmitter } from '../utils/event-emitter.js'; import { ExecutionResultImpl } from '../core/result.js'; export class BaseAdapter extends EnhancedEventEmitter { constructor(config = {}) { super(); const defaultPatterns = [ /"(api[_-]?key|apikey|password|token|secret|client[_-]?secret)":\s*"([^"]+)"/gi, /\b(api[_-]?key|apikey|access[_-]?token|auth[_-]?token|authentication[_-]?token|private[_-]?key|secret[_-]?key)(\s*[:=]\s*)("([^"]+)"|'([^']+)'|([^"'\s]+))/gi, /(Authorization:\s*)(Bearer|Basic)(\s+)([a-zA-Z0-9_\-/.+=]+)/gi, /\b(aws[_-]?access[_-]?key[_-]?id|aws[_-]?secret[_-]?access[_-]?key)(\s*[:=]\s*)("([^"]+)"|'([^']+)'|([^"'\s]+))/gi, /\b(gh[ps]_[a-zA-Z0-9]{16,})/gi, /\b(github[_-]?token)(\s*[:=]\s*)("([^"]+)"|'([^']+)'|([^"'\s]+))/gi, /\b(token)(\s*[:=]\s*)("([^"]+)"|'([^']+)'|([^"'\s]+))/gi, /\b(password|passwd|pwd)(\s*[:=]\s*)("([^"]+)"|'([^']+)'|([^\s]+))/gi, /(--password)(\s+)("([^"]+)"|'([^']+)'|([^"'\s]+))/gi, /(--client[_-]?secret|--secret)(\s+)("([^"]+)"|'([^']+)'|([^"'\s]+))/gi, /-----BEGIN\s+(RSA|DSA|EC|OPENSSH)\s+PRIVATE\s+KEY-----[\s\S]+?-----END\s+(RSA|DSA|EC|OPENSSH)\s+PRIVATE\s+KEY-----/gi, /\b([A-Z][A-Z0-9_]*(?:SECRET|TOKEN|KEY|PASSWORD|PASSWD|PWD|APIKEY|API_KEY)[A-Z0-9_]*)(\s*[:=]\s*)("([^"]+)"|'([^']+)'|([^\s]+))/gi, /\b(secret|client[_-]?secret)(\s*[:=]\s*)("([^"]+)"|'([^']+)'|([^"'\s]+))/gi, /\b(Bearer)(\s+)([a-zA-Z0-9_\-/.]+)/gi ]; this.config = { defaultTimeout: config.defaultTimeout ?? 120000, defaultCwd: config.defaultCwd ?? process.cwd(), defaultEnv: config.defaultEnv ?? {}, defaultShell: config.defaultShell ?? true, encoding: config.encoding ?? 'utf8', maxBuffer: config.maxBuffer ?? 10 * 1024 * 1024, throwOnNonZeroExit: config.throwOnNonZeroExit !== undefined ? config.throwOnNonZeroExit : true, sensitiveDataMasking: { enabled: config.sensitiveDataMasking?.enabled ?? true, patterns: config.sensitiveDataMasking?.patterns ?? defaultPatterns, replacement: config.sensitiveDataMasking?.replacement ?? '[REDACTED]' } }; this.name = ''; } emitAdapterEvent(event, data) { if (!this.listenerCount(event)) return; this.emit(event, { ...data, timestamp: new Date(), adapter: this.adapterName }); } mergeCommand(command) { return { ...command, cwd: command.cwd ?? this.config.defaultCwd, env: { ...this.config.defaultEnv, ...command.env }, timeout: command.timeout ?? this.config.defaultTimeout, shell: command.shell ?? this.config.defaultShell, stdout: command.stdout ?? 'pipe', stderr: command.stderr ?? 'pipe' }; } createStreamHandler(options) { return new StreamHandler({ encoding: this.config.encoding, maxBuffer: this.config.maxBuffer, onData: options?.onData ? (chunk) => { const maskedChunk = this.maskSensitiveData(chunk); options.onData(maskedChunk); } : undefined }); } createProgressReporter(command) { if (!command.progress?.enabled) { return null; } return new ProgressReporter({ enabled: true, onProgress: command.progress.onProgress, updateInterval: command.progress.updateInterval, reportLines: command.progress.reportLines, prefix: this.adapterName }); } async handleTimeout(promise, timeout, command, cleanup) { if (timeout <= 0) { return promise; } const timeoutPromise = new Promise((_, reject) => { const timer = setTimeout(() => { if (cleanup) cleanup(); reject(new TimeoutError(command, timeout)); }, timeout); promise.finally(() => clearTimeout(timer)); }); return Promise.race([promise, timeoutPromise]); } maskSensitiveData(text) { if (!this.config.sensitiveDataMasking.enabled || !text) { return text; } let maskedText = text; for (const pattern of this.config.sensitiveDataMasking.patterns) { const globalPattern = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g'); maskedText = maskedText.replace(globalPattern, (match, ...args) => { if (match.includes('BEGIN') && match.includes('PRIVATE KEY')) { return this.config.sensitiveDataMasking.replacement; } if (match.match(/^gh[ps]_[a-zA-Z0-9]{16,}$/)) { return this.config.sensitiveDataMasking.replacement; } const groups = args.slice(0, -2); if (groups.length === 0 || groups.every(g => g === undefined)) { return this.config.sensitiveDataMasking.replacement; } if (groups.length === 2 && match.includes('":')) { return `"${groups[0]}": ${this.config.sensitiveDataMasking.replacement}`; } if (groups.length === 4 && groups[0] && groups[0].includes('Authorization') && groups[1] && groups[2] !== undefined && groups[3]) { return groups[0] + groups[1] + ' ' + this.config.sensitiveDataMasking.replacement; } if (groups.length === 6) { const key = groups[0]; const separator = groups[1]; const quotedDouble = groups[3]; const quotedSingle = groups[4]; const unquoted = groups[5]; if (key.startsWith('--')) { return key + ' ' + this.config.sensitiveDataMasking.replacement; } return key + separator + this.config.sensitiveDataMasking.replacement; } if (groups.length === 5 && groups[0] && groups[0].startsWith('--')) { return groups[0] + ' ' + this.config.sensitiveDataMasking.replacement; } if (groups.length === 3 && groups[0] && groups[1] !== undefined && groups[2]) { if (groups[0] === 'Bearer') { return groups[0] + ' ' + this.config.sensitiveDataMasking.replacement; } return groups[0] + groups[1] + this.config.sensitiveDataMasking.replacement; } if (groups.length === 2 && groups[0] && groups[1]) { return groups[0] + this.config.sensitiveDataMasking.replacement; } if (groups.length === 1 && groups[0]) { return this.config.sensitiveDataMasking.replacement; } if (match.includes('=')) { const [key,] = match.split('=', 2); return key + '=' + this.config.sensitiveDataMasking.replacement; } else if (match.includes(':')) { const [key,] = match.split(':', 2); return key + ': ' + this.config.sensitiveDataMasking.replacement; } return this.config.sensitiveDataMasking.replacement; }); } return maskedText; } createResultSync(stdout, stderr, exitCode, signal, command, startTime, endTime, context) { const maskedStdout = this.maskSensitiveData(stdout); const maskedStderr = this.maskSensitiveData(stderr); const maskedCommand = this.maskSensitiveData(command); const result = new ExecutionResultImpl(maskedStdout, maskedStderr, exitCode, signal, maskedCommand, endTime - startTime, new Date(startTime), new Date(endTime), this.adapterName, context?.host, context?.container); const commandForThrowCheck = context?.originalCommand ?? command; if (this.shouldThrowOnNonZeroExit(commandForThrowCheck, exitCode)) { result.throwIfFailed(); } return result; } async createResult(stdout, stderr, exitCode, signal, command, startTime, endTime, context) { const maskedStdout = this.maskSensitiveData(stdout); const maskedStderr = this.maskSensitiveData(stderr); const maskedCommand = this.maskSensitiveData(command); const result = new ExecutionResultImpl(maskedStdout, maskedStderr, exitCode, signal, maskedCommand, endTime - startTime, new Date(startTime), new Date(endTime), this.adapterName, context?.host, context?.container); const commandForThrowCheck = context?.originalCommand ?? command; if (this.shouldThrowOnNonZeroExit(commandForThrowCheck, exitCode)) { result.throwIfFailed(); } return result; } shouldThrowOnNonZeroExit(command, exitCode) { if (exitCode === 0) { return false; } if (typeof command === 'string') { return this.config.throwOnNonZeroExit; } if (command.nothrow !== undefined) { return !command.nothrow; } return this.config.throwOnNonZeroExit; } buildCommandString(command) { if (command.args && command.args.length > 0) { return `${command.command} ${command.args.join(' ')}`; } return command.command; } async handleAbortSignal(signal, cleanup) { if (!signal) return; if (signal.aborted) { cleanup(); throw new AdapterError(this.adapterName, 'execute', new Error('Operation aborted')); } const abortHandler = () => { cleanup(); }; signal.addEventListener('abort', abortHandler, { once: true }); } createCombinedEnv(baseEnv, commandEnv) { const combined = {}; for (const [key, value] of Object.entries(process.env)) { if (value !== undefined) { combined[key] = value; } } Object.assign(combined, baseEnv); if (commandEnv) { Object.assign(combined, commandEnv); } return combined; } updateConfig(config) { const newSensitiveDataMasking = config.sensitiveDataMasking ? { enabled: config.sensitiveDataMasking.enabled ?? this.config.sensitiveDataMasking.enabled, patterns: config.sensitiveDataMasking.patterns ?? this.config.sensitiveDataMasking.patterns, replacement: config.sensitiveDataMasking.replacement ?? this.config.sensitiveDataMasking.replacement } : this.config.sensitiveDataMasking; this.config = { defaultTimeout: config.defaultTimeout ?? this.config.defaultTimeout, defaultCwd: config.defaultCwd ?? this.config.defaultCwd, defaultEnv: config.defaultEnv ?? this.config.defaultEnv, defaultShell: config.defaultShell ?? this.config.defaultShell, encoding: config.encoding ?? this.config.encoding, maxBuffer: config.maxBuffer ?? this.config.maxBuffer, throwOnNonZeroExit: config.throwOnNonZeroExit !== undefined ? config.throwOnNonZeroExit : this.config.throwOnNonZeroExit, sensitiveDataMasking: newSensitiveDataMasking }; } getConfig() { return { ...this.config }; } } //# sourceMappingURL=base-adapter.js.map