UNPKG

@dawans/promptshield

Version:

Secure your LLM stack with enterprise-grade RulePacks for AI safety scanning

213 lines (212 loc) 8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultScanOrchestrator = void 0; const Result_1 = require("../../../../shared/types/Result"); const ScanResult_1 = require("../entities/ScanResult"); const ScanContext_1 = require("../entities/ScanContext"); /** * Main orchestrator for scan operations */ class DefaultScanOrchestrator { constructor(fileReader, processors, ruleEngine, strategy, metricsCollector) { this.fileReader = fileReader; this.processors = processors; this.ruleEngine = ruleEngine; this.strategy = strategy; this.metricsCollector = metricsCollector; } /** * Executes a scan request */ async scan(request) { // Validate request const validationResult = this.validateRequest(request); if (validationResult.isErr()) { return (0, Result_1.err)(validationResult.error); } // Create context const contextResult = await this.createContext(request); if (contextResult.isErr()) { return (0, Result_1.err)(contextResult.error); } // Execute scan const scanResult = await this.orchestrate(contextResult.value, request.input); return scanResult; } /** * Validates a scan request */ validateRequest(request) { if (!request.input || request.input.trim() === '') { return (0, Result_1.err)(new Error('Input is required')); } if (!request.config) { return (0, Result_1.err)(new Error('Configuration is required')); } return (0, Result_1.ok)(undefined); } /** * Creates a scan context from a request */ async createContext(request) { try { // Load rulepack const rulePackResult = await this.ruleEngine.loadRulePack(request.config.rulepack); if (rulePackResult.isErr()) { return (0, Result_1.err)(rulePackResult.error); } const context = new ScanContext_1.ScanContext(request.config, rulePackResult.value); return (0, Result_1.ok)(context); } catch (error) { return (0, Result_1.err)(new Error(`Failed to create scan context: ${error}`)); } } /** * Orchestrates the entire scan process */ async orchestrate(context, input) { this.metricsCollector.start(); try { // Check if input is a file or directory const isFile = await this.fileReader.exists(input); const isDir = await this.fileReader.isDirectory(input); if (isDir) { return this.scanDirectory(context, input); } else if (isFile) { return this.scanFile(context, input); } else { // Treat as direct content return this.scanContent(context, input, 'direct'); } } catch (error) { return (0, Result_1.err)(new Error(`Scan orchestration failed: ${error}`)); } } /** * Scans a directory */ async scanDirectory(context, dirPath) { const filesResult = await this.fileReader.listFiles(dirPath); if (filesResult.isErr()) { return (0, Result_1.err)(filesResult.error); } const allViolations = []; let totalObjects = 0; for (const filePath of filesResult.value) { const fileResult = await this.scanFile(context, filePath); if (fileResult.isOk()) { allViolations.push(...fileResult.value.violations); totalObjects += fileResult.value.metrics.objectsScanned; } } const baseMetrics = this.metricsCollector.end(); const metrics = { ...baseMetrics, objectsScanned: totalObjects, streamingUsed: false, }; return (0, Result_1.ok)(new ScanResult_1.ScanResult(allViolations, metrics)); } /** * Scans a single file */ async scanFile(context, filePath) { // Read file content const contentResult = await this.fileReader.readFile(filePath); if (contentResult.isErr()) { return (0, Result_1.err)(contentResult.error); } // Get file size for streaming decision const sizeResult = await this.fileReader.getFileSize(filePath); const fileSize = sizeResult.isOk() ? sizeResult.value : 0; // Find appropriate processor const processor = this.findProcessor(filePath); if (!processor) { return (0, Result_1.err)(new Error(`No processor found for file: ${filePath}`)); } // Determine if streaming should be used const useStreaming = this.strategy.shouldUseStreaming(fileSize, context.getStreamingThreshold()); if (useStreaming && 'processStream' in processor) { return this.scanWithStreaming(context, contentResult.value, processor); } else { return this.scanContent(context, contentResult.value, filePath); } } /** * Scans content directly */ async scanContent(context, content, source) { // Find appropriate processor based on source const processor = this.findProcessor(source) || this.processors.get('text'); if (!processor) { return (0, Result_1.err)(new Error(`No processor found for content`)); } // Process content const processedResult = await processor.process(content, context); if (processedResult.isErr()) { return (0, Result_1.err)(processedResult.error); } // Apply rules to processed content const violations = []; const enabledRules = context.rulePack.getEnabledRules(); for (const item of processedResult.value) { const itemViolations = await this.ruleEngine.applyRules(item.fields, enabledRules, item.metadata); if (itemViolations.isOk()) { violations.push(...itemViolations.value); } } // Collect metrics const baseMetrics = this.metricsCollector.end(); const metrics = { ...baseMetrics, objectsScanned: processedResult.value.length, rulesApplied: enabledRules.length, streamingUsed: false, }; return (0, Result_1.ok)(new ScanResult_1.ScanResult(violations, metrics)); } /** * Scans with streaming for large files */ async scanWithStreaming(context, content, processor) { const violations = []; const enabledRules = context.rulePack.getEnabledRules(); let itemCount = 0; const result = await processor.processStream(content, context, async (item) => { const itemViolations = await this.ruleEngine.applyRules(item.fields, enabledRules, item.metadata); if (itemViolations.isOk()) { violations.push(...itemViolations.value); } itemCount++; this.metricsCollector.recordProcessing(itemCount, process.memoryUsage().heapUsed); }); if (result.isErr()) { return (0, Result_1.err)(result.error); } const baseMetrics = this.metricsCollector.end(); const metrics = { ...baseMetrics, objectsScanned: itemCount, rulesApplied: enabledRules.length, streamingUsed: true, }; return (0, Result_1.ok)(new ScanResult_1.ScanResult(violations, metrics)); } /** * Finds appropriate processor for a file */ findProcessor(filePath) { for (const [, processor] of this.processors) { if (processor.canProcess(filePath)) { return processor; } } return undefined; } } exports.DefaultScanOrchestrator = DefaultScanOrchestrator;