@dawans/promptshield
Version:
Secure your LLM stack with enterprise-grade RulePacks for AI safety scanning
213 lines (212 loc) • 8 kB
JavaScript
"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;