@dawans/promptshield
Version:
Secure your LLM stack with enterprise-grade RulePacks for AI safety scanning
252 lines (251 loc) • 11.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.RulePackValidatorImpl = void 0;
const Result_1 = require("../../../../shared/types/Result");
const ValidationResult_1 = require("../../core/entities/ValidationResult");
const RulePack_1 = require("../../../rules/core/entities/RulePack");
const fs = __importStar(require("fs"));
const yaml = __importStar(require("js-yaml"));
const path = __importStar(require("path"));
/**
* RulePack validator implementation
*/
class RulePackValidatorImpl {
async validate(target, options) {
return await this.validateRulePack(target, options);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
supports(target, _options) {
const ext = path.extname(target).toLowerCase();
return ext === '.yaml' || ext === '.yml';
}
async validateRulePack(filePath, options) {
const builder = new ValidationResult_1.ValidationResultBuilder(filePath, 'rulepack');
try {
// Check if file exists
if (!fs.existsSync(filePath)) {
builder.addError('file', `RulePack file not found: ${filePath}`, 'FILE_NOT_FOUND');
return (0, Result_1.ok)(builder.build());
}
// Check if file is readable
try {
fs.accessSync(filePath, fs.constants.R_OK);
}
catch {
builder.addError('file', `RulePack file is not readable: ${filePath}`, 'FILE_NOT_READABLE');
return (0, Result_1.ok)(builder.build());
}
// Read file content
const content = await fs.promises.readFile(filePath, 'utf-8');
// Validate YAML syntax
const yamlResult = await this.validateYamlSyntax(content);
if (yamlResult.isErr()) {
builder.addError('yaml', yamlResult.error.message, 'YAML_SYNTAX_ERROR');
return (0, Result_1.ok)(builder.build());
}
// Parse YAML
let rulePackData;
try {
rulePackData = yaml.load(content);
}
catch (error) {
builder.addError('yaml', `Invalid YAML: ${error}`, 'YAML_PARSE_ERROR');
return (0, Result_1.ok)(builder.build());
}
// Validate basic structure
if (!rulePackData || typeof rulePackData !== 'object') {
builder.addError('structure', 'RulePack must be a valid object', 'INVALID_STRUCTURE');
return (0, Result_1.ok)(builder.build());
}
// Validate required fields
this.validateRequiredFields(rulePackData, builder, options);
// Validate rules array
if (rulePackData.rules && Array.isArray(rulePackData.rules)) {
await this.validateRulesArray(rulePackData.rules, builder, options);
}
else {
builder.addError('rules', 'RulePack must contain a rules array', 'MISSING_RULES');
}
// Validate using RulePack entity (if structure is valid)
if (builder.build().isValid) {
try {
RulePack_1.RulePack.fromYaml(rulePackData);
}
catch (error) {
builder.addError('schema', `RulePack schema validation failed: ${error}`, 'SCHEMA_VALIDATION_ERROR');
}
}
return (0, Result_1.ok)(builder.build());
}
catch (error) {
return (0, Result_1.err)(new Error(`RulePack validation failed: ${error}`));
}
}
async validateYamlSyntax(content) {
try {
yaml.load(content);
return (0, Result_1.ok)(true);
}
catch (error) {
return (0, Result_1.err)(new Error(`YAML syntax error: ${error}`));
}
}
async validateRuleSchema(ruleData) {
try {
// Validate individual rule structure
if (!ruleData || typeof ruleData !== 'object') {
return (0, Result_1.err)(new Error('Rule must be a valid object'));
}
// Required fields
if (!ruleData.id || typeof ruleData.id !== 'string') {
return (0, Result_1.err)(new Error('Rule must have a valid id'));
}
if (!ruleData.description || typeof ruleData.description !== 'string') {
return (0, Result_1.err)(new Error('Rule must have a valid description'));
}
// Must have either match_keywords or match_regex
if (!ruleData.match_keywords && !ruleData.match_regex) {
return (0, Result_1.err)(new Error('Rule must have either match_keywords or match_regex'));
}
return (0, Result_1.ok)(true);
}
catch (error) {
return (0, Result_1.err)(new Error(`Rule schema validation failed: ${error}`));
}
}
async validateRegexPatterns(rules) {
const builder = new ValidationResult_1.ValidationResultBuilder('regex-patterns', 'rulepack');
try {
for (const rule of rules) {
if (rule.match_regex && Array.isArray(rule.match_regex)) {
for (let i = 0; i < rule.match_regex.length; i++) {
const pattern = rule.match_regex[i];
try {
new RegExp(pattern);
}
catch (error) {
builder.addError(`rules.${rule.id}.match_regex[${i}]`, `Invalid regex pattern: ${pattern} - ${error}`, 'INVALID_REGEX');
}
}
}
}
return (0, Result_1.ok)(builder.build());
}
catch (error) {
return (0, Result_1.err)(new Error(`Regex validation failed: ${error}`));
}
}
validateRequiredFields(rulePackData, builder, options) {
// Required fields
if (!rulePackData.name || typeof rulePackData.name !== 'string') {
builder.addError('name', 'RulePack must have a valid name', 'MISSING_NAME');
}
if (!rulePackData.description ||
typeof rulePackData.description !== 'string') {
builder.addError('description', 'RulePack must have a valid description', 'MISSING_DESCRIPTION');
}
if (!rulePackData.version || typeof rulePackData.version !== 'string') {
builder.addError('version', 'RulePack must have a valid version', 'MISSING_VERSION');
}
// Optional fields in strict mode
if (options.strict) {
if (!rulePackData.last_updated) {
builder.addWarning('last_updated', 'RulePack should have a last_updated field', 'MISSING_LAST_UPDATED');
}
if (!rulePackData.author) {
builder.addWarning('author', 'RulePack should have an author field', 'MISSING_AUTHOR');
}
}
}
async validateRulesArray(rules, builder, options) {
if (rules.length === 0) {
builder.addWarning('rules', 'RulePack has no rules defined', 'NO_RULES');
return;
}
const seenIds = new Set();
let errorCount = 0;
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
const rulePrefix = `rules[${i}]`;
// Check max errors limit
if (errorCount >= options.maxErrors) {
builder.addWarning('validation', `Validation stopped after ${options.maxErrors} errors`, 'MAX_ERRORS_REACHED');
break;
}
// Validate rule schema
const schemaResult = await this.validateRuleSchema(rule);
if (schemaResult.isErr()) {
builder.addError(rulePrefix, schemaResult.error.message, 'RULE_SCHEMA_ERROR');
errorCount++;
continue;
}
// Check for duplicate IDs
if (seenIds.has(rule.id)) {
builder.addError(`${rulePrefix}.id`, `Duplicate rule ID: ${rule.id}`, 'DUPLICATE_ID');
errorCount++;
}
else {
seenIds.add(rule.id);
}
// Validate regex patterns if present
if (rule.match_regex && options.validateRegex) {
const regexResult = await this.validateRegexPatterns([rule]);
if (regexResult.isOk() && !regexResult.value.isValid) {
for (const error of regexResult.value.errors) {
builder.addError(`${rulePrefix}.${error.field}`, error.message, error.code);
errorCount++;
}
}
}
// Strict mode validations
if (options.strict) {
if (!rule.category || typeof rule.category !== 'string') {
builder.addError(`${rulePrefix}.category`, 'Rule must have a valid category in strict mode', 'MISSING_CATEGORY');
errorCount++;
}
if (!rule.severity || typeof rule.severity !== 'string') {
builder.addError(`${rulePrefix}.severity`, 'Rule must have a valid severity in strict mode', 'MISSING_SEVERITY');
errorCount++;
}
if (!['low', 'medium', 'high', 'critical'].includes(rule.severity)) {
builder.addError(`${rulePrefix}.severity`, 'Invalid severity level. Must be: low, medium, high, or critical', 'INVALID_SEVERITY');
errorCount++;
}
}
}
}
}
exports.RulePackValidatorImpl = RulePackValidatorImpl;