sourcefinder/src/security/AntiInjectionEngine.js
Alexis Trouvé a7bd6115b7
Some checks failed
SourceFinder CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
SourceFinder CI/CD Pipeline / Unit Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Integration Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Performance Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Code Coverage Report (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (16.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (18.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (20.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Regression Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Audit (push) Has been cancelled
SourceFinder CI/CD Pipeline / Notify Results (push) Has been cancelled
feat: Implémentation complète du système SourceFinder avec tests
- Architecture modulaire avec injection de dépendances
- Système de scoring intelligent multi-facteurs (spécificité, fraîcheur, qualité, réutilisation)
- Moteur anti-injection 4 couches (preprocessing, patterns, sémantique, pénalités)
- API REST complète avec validation et rate limiting
- Repository JSON avec index mémoire et backup automatique
- Provider LLM modulaire pour génération de contenu
- Suite de tests complète (Jest) :
  * Tests unitaires pour sécurité et scoring
  * Tests d'intégration API end-to-end
  * Tests de sécurité avec simulation d'attaques
  * Tests de performance et charge
- Pipeline CI/CD avec GitHub Actions
- Logging structuré et monitoring
- Configuration ESLint et environnement de test

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-15 23:06:10 +08:00

975 lines
29 KiB
JavaScript

/**
* AntiInjectionEngine - Système de protection anti-prompt injection
* Implémente les 4 couches de sécurité selon CDC :
* Layer 1: Content Preprocessing
* Layer 2: Pattern Detection
* Layer 3: Semantic Validation
* Layer 4: Source Scoring avec pénalités
*/
const logger = require('../utils/logger');
const { setupTracer } = logger;
class AntiInjectionEngine {
constructor() {
this.tracer = setupTracer('AntiInjectionEngine');
// Patterns dangereux - Layer 2
this.dangerousPatterns = [
// Instructions directes
/ignore\s+previous\s+instructions/gi,
/you\s+are\s+now/gi,
/forget\s+everything/gi,
/new\s+instructions?:/gi,
/system\s+prompt:/gi,
/override\s+instructions/gi,
/disregard\s+(all\s+)?previous/gi,
// Redirections de contexte
/instead\s+of\s+writing\s+about/gi,
/don't\s+write\s+about.*write\s+about/gi,
/change\s+the\s+topic\s+to/gi,
/focus\s+on.*instead/gi,
// Injections de code/commandes
/<script[^>]*>/gi,
/<iframe[^>]*>/gi,
/javascript:/gi,
/eval\s*\(/gi,
/exec\s*\(/gi,
/system\s*\(/gi,
/\$\{.*\}/g, // Template literals
/`.*`/g, // Backticks
// Métaprompts et tests
/this\s+is\s+a\s+test/gi,
/output\s+json\s+format/gi,
/return\s+only/gi,
/respond\s+with\s+only/gi,
/answer\s+with\s+(yes|no|true|false)(\s+only)?/gi,
// Tentatives de manipulation
/pretend\s+(to\s+be|you\s+are)/gi,
/act\s+as\s+(if\s+)?you/gi,
/simulate\s+(being|that)/gi,
/role\s*play/gi,
// Bypass attempts
/\/\*.*ignore.*\*\//gi,
/<!--.*ignore.*-->/gi,
/\\n\\n/g, // Tentatives newlines
/\n\s*\n\s*---/g // Séparateurs suspects
];
// Patterns de validation sémantique - Layer 3
this.semanticValidationRules = [
{
name: 'dog_breed_context',
pattern: /(chien|dog|race|breed|canin)/gi,
minMatches: 1,
weight: 0.4
},
{
name: 'animal_context',
pattern: /(animal|pet|élevage|vétérinaire|comportement)/gi,
minMatches: 1,
weight: 0.3
},
{
name: 'relevant_topics',
pattern: /(santé|alimentation|dressage|éducation|soins|exercice)/gi,
minMatches: 1,
weight: 0.3
}
];
// Scores de pénalité - Layer 4
this.penaltyScores = {
PROMPT_INJECTION_DETECTED: -50,
SEMANTIC_INCONSISTENCY: -30,
UNTRUSTED_SOURCE_HISTORY: -20,
SUSPICIOUS_CONTENT_STRUCTURE: -15,
MODERATE_RISK_INDICATORS: -10
};
// Statistiques
this.stats = {
totalValidated: 0,
injectionAttempts: 0,
semanticFailures: 0,
falsePositives: 0,
averageProcessingTime: 0,
riskLevelDistribution: {
low: 0,
medium: 0,
high: 0,
critical: 0
}
};
// Cache des résultats de validation
this.validationCache = new Map();
this.cacheTimeout = 300000; // 5 minutes
}
/**
* Valider le contenu principal - Point d'entrée
* @param {Object} content - Contenu à valider
* @param {Object} context - Contexte de validation
* @returns {Promise<Object>} Résultat de validation complet
*/
async validateContent(content, context = {}) {
return await this.tracer.run('validateContent', async () => {
const startTime = Date.now();
this.stats.totalValidated++;
try {
// Générer clé de cache
const cacheKey = this.generateCacheKey(content, context);
// Vérifier cache
const cachedResult = this.getFromCache(cacheKey);
if (cachedResult) {
return cachedResult;
}
logger.debug('Starting content validation', {
contentLength: content.content?.length || 0,
source: content.sourceType || 'unknown',
raceCode: context.raceCode
});
// Layer 1: Préprocessing du contenu
const preprocessResult = await this.layer1_preprocessContent(content);
// Layer 2: Détection de patterns
const patternResult = await this.layer2_detectPatterns(preprocessResult);
// Layer 3: Validation sémantique
const semanticResult = await this.layer3_semanticValidation(preprocessResult, context);
// Layer 4: Calcul des pénalités
const penaltyResult = await this.layer4_calculatePenalties(patternResult, semanticResult, content);
// Construire résultat final
const validationResult = {
isValid: this.determineValidityStatus(patternResult, semanticResult, penaltyResult),
riskLevel: this.calculateRiskLevel(patternResult, semanticResult),
processingTime: Date.now() - startTime,
// Détails par couche
layers: {
preprocessing: preprocessResult,
patternDetection: patternResult,
semanticValidation: semanticResult,
penalties: penaltyResult
},
// Contenu nettoyé
cleanedContent: {
...content,
title: preprocessResult.cleanedTitle,
content: preprocessResult.cleanedContent
},
// Métadonnées de sécurité
securityMetadata: {
engine: 'AntiInjectionEngine',
version: '1.0',
validatedAt: new Date().toISOString(),
context: {
raceCode: context.raceCode,
sourceType: content.sourceType,
clientId: context.clientId
}
},
// Recommandations
recommendations: this.generateSecurityRecommendations(patternResult, semanticResult, penaltyResult)
};
// Mise en cache
this.cacheResult(cacheKey, validationResult);
// Mise à jour statistiques
this.updateValidationStats(validationResult);
// Logging selon niveau de risque
this.logValidationResult(validationResult, content, context);
return validationResult;
} catch (error) {
logger.error('Content validation failed', error, {
contentId: content.id,
raceCode: context.raceCode
});
return {
isValid: false,
riskLevel: 'critical',
error: error.message,
processingTime: Date.now() - startTime,
securityMetadata: {
engine: 'AntiInjectionEngine',
status: 'error',
validatedAt: new Date().toISOString()
}
};
}
}, {
contentId: content.id,
raceCode: context.raceCode
});
}
/**
* Layer 1: Préprocessing et nettoyage du contenu
*/
async layer1_preprocessContent(content) {
return await this.tracer.run('layer1_preprocessing', async () => {
const originalTitle = content.title || '';
const originalContent = content.content || '';
// Normalisation de base
let cleanedTitle = originalTitle.trim();
let cleanedContent = originalContent.trim();
// Supprimer HTML potentiellement dangereux
cleanedTitle = this.removeHtmlTags(cleanedTitle);
cleanedContent = this.removeHtmlTags(cleanedContent);
// Normaliser espaces et caractères
cleanedTitle = this.normalizeWhitespace(cleanedTitle);
cleanedContent = this.normalizeWhitespace(cleanedContent);
// Supprimer caractères de contrôle suspects
cleanedTitle = this.removeControlCharacters(cleanedTitle);
cleanedContent = this.removeControlCharacters(cleanedContent);
// Encoder caractères potentiellement dangereux
cleanedTitle = this.encodeSpecialCharacters(cleanedTitle);
cleanedContent = this.encodeSpecialCharacters(cleanedContent);
return {
cleanedTitle,
cleanedContent,
originalTitle,
originalContent,
changesApplied: {
htmlRemoved: originalContent !== cleanedContent,
whitespaceNormalized: true,
controlCharsRemoved: true,
specialCharsEncoded: true
},
cleaningStats: {
titleLengthChange: originalTitle.length - cleanedTitle.length,
contentLengthChange: originalContent.length - cleanedContent.length,
cleaningScore: this.calculateCleaningScore(originalContent, cleanedContent)
}
};
});
}
/**
* Layer 2: Détection de patterns dangereux
*/
async layer2_detectPatterns(preprocessResult) {
return await this.tracer.run('layer2_patternDetection', async () => {
const { cleanedTitle, cleanedContent } = preprocessResult;
const fullText = `${cleanedTitle} ${cleanedContent}`;
const detectedPatterns = [];
let totalRiskScore = 0;
// Analyser chaque pattern dangereux
for (const [index, pattern] of this.dangerousPatterns.entries()) {
const matches = fullText.match(pattern);
if (matches && matches.length > 0) {
const patternInfo = {
patternIndex: index,
pattern: pattern.source,
matches: matches,
matchCount: matches.length,
riskWeight: this.getPatternRiskWeight(pattern),
locations: this.findPatternLocations(fullText, pattern)
};
detectedPatterns.push(patternInfo);
totalRiskScore += patternInfo.riskWeight * patternInfo.matchCount;
}
}
// Analyser structure suspecte
const structureAnalysis = this.analyzeContentStructure(fullText);
if (structureAnalysis.suspicious) {
totalRiskScore += structureAnalysis.riskScore;
}
return {
detectedPatterns,
totalPatterns: detectedPatterns.length,
totalRiskScore,
maxIndividualRisk: Math.max(...detectedPatterns.map(p => p.riskWeight), 0),
structureAnalysis,
hasHighRiskPatterns: detectedPatterns.some(p => p.riskWeight >= 8),
hasMediumRiskPatterns: detectedPatterns.some(p => p.riskWeight >= 5),
summary: this.summarizePatternDetection(detectedPatterns, totalRiskScore)
};
});
}
/**
* Layer 3: Validation sémantique
*/
async layer3_semanticValidation(preprocessResult, context) {
return await this.tracer.run('layer3_semanticValidation', async () => {
const { cleanedTitle, cleanedContent } = preprocessResult;
const fullText = `${cleanedTitle} ${cleanedContent}`;
const validationResults = [];
let semanticScore = 0;
let totalWeight = 0;
// Appliquer chaque règle de validation sémantique
for (const rule of this.semanticValidationRules) {
const matches = fullText.match(rule.pattern);
const matchCount = matches ? matches.length : 0;
const ruleResult = {
ruleName: rule.name,
required: rule.minMatches,
found: matchCount,
passed: matchCount >= rule.minMatches,
weight: rule.weight,
matches: matches || [],
score: matchCount >= rule.minMatches ? rule.weight : 0
};
validationResults.push(ruleResult);
semanticScore += ruleResult.score;
totalWeight += rule.weight;
}
// Validation spécifique au contexte race
const raceValidation = await this.validateRaceContext(fullText, context.raceCode);
// Détection d'incohérences
const inconsistencies = this.detectSemanticInconsistencies(fullText, context);
// Score sémantique final (0-1)
const finalSemanticScore = totalWeight > 0 ? semanticScore / totalWeight : 0;
return {
validationResults,
raceValidation,
inconsistencies,
semanticScore: finalSemanticScore,
passed: finalSemanticScore >= 0.3, // Seuil minimum 30%
confidence: this.calculateSemanticConfidence(validationResults, inconsistencies),
contextRelevance: this.assessContextRelevance(fullText, context),
recommendations: this.generateSemanticRecommendations(validationResults, raceValidation)
};
});
}
/**
* Layer 4: Calcul des pénalités et score final
*/
async layer4_calculatePenalties(patternResult, semanticResult, content) {
return await this.tracer.run('layer4_penalties', async () => {
let totalPenalty = 0;
const appliedPenalties = [];
// Pénalité injection détectée
if (patternResult.hasHighRiskPatterns) {
totalPenalty += this.penaltyScores.PROMPT_INJECTION_DETECTED;
appliedPenalties.push({
type: 'PROMPT_INJECTION_DETECTED',
score: this.penaltyScores.PROMPT_INJECTION_DETECTED,
reason: `${patternResult.totalPatterns} patterns dangereux détectés`
});
}
// Pénalité incohérence sémantique
if (!semanticResult.passed) {
totalPenalty += this.penaltyScores.SEMANTIC_INCONSISTENCY;
appliedPenalties.push({
type: 'SEMANTIC_INCONSISTENCY',
score: this.penaltyScores.SEMANTIC_INCONSISTENCY,
reason: `Score sémantique: ${Math.round(semanticResult.semanticScore * 100)}%`
});
}
// Pénalité source historique
const sourceHistory = await this.checkSourceHistory(content);
if (sourceHistory.isUntrusted) {
totalPenalty += this.penaltyScores.UNTRUSTED_SOURCE_HISTORY;
appliedPenalties.push({
type: 'UNTRUSTED_SOURCE_HISTORY',
score: this.penaltyScores.UNTRUSTED_SOURCE_HISTORY,
reason: sourceHistory.reason
});
}
// Pénalité structure suspecte
if (patternResult.structureAnalysis.suspicious) {
totalPenalty += this.penaltyScores.SUSPICIOUS_CONTENT_STRUCTURE;
appliedPenalties.push({
type: 'SUSPICIOUS_CONTENT_STRUCTURE',
score: this.penaltyScores.SUSPICIOUS_CONTENT_STRUCTURE,
reason: patternResult.structureAnalysis.reason
});
}
// Pénalité risque modéré
if (patternResult.hasMediumRiskPatterns && !patternResult.hasHighRiskPatterns) {
totalPenalty += this.penaltyScores.MODERATE_RISK_INDICATORS;
appliedPenalties.push({
type: 'MODERATE_RISK_INDICATORS',
score: this.penaltyScores.MODERATE_RISK_INDICATORS,
reason: 'Patterns de risque modéré détectés'
});
}
return {
totalPenalty,
appliedPenalties,
penaltyCount: appliedPenalties.length,
maxIndividualPenalty: Math.min(...appliedPenalties.map(p => p.score), 0),
sourceHistory,
finalRecommendation: this.generateFinalRecommendation(totalPenalty, patternResult, semanticResult)
};
});
}
// === Méthodes utilitaires ===
removeHtmlTags(text) {
return text
.replace(/<script[^>]*>.*?<\/script>/gi, '')
.replace(/<style[^>]*>.*?<\/style>/gi, '')
.replace(/<[^>]*>/g, '');
}
normalizeWhitespace(text) {
return text
.replace(/\s+/g, ' ')
.replace(/\n\s*\n/g, '\n')
.trim();
}
removeControlCharacters(text) {
return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
}
encodeSpecialCharacters(text) {
const specialChars = {
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'&': '&amp;'
};
return text.replace(/[<>"'&]/g, char => specialChars[char]);
}
calculateCleaningScore(original, cleaned) {
if (original === cleaned) return 100;
const lengthDiff = Math.abs(original.length - cleaned.length);
const maxLength = Math.max(original.length, cleaned.length);
return Math.max(0, 100 - ((lengthDiff / maxLength) * 100));
}
getPatternRiskWeight(pattern) {
const source = pattern.source.toLowerCase();
if (source.includes('ignore') || source.includes('forget')) return 10;
if (source.includes('script') || source.includes('eval')) return 9;
if (source.includes('system') || source.includes('exec')) return 8;
if (source.includes('instead') || source.includes('pretend')) return 7;
if (source.includes('json') || source.includes('only')) return 6;
return 5; // Risque par défaut
}
findPatternLocations(text, pattern) {
const locations = [];
let match;
pattern.lastIndex = 0; // Reset regex
while ((match = pattern.exec(text)) !== null) {
locations.push({
start: match.index,
end: match.index + match[0].length,
context: text.substring(Math.max(0, match.index - 20), match.index + match[0].length + 20)
});
if (!pattern.global) break;
}
return locations;
}
analyzeContentStructure(text) {
let riskScore = 0;
let suspicious = false;
const reasons = [];
// Trop de newlines consécutives
const excessiveNewlines = (text.match(/\n{3,}/g) || []).length;
if (excessiveNewlines > 3) {
riskScore += 2;
suspicious = true;
reasons.push('Trop de sauts de ligne consécutifs');
}
// Caractères de séparation suspects
const suspiciousSeparators = (text.match(/---+|===+|\*\*\*+/g) || []).length;
if (suspiciousSeparators > 2) {
riskScore += 3;
suspicious = true;
reasons.push('Séparateurs suspects détectés');
}
// Ratio majuscules anormal
const upperCaseRatio = (text.match(/[A-Z]/g) || []).length / text.length;
if (upperCaseRatio > 0.3) {
riskScore += 2;
suspicious = true;
reasons.push('Ratio majuscules anormal');
}
return {
suspicious,
riskScore,
reasons: reasons.join(', '),
metrics: {
excessiveNewlines,
suspiciousSeparators,
upperCaseRatio: Math.round(upperCaseRatio * 100)
}
};
}
summarizePatternDetection(patterns, totalRiskScore) {
if (patterns.length === 0) {
return 'Aucun pattern dangereux détecté';
}
const highRisk = patterns.filter(p => p.riskWeight >= 8).length;
const mediumRisk = patterns.filter(p => p.riskWeight >= 5 && p.riskWeight < 8).length;
const lowRisk = patterns.length - highRisk - mediumRisk;
return `${patterns.length} patterns détectés (Risque élevé: ${highRisk}, moyen: ${mediumRisk}, faible: ${lowRisk})`;
}
async validateRaceContext(text, raceCode) {
if (!raceCode) return { passed: true, score: 1, reason: 'Pas de race spécifique à valider' };
// Extraire numéro de race
const raceNumber = raceCode.split('-')[0];
// Rechercher mentions de la race
const racePattern = new RegExp(`(${raceNumber}|race\\s+${raceNumber})`, 'gi');
const raceMatches = text.match(racePattern);
const passed = raceMatches && raceMatches.length > 0;
const score = passed ? 1 : 0;
return {
passed,
score,
matches: raceMatches || [],
reason: passed ? 'Race mentionnée dans le contenu' : 'Race non mentionnée'
};
}
detectSemanticInconsistencies(text, context) {
const inconsistencies = [];
// Vérifier cohérence animal/chien
const hasAnimalMention = /animal|pet/gi.test(text);
const hasDogMention = /chien|dog|canin/gi.test(text);
if (hasAnimalMention && !hasDogMention && context.raceCode) {
inconsistencies.push({
type: 'animal_type_mismatch',
severity: 'medium',
description: 'Mention d\'animaux mais pas de chiens spécifiquement'
});
}
// Vérifier langue cohérente
const frenchWords = (text.match(/\b(le|la|les|de|du|des|et|avec|pour|dans)\b/gi) || []).length;
const englishWords = (text.match(/\b(the|and|with|for|in|of|to|a|an)\b/gi) || []).length;
if (frenchWords > 0 && englishWords > frenchWords) {
inconsistencies.push({
type: 'language_inconsistency',
severity: 'low',
description: 'Mélange de français et anglais détecté'
});
}
return inconsistencies;
}
calculateSemanticConfidence(validationResults, inconsistencies) {
const passedRules = validationResults.filter(r => r.passed).length;
const totalRules = validationResults.length;
const baseConfidence = totalRules > 0 ? passedRules / totalRules : 0;
const inconsistencyPenalty = inconsistencies.length * 0.1;
return Math.max(0, baseConfidence - inconsistencyPenalty);
}
assessContextRelevance(text, context) {
let relevanceScore = 0;
const factors = [];
// Contexte race
if (context.raceCode && text.includes(context.raceCode.split('-')[0])) {
relevanceScore += 0.3;
factors.push('Race code found');
}
// Contexte produit
if (context.productContext && text.toLowerCase().includes(context.productContext.toLowerCase())) {
relevanceScore += 0.2;
factors.push('Product context relevant');
}
// Mots-clés pertinents
const relevantKeywords = ['éducation', 'santé', 'comportement', 'alimentation', 'soins'];
const foundKeywords = relevantKeywords.filter(keyword => text.toLowerCase().includes(keyword));
relevanceScore += foundKeywords.length * 0.1;
if (foundKeywords.length > 0) {
factors.push(`${foundKeywords.length} keywords found`);
}
return {
score: Math.min(1, relevanceScore),
factors,
foundKeywords
};
}
generateSemanticRecommendations(validationResults, raceValidation) {
const recommendations = [];
const failedRules = validationResults.filter(r => !r.passed);
if (failedRules.length > 0) {
recommendations.push({
type: 'semantic_improvement',
priority: 'high',
message: `Améliorer la pertinence pour: ${failedRules.map(r => r.ruleName).join(', ')}`
});
}
if (!raceValidation.passed) {
recommendations.push({
type: 'race_context',
priority: 'medium',
message: 'Mentionner la race spécifique dans le contenu'
});
}
return recommendations;
}
async checkSourceHistory(content) {
// Simulation - À intégrer avec le système de stock
const sourceDomain = content.sourceDomain || content.url;
if (!sourceDomain) {
return { isUntrusted: false, reason: 'Pas de domaine source' };
}
// Sources connues non fiables
const untrustedDomains = ['example.com', 'test.com', 'spam.com'];
if (untrustedDomains.some(domain => sourceDomain.includes(domain))) {
return {
isUntrusted: true,
reason: `Source ${sourceDomain} dans la liste des domaines non fiables`
};
}
return { isUntrusted: false, reason: 'Source fiable' };
}
generateFinalRecommendation(totalPenalty, patternResult, semanticResult) {
if (totalPenalty <= -50 || patternResult.hasHighRiskPatterns) {
return {
action: 'REJECT',
reason: 'Risque sécuritaire critique détecté',
confidence: 'high'
};
}
if (totalPenalty <= -30 || !semanticResult.passed) {
return {
action: 'QUARANTINE',
reason: 'Contenu suspect nécessitant révision manuelle',
confidence: 'medium'
};
}
if (totalPenalty <= -10 || patternResult.hasMediumRiskPatterns) {
return {
action: 'ACCEPT_WITH_MONITORING',
reason: 'Risque faible mais surveillance recommandée',
confidence: 'medium'
};
}
return {
action: 'ACCEPT',
reason: 'Contenu validé, aucun risque détecté',
confidence: 'high'
};
}
generateSecurityRecommendations(patternResult, semanticResult, penaltyResult) {
const recommendations = [];
if (patternResult.hasHighRiskPatterns) {
recommendations.push({
type: 'CRITICAL',
message: 'Patterns d\'injection détectés - Rejeter le contenu',
patterns: patternResult.detectedPatterns.map(p => p.pattern)
});
}
if (!semanticResult.passed) {
recommendations.push({
type: 'WARNING',
message: 'Contenu peu pertinent au contexte demandé',
score: Math.round(semanticResult.semanticScore * 100)
});
}
if (penaltyResult.sourceHistory.isUntrusted) {
recommendations.push({
type: 'INFO',
message: 'Source historiquement non fiable',
details: penaltyResult.sourceHistory.reason
});
}
return recommendations;
}
determineValidityStatus(patternResult, semanticResult, penaltyResult) {
// Rejet immédiat si patterns critiques
if (patternResult.hasHighRiskPatterns) return false;
// Rejet si pénalités trop élevées
if (penaltyResult.totalPenalty <= -50) return false;
// Rejet si sémantique insuffisante ET patterns suspects
if (!semanticResult.passed && patternResult.hasMediumRiskPatterns) return false;
return true;
}
calculateRiskLevel(patternResult, semanticResult) {
if (patternResult.hasHighRiskPatterns) return 'critical';
if (patternResult.totalRiskScore >= 15) return 'high';
if (!semanticResult.passed || patternResult.hasMediumRiskPatterns) return 'medium';
return 'low';
}
// === Cache et performances ===
generateCacheKey(content, context) {
const contentHash = this.simpleHash(content.content + content.title);
const contextHash = this.simpleHash(JSON.stringify(context));
return `validation:${contentHash}:${contextHash}`;
}
simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return hash.toString(36);
}
getFromCache(cacheKey) {
const cached = this.validationCache.get(cacheKey);
if (!cached) return null;
if (Date.now() - cached.timestamp > this.cacheTimeout) {
this.validationCache.delete(cacheKey);
return null;
}
return cached.result;
}
cacheResult(cacheKey, result) {
this.validationCache.set(cacheKey, {
result,
timestamp: Date.now()
});
// Nettoyage périodique du cache
if (this.validationCache.size > 1000) {
this.cleanupCache();
}
}
cleanupCache() {
const now = Date.now();
for (const [key, cached] of this.validationCache.entries()) {
if (now - cached.timestamp > this.cacheTimeout) {
this.validationCache.delete(key);
}
}
}
// === Statistiques et monitoring ===
updateValidationStats(result) {
this.stats.averageProcessingTime = this.updateRunningAverage(
this.stats.averageProcessingTime,
result.processingTime,
this.stats.totalValidated
);
this.stats.riskLevelDistribution[result.riskLevel]++;
if (result.layers.patternDetection.hasHighRiskPatterns) {
this.stats.injectionAttempts++;
}
if (!result.layers.semanticValidation.passed) {
this.stats.semanticFailures++;
}
}
updateRunningAverage(currentAvg, newValue, totalCount) {
if (totalCount === 1) return newValue;
const alpha = 1 / totalCount;
return alpha * newValue + (1 - alpha) * currentAvg;
}
logValidationResult(result, content, context) {
const logData = {
contentId: content.id,
riskLevel: result.riskLevel,
isValid: result.isValid,
processingTime: result.processingTime,
patternsDetected: result.layers.patternDetection.totalPatterns,
semanticScore: Math.round(result.layers.semanticValidation.semanticScore * 100),
totalPenalty: result.layers.penalties.totalPenalty,
raceCode: context.raceCode
};
switch (result.riskLevel) {
case 'critical':
logger.securityEvent('CRITICAL security threat detected', 'PROMPT_INJECTION', logData);
break;
case 'high':
logger.securityEvent('HIGH security risk detected', 'SUSPICIOUS_CONTENT', logData);
break;
case 'medium':
logger.warn('Medium security risk in content', logData);
break;
default:
logger.debug('Content validation completed', logData);
}
}
/**
* Obtenir statistiques de sécurité
*/
getSecurityStats() {
const cacheStats = {
size: this.validationCache.size,
hitRate: this.stats.totalValidated > 0 ?
(this.stats.totalValidated - this.stats.injectionAttempts - this.stats.semanticFailures) / this.stats.totalValidated : 0
};
return {
...this.stats,
cache: cacheStats,
engine: 'AntiInjectionEngine',
version: '1.0',
lastUpdate: new Date().toISOString()
};
}
/**
* Réinitialiser statistiques
*/
resetStats() {
this.stats = {
totalValidated: 0,
injectionAttempts: 0,
semanticFailures: 0,
falsePositives: 0,
averageProcessingTime: 0,
riskLevelDistribution: {
low: 0,
medium: 0,
high: 0,
critical: 0
}
};
}
/**
* Health check du moteur de sécurité
*/
async healthCheck() {
try {
const testContent = {
id: 'health-check',
title: 'Test de santé du système',
content: 'Contenu de test pour validation du moteur de sécurité',
sourceType: 'system'
};
const testContext = {
raceCode: '352-1',
clientId: 'health-check'
};
const result = await this.validateContent(testContent, testContext);
return {
status: 'healthy',
engine: 'AntiInjectionEngine',
testResult: {
processed: true,
processingTime: result.processingTime,
riskLevel: result.riskLevel
},
stats: this.getSecurityStats(),
cache: {
size: this.validationCache.size,
enabled: true
}
};
} catch (error) {
return {
status: 'error',
engine: 'AntiInjectionEngine',
error: error.message
};
}
}
}
module.exports = AntiInjectionEngine;