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