UNPKG

@dawans/promptshield

Version:

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

220 lines (219 loc) 7.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultRuleMatcher = exports.DefaultRuleEngine = void 0; const Result_1 = require("../../../../shared/types/Result"); /** * Default implementation of the rule engine */ class DefaultRuleEngine { constructor(repository, matcher) { this.repository = repository; this.matcher = matcher; } /** * Loads a rulepack from a file or default location */ async loadRulePack(path) { try { // Use default path if 'default' is specified const rulePackPath = path === 'default' ? this.repository.getDefaultPath() : path; const result = await this.repository.loadFromYaml(rulePackPath); if (result.isErr()) { return (0, Result_1.err)(result.error); } // Validate the loaded rulepack const validationResult = this.validateRulePack(result.value); if (validationResult.isErr()) { return (0, Result_1.err)(validationResult.error); } return (0, Result_1.ok)(result.value); } catch (error) { return (0, Result_1.err)(new Error(`Failed to load rulepack: ${error}`)); } } /** * Validates a rulepack */ validateRulePack(rulePack) { try { // RulePack constructor already validates, but we can add extra validation here if (rulePack.getEnabledRules().length === 0) { return (0, Result_1.err)(new Error('RulePack has no enabled rules')); } return (0, Result_1.ok)(undefined); } catch (error) { return (0, Result_1.err)(new Error(`RulePack validation failed: ${error}`)); } } /** * Applies rules to content and returns violations */ async applyRules(fields, rules, metadata) { try { const violations = []; for (const rule of rules) { if (!rule.enabled) continue; // Apply rule to each field for (const [fieldName, fieldValue] of Object.entries(fields)) { if (!fieldValue) continue; const matches = this.matcher.match(fieldValue, rule); for (const match of matches) { if (match.matched) { violations.push(this.createViolation(rule, fieldName, match, metadata)); } } } } return (0, Result_1.ok)(violations); } catch (error) { return (0, Result_1.err)(new Error(`Failed to apply rules: ${error}`)); } } /** * Tests a single rule against content */ testRule(content, rule) { try { const matches = this.matcher.match(content, rule); return (0, Result_1.ok)({ matches: matches.some((m) => m.matched), positions: matches.filter((m) => m.matched).map((m) => m.position), }); } catch (error) { return (0, Result_1.err)(new Error(`Failed to test rule: ${error}`)); } } /** * Creates a violation from a match */ createViolation(rule, field, match, metadata) { return { ruleId: rule.id, ruleName: rule.id, // Using ID as name for now ruleDescription: rule.description, severity: rule.severity, category: rule.category, message: `${rule.description} found in ${field}`, field, objectIndex: metadata?.index ?? 0, position: match.position, context: match.context, metadata: { pattern: match.pattern, confidence: 1.0, tags: [], }, }; } } exports.DefaultRuleEngine = DefaultRuleEngine; /** * Default implementation of rule matcher */ class DefaultRuleMatcher { /** * Matches a rule against content */ match(content, rule) { const matches = []; // Match regex patterns if (rule.hasRegexPatterns()) { const regexMatches = this.matchRegexPatterns(content, rule); matches.push(...regexMatches); } // Match keyword patterns if (rule.hasKeywordPatterns()) { const keywordMatches = this.matchKeywordPatterns(content, rule); matches.push(...keywordMatches); } return matches; } /** * Matches regex patterns */ matchRegexPatterns(content, rule) { const matches = []; const patterns = rule.getCompiledRegexPatterns(); for (const pattern of patterns) { let match; while ((match = pattern.exec(content)) !== null) { const start = match.index; const end = start + match[0].length; matches.push({ matched: true, position: { start, end, line: this.getLineNumber(content, start), column: this.getColumnNumber(content, start), }, context: this.getContext(content, start, end), pattern: pattern.source, }); } } return matches; } /** * Matches keyword patterns */ matchKeywordPatterns(content, rule) { const matches = []; const keywords = rule.getNormalizedKeywords(); const searchContent = rule.caseSensitive ? content : content.toLowerCase(); for (const keyword of keywords) { let index = 0; while ((index = searchContent.indexOf(keyword, index)) !== -1) { const start = index; const end = start + keyword.length; matches.push({ matched: true, position: { start, end, line: this.getLineNumber(content, start), column: this.getColumnNumber(content, start), }, context: this.getContext(content, start, end), pattern: keyword, }); index = end; } } return matches; } /** * Gets context around a match */ getContext(content, start, end, contextSize = 50) { const beforeStart = Math.max(0, start - contextSize); const afterEnd = Math.min(content.length, end + contextSize); return { before: content.substring(beforeStart, start), match: content.substring(start, end), after: content.substring(end, afterEnd), }; } /** * Gets line number for a position */ getLineNumber(content, position) { const lines = content.substring(0, position).split('\n'); return lines.length; } /** * Gets column number for a position */ getColumnNumber(content, position) { const lines = content.substring(0, position).split('\n'); const lastLine = lines[lines.length - 1]; return lastLine.length + 1; } } exports.DefaultRuleMatcher = DefaultRuleMatcher;