221 lines
7.5 KiB
TypeScript
221 lines
7.5 KiB
TypeScript
// src/pages/api/ai/enhance-input.ts - Enhanced AI service compatibility
|
|
import type { APIRoute } from 'astro';
|
|
import { withAPIAuth } from '../../../utils/auth.js';
|
|
import { apiError, apiServerError, createAuthErrorResponse } from '../../../utils/api.js';
|
|
import { enqueueApiCall } from '../../../utils/rateLimitedQueue.js';
|
|
|
|
export const prerender = false;
|
|
|
|
function getEnv(key: string): string {
|
|
const value = process.env[key];
|
|
if (!value) {
|
|
throw new Error(`Missing environment variable: ${key}`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
const AI_ENDPOINT = getEnv('AI_ANALYZER_ENDPOINT');
|
|
const AI_ANALYZER_API_KEY = getEnv('AI_ANALYZER_API_KEY');
|
|
const AI_ANALYZER_MODEL = getEnv('AI_ANALYZER_MODEL');
|
|
|
|
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
|
const RATE_LIMIT_WINDOW = 60 * 1000;
|
|
const RATE_LIMIT_MAX = 5;
|
|
|
|
function sanitizeInput(input: string): string {
|
|
return input
|
|
.replace(/```[\s\S]*?```/g, '[CODE_BLOCK_REMOVED]')
|
|
.replace(/\<\/?[^>]+(>|$)/g, '')
|
|
.replace(/\b(system|assistant|user)\s*[:]/gi, '[ROLE_REMOVED]')
|
|
.replace(/\b(ignore|forget|disregard)\s+(previous|all|your)\s+(instructions?|context|rules?)/gi, '[INSTRUCTION_REMOVED]')
|
|
.trim()
|
|
.slice(0, 1000);
|
|
}
|
|
|
|
function checkRateLimit(userId: string): boolean {
|
|
const now = Date.now();
|
|
const userLimit = rateLimitStore.get(userId);
|
|
|
|
if (!userLimit || now > userLimit.resetTime) {
|
|
rateLimitStore.set(userId, { count: 1, resetTime: now + RATE_LIMIT_WINDOW });
|
|
return true;
|
|
}
|
|
|
|
if (userLimit.count >= RATE_LIMIT_MAX) {
|
|
return false;
|
|
}
|
|
|
|
userLimit.count++;
|
|
return true;
|
|
}
|
|
|
|
function cleanupExpiredRateLimits() {
|
|
const now = Date.now();
|
|
for (const [userId, limit] of rateLimitStore.entries()) {
|
|
if (now > limit.resetTime) {
|
|
rateLimitStore.delete(userId);
|
|
}
|
|
}
|
|
}
|
|
|
|
setInterval(cleanupExpiredRateLimits, 5 * 60 * 1000);
|
|
|
|
function createEnhancementPrompt(input: string): string {
|
|
return `Sie sind ein DFIR-Experte mit Spezialisierung auf forensische Methodik. Ein Nutzer beschreibt ein forensisches Szenario oder Problem. Analysieren Sie die Eingabe auf Vollständigkeit für eine wissenschaftlich fundierte forensische Untersuchung.
|
|
|
|
ANALYSIEREN SIE DIESE FORENSISCHEN KATEGORIEN:
|
|
1. **Incident Context**: Was ist passiert? Welche Angriffsvektoren oder technischen Probleme liegen vor?
|
|
2. **Affected Systems**: Welche spezifischen Technologien/Plattformen sind betroffen? (Windows/Linux/ICS/SCADA/Mobile/Cloud/Network Infrastructure)
|
|
3. **Available Evidence**: Welche forensischen Datenquellen stehen zur Verfügung? (RAM-Dumps, Disk-Images, Log-Files, Network-Captures, Registry-Hives)
|
|
4. **Investigation Objectives**: Was soll erreicht werden? (IOC-Extraktion, Timeline-Rekonstruktion, Attribution, Impact-Assessment)
|
|
5. **Timeline Constraints**: Wie zeitkritisch ist die Untersuchung?
|
|
6. **Legal & Compliance**: Rechtliche Anforderungen, Chain of Custody, Compliance-Rahmen (DSGVO, sector-specific regulations)
|
|
7. **Technical Constraints**: Verfügbare Ressourcen, Skills, Infrastrukturbeschränkungen
|
|
|
|
WENN die Beschreibung alle kritischen forensischen Aspekte abdeckt: Geben Sie eine leere Liste [] zurück.
|
|
|
|
WENN wichtige forensische Details fehlen: Formulieren Sie 2-3 präzise Fragen, die die kritischsten Lücken für eine wissenschaftlich fundierte forensische Analyse schließen.
|
|
|
|
QUALITÄTSKRITERIEN FÜR FRAGEN:
|
|
- Forensisch spezifisch, nicht allgemein (NICHT: "Mehr Details?")
|
|
- Methodisch relevant (NICHT: "Wann passierte das?")
|
|
- Priorisiert nach Auswirkung auf die forensische Untersuchungsqualität
|
|
- Die Frage soll maximal 20 Wörter umfassen
|
|
|
|
ANTWORTFORMAT (NUR JSON, KEIN ZUSÄTZLICHER TEXT):
|
|
[
|
|
"spezifische Frage 1?",
|
|
"spezifische Frage 2?",
|
|
"spezifische Frage 3?"
|
|
]
|
|
|
|
NUTZER-EINGABE:
|
|
${input}
|
|
`.trim();
|
|
}
|
|
|
|
async function callAIService(prompt: string): Promise<Response> {
|
|
const endpoint = AI_ENDPOINT;
|
|
const apiKey = AI_ANALYZER_API_KEY;
|
|
const model = AI_ANALYZER_MODEL;
|
|
|
|
let headers: Record<string, string> = {
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
if (apiKey) {
|
|
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
console.log('[ENHANCE API] Using API key authentication');
|
|
} else {
|
|
console.log('[ENHANCE API] No API key - making request without authentication');
|
|
}
|
|
|
|
const requestBody = {
|
|
model,
|
|
messages: [{ role: 'user', content: prompt }],
|
|
max_tokens: 300,
|
|
temperature: 0.7,
|
|
top_p: 0.9,
|
|
frequency_penalty: 0.2,
|
|
presence_penalty: 0.1
|
|
};
|
|
|
|
return fetch(`${endpoint}/v1/chat/completions`, {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify(requestBody)
|
|
});
|
|
}
|
|
|
|
export const POST: APIRoute = async ({ request }) => {
|
|
try {
|
|
const authResult = await withAPIAuth(request, 'ai');
|
|
if (!authResult.authenticated) {
|
|
return createAuthErrorResponse();
|
|
}
|
|
|
|
const userId = authResult.userId;
|
|
|
|
if (!checkRateLimit(userId)) {
|
|
return apiError.rateLimit('Enhancement rate limit exceeded');
|
|
}
|
|
|
|
const body = await request.json();
|
|
const { input } = body;
|
|
|
|
if (!input || typeof input !== 'string' || input.length < 40) {
|
|
return apiError.badRequest('Input too short for enhancement (minimum 40 characters)');
|
|
}
|
|
|
|
const sanitizedInput = sanitizeInput(input);
|
|
if (sanitizedInput.length < 40) {
|
|
return apiError.badRequest('Input too short after sanitization');
|
|
}
|
|
|
|
const systemPrompt = createEnhancementPrompt(sanitizedInput);
|
|
const taskId = `enhance_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`;
|
|
|
|
const aiResponse = await enqueueApiCall(() => callAIService(systemPrompt), taskId);
|
|
|
|
if (!aiResponse.ok) {
|
|
const errorText = await aiResponse.text();
|
|
console.error('[ENHANCE API] AI enhancement error:', errorText, 'Status:', aiResponse.status);
|
|
return apiServerError.unavailable('Enhancement service unavailable');
|
|
}
|
|
|
|
const aiData = await aiResponse.json();
|
|
const aiContent = aiData.choices?.[0]?.message?.content;
|
|
|
|
if (!aiContent) {
|
|
return apiServerError.unavailable('No enhancement response');
|
|
}
|
|
|
|
let questions;
|
|
try {
|
|
const cleanedContent = aiContent
|
|
.replace(/^```json\s*/i, '')
|
|
.replace(/\s*```\s*$/, '')
|
|
.trim();
|
|
questions = JSON.parse(cleanedContent);
|
|
|
|
if (!Array.isArray(questions)) {
|
|
throw new Error('Response is not an array');
|
|
}
|
|
|
|
questions = questions
|
|
.filter(q => typeof q === 'string' && q.length > 20 && q.length < 200)
|
|
.filter(q => q.includes('?'))
|
|
.filter(q => {
|
|
const forensicsTerms = ['forensisch', 'log', 'dump', 'image', 'artefakt', 'evidence', 'incident', 'system', 'netzwerk', 'zeitraum', 'verfügbar'];
|
|
const lowerQ = q.toLowerCase();
|
|
return forensicsTerms.some(term => lowerQ.includes(term));
|
|
})
|
|
.map(q => q.trim())
|
|
.slice(0, 3);
|
|
|
|
if (questions.length === 0) {
|
|
questions = [];
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Failed to parse enhancement response:', aiContent);
|
|
questions = [];
|
|
}
|
|
|
|
console.log(`[ENHANCE API] User: ${userId}, Forensics Questions: ${questions.length}, Input length: ${sanitizedInput.length}`);
|
|
|
|
return new Response(JSON.stringify({
|
|
success: true,
|
|
questions,
|
|
taskId,
|
|
inputComplete: questions.length === 0
|
|
}), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Enhancement error:', error);
|
|
return apiServerError.internal('Enhancement processing failed');
|
|
}
|
|
}; |