UNPKG

@dawans/promptshield

Version:

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

252 lines (251 loc) 11.2 kB
"use strict"; 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;