overcuriousity 72d5f267f0 phase 1
2025-07-26 21:02:10 +02:00

431 lines
16 KiB
TypeScript

// src/pages/api/ai/query.ts
import type { APIRoute } from 'astro';
import { withAPIAuth } from '../../../utils/auth.js';
import { getCompressedToolsDataForAI } from '../../../utils/dataService.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_MODEL = getEnv('AI_MODEL');
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
const RATE_LIMIT_WINDOW = 60 * 1000;
const RATE_LIMIT_MAX = 10;
function sanitizeInput(input: string): string {
let sanitized = input
.replace(/```[\s\S]*?```/g, '[CODE_BLOCK_REMOVED]') // Remove code blocks
.replace(/\<\/?[^>]+(>|$)/g, '') // Remove HTML tags
.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();
sanitized = sanitized.slice(0, 2000).replace(/\s+/g, ' ');
return sanitized;
}
function stripMarkdownJson(content: string): string {
return content
.replace(/^```json\s*/i, '')
.replace(/\s*```\s*$/, '')
.trim();
}
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);
async function loadToolsDatabase() {
try {
return await getCompressedToolsDataForAI();
} catch (error) {
console.error('Failed to load tools database:', error);
throw new Error('Database unavailable');
}
}
function createWorkflowSystemPrompt(toolsData: any): string {
const toolsList = toolsData.tools.map((tool: any) => ({
name: tool.name,
description: tool.description,
domains: tool.domains,
phases: tool.phases,
domainAgnostic: tool['domain-agnostic-software'],
platforms: tool.platforms,
skillLevel: tool.skillLevel,
license: tool.license,
tags: tool.tags,
related_concepts: tool.related_concepts || []
}));
const conceptsList = toolsData.concepts.map((concept: any) => ({
name: concept.name,
description: concept.description,
domains: concept.domains,
phases: concept.phases,
skillLevel: concept.skillLevel,
tags: concept.tags
}));
const regularPhases = toolsData.phases || [];
const domainAgnosticSoftware = toolsData['domain-agnostic-software'] || [];
const allPhaseItems = [
...regularPhases,
...domainAgnosticSoftware
];
const phasesDescription = allPhaseItems.map((phase: any) =>
`- ${phase.id}: ${phase.name}`
).join('\n');
const domainsDescription = toolsData.domains.map((domain: any) =>
`- ${domain.id}: ${domain.name}`
).join('\n');
const phaseDescriptions = regularPhases.map((phase: any) =>
`- ${phase.name}: ${phase.description || 'Tools/Methods for this phase'}`
).join('\n');
const domainAgnosticDescriptions = domainAgnosticSoftware.map((section: any) =>
`- ${section.name}: ${section.description || 'Cross-cutting software and platforms'}`
).join('\n');
const validPhases = [
...regularPhases.map((p: any) => p.id),
...domainAgnosticSoftware.map((s: any) => s.id)
].join('|');
return `Du bist ein DFIR (Digital Forensics and Incident Response) Experte, der Ermittlern bei der Auswahl von Software und Methoden hilft.
VERFÜGBARE TOOLS/METHODEN:
${JSON.stringify(toolsList, null, 2)}
VERFÜGBARE HINTERGRUNDWISSEN-KONZEPTE:
${JSON.stringify(conceptsList, null, 2)}
UNTERSUCHUNGSPHASEN (NIST Framework):
${phasesDescription}
FORENSISCHE DOMÄNEN:
${domainsDescription}
WICHTIGE REGELN:
1. Pro Phase 2-3 Tools/Methoden empfehlen (immer mindestens 2 wenn verfügbar)
2. Tools/Methoden können in MEHREREN Phasen empfohlen werden wenn sinnvoll - versuche ein Tool/Methode für jede Phase zu empfehlen, selbst wenn die Priorität "low" ist.
3. Für Reporting-Phase: Visualisierungs- und Dokumentationssoftware einschließen
4. Gib stets dem spezieller für den Fall geeigneten Werkzeug den Vorzug.
5. Deutsche Antworten für deutsche Anfragen, English for English queries
6. Methoden haben, sofern für das SZENARIO passend, IMMER Vorrang vor Software.
7. Bevorzuge alles, was nicht proprietär ist (license != "Proprietary"), aber erkenne an, wenn proprietäre Software besser geeignet ist.
8. WICHTIG: Erwähne relevante Hintergrundwissen-Konzepte wenn Tools verwendet werden, die related_concepts haben
9. Konzepte sind NICHT Tools - empfehle sie nicht als actionable Schritte, sondern als Wissensbasis
ENHANCED CONTEXTUAL ANALYSIS:
10. Analysiere das Szenario detailliert und identifiziere Schlüsselelemente, Bedrohungen und forensische Herausforderungen
11. Entwickle einen strategischen Untersuchungsansatz basierend auf dem spezifischen Szenario
12. Identifiziere zeitkritische oder besonders wichtige Faktoren für diesen Fall
SOFTWARE/METHODEN-AUSWAHL NACH PHASE:
${phaseDescriptions}
DOMÄNENAGNOSTISCHE SOFTWARE/METHODEN:
${domainAgnosticDescriptions}
ANTWORT-FORMAT (strict JSON):
{
"scenario_analysis": "Detaillierte Analyse des Szenarios: Erkannte Schlüsselelemente, Art des Vorfalls, betroffene Systeme, potentielle Bedrohungen und forensische Herausforderungen",
"investigation_approach": "Strategischer Untersuchungsansatz für dieses spezifische Szenario: Prioritäten, Reihenfolge der Phasen, besondere Überlegungen",
"critical_considerations": "Zeitkritische Faktoren, wichtige Sicherheitsaspekte oder besondere Vorsichtsmaßnahmen für diesen Fall",
"recommended_tools": [
{
"name": "EXAKTER Name aus der Tools-Database",
"priority": "high|medium|low",
"phase": "${validPhases}",
"justification": "Warum diese Methode für diese Phase und dieses spezifische Szenario geeignet ist - mit Bezug zu den erkannten Schlüsselelementen"
}
],
"workflow_suggestion": "Vorgeschlagener Untersuchungsablauf mit konkreten Schritten für dieses Szenario",
"background_knowledge": [
{
"concept_name": "EXAKTER Name aus der Konzepte-Database",
"relevance": "Warum dieses Konzept für das Szenario relevant ist, und bei welchen der empfohlenen Methoden/Tools."
}
],
"additional_notes": "Wichtige Überlegungen und Hinweise"
}
Antworte NUR mit validen JSON. Keine zusätzlichen Erklärungen außerhalb des JSON.`;
}
function createToolSystemPrompt(toolsData: any): string {
const toolsList = toolsData.tools.map((tool: any) => ({
name: tool.name,
description: tool.description,
domains: tool.domains,
phases: tool.phases,
platforms: tool.platforms,
skillLevel: tool.skillLevel,
license: tool.license,
tags: tool.tags,
url: tool.url,
projectUrl: tool.projectUrl,
related_concepts: tool.related_concepts || []
}));
const conceptsList = toolsData.concepts.map((concept: any) => ({
name: concept.name,
description: concept.description,
domains: concept.domains,
phases: concept.phases,
skillLevel: concept.skillLevel,
tags: concept.tags
}));
return `Du bist ein DFIR (Digital Forensics and Incident Response) Experte, der bei der Auswahl spezifischer Software/Methoden für konkrete Probleme hilft.
VERFÜGBARE TOOLS/METHODEN:
${JSON.stringify(toolsList, null, 2)}
VERFÜGBARE HINTERGRUNDWISSEN-KONZEPTE:
${JSON.stringify(conceptsList, null, 2)}
WICHTIGE REGELN:
1. Analysiere das spezifische Problem/die Anforderung sorgfältig
2. Empfehle 1-3 Methoden/Tools, sortiert nach Eignung (beste Empfehlung zuerst)
3. Gib detaillierte Erklärungen, WARUM und WIE jede Methode/Tool das Problem löst
4. Berücksichtige praktische Aspekte: Skill Level, Plattformen, Verfügbarkeit
5. Deutsche Antworten für deutsche Anfragen, English for English queries
6. Gib konkrete Anwendungshinweise, nicht nur allgemeine Beschreibungen - Methoden haben, sofern für das SZENARIO passend, IMMER Vorrang vor Software.
7. Erwähne sowohl Stärken als auch Schwächen/Limitationen
8. Schlage alternative Ansätze vor, wenn sinnvoll
9. Gib grundsätzliche Hinweise, WIE die Methode/Tool konkret eingesetzt wird
10. WICHTIG: Erwähne relevante Hintergrundwissen-Konzepte wenn Tools verwendet werden, die related_concepts haben
11. Konzepte sind NICHT Tools - empfehle sie nicht als actionable Schritte, sondern als Wissensbasis
ENHANCED CONTEXTUAL ANALYSIS:
12. Analysiere das Problem detailliert und identifiziere technische Anforderungen, Herausforderungen und Erfolgsfaktoren
13. Entwickle einen strategischen Lösungsansatz basierend auf dem spezifischen Problem
14. Identifiziere wichtige Voraussetzungen oder Warnungen für die Anwendung
ANTWORT-FORMAT (strict JSON):
{
"problem_analysis": "Detaillierte Analyse des Problems: Erkannte technische Anforderungen, Herausforderungen, benötigte Fähigkeiten und Erfolgsfaktoren",
"investigation_approach": "Strategischer Lösungsansatz für dieses spezifische Problem: Herangehensweise, Prioritäten, optimale Anwendungsreihenfolge",
"critical_considerations": "Wichtige Voraussetzungen, potentielle Fallstricke oder Warnungen für die Anwendung der empfohlenen Lösungen",
"recommended_tools": [
{
"name": "EXAKTER Name aus der Tools-Database",
"rank": 1,
"suitability_score": "high|medium|low",
"detailed_explanation": "Detaillierte Erklärung, warum dieses Tool/diese Methode das spezifische Problem löst - mit Bezug zu den erkannten Anforderungen",
"implementation_approach": "Konkrete Schritte/Ansatz zur Anwendung für dieses spezifische Problem",
"pros": ["Spezifische Vorteile für diesen Anwendungsfall", "Weitere Vorteile"],
"cons": ["Potentielle Nachteile oder Limitationen", "Weitere Einschränkungen"],
"alternatives": "Alternative Ansätze oder ergänzende Tools/Methoden, falls relevant"
}
],
"background_knowledge": [
{
"concept_name": "EXAKTER Name aus der Konzepte-Database",
"relevance": "Warum dieses Konzept für die empfohlenen Tools/das Problem relevant ist, und für welche der empfohlenen Methoden/Tools."
}
],
"additional_considerations": "Wichtige Überlegungen, Voraussetzungen oder Warnungen"
}
Antworte NUR mit validen JSON. Keine zusätzlichen Erklärungen außerhalb des JSON.`;
}
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('Rate limit exceeded');
}
const body = await request.json();
const { query, mode = 'workflow', taskId: clientTaskId } = body;
if (!query || typeof query !== 'string') {
return apiError.badRequest('Query required');
}
if (!['workflow', 'tool'].includes(mode)) {
return apiError.badRequest('Invalid mode. Must be "workflow" or "tool"');
}
const sanitizedQuery = sanitizeInput(query);
if (sanitizedQuery.includes('[FILTERED]')) {
return apiError.badRequest('Invalid input detected');
}
const toolsData = await loadToolsDatabase();
const systemPrompt = mode === 'workflow'
? createWorkflowSystemPrompt(toolsData)
: createToolSystemPrompt(toolsData);
const taskId = clientTaskId || `ai_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
const aiResponse = await enqueueApiCall(() =>
fetch(process.env.AI_API_ENDPOINT + '/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.AI_API_KEY}`
},
body: JSON.stringify({
model: AI_MODEL,
messages: [
{
role: 'system',
content: systemPrompt
},
{
role: 'user',
content: sanitizedQuery
}
],
max_tokens: 2000,
temperature: 0.3
})
})
, taskId);
if (!aiResponse.ok) {
console.error('AI API error:', await aiResponse.text());
return apiServerError.unavailable('AI service unavailable');
}
const aiData = await aiResponse.json();
const aiContent = aiData.choices?.[0]?.message?.content;
if (!aiContent) {
return apiServerError.unavailable('No response from AI');
}
let recommendation;
try {
const cleanedContent = stripMarkdownJson(aiContent);
recommendation = JSON.parse(cleanedContent);
} catch (error) {
console.error('Failed to parse AI response:', aiContent);
return apiServerError.unavailable('Invalid AI response format');
}
const validToolNames = new Set(toolsData.tools.map((t: any) => t.name));
const validConceptNames = new Set(toolsData.concepts.map((c: any) => c.name));
let validatedRecommendation;
if (mode === 'workflow') {
validatedRecommendation = {
...recommendation,
// Ensure all new fields are included with fallbacks
scenario_analysis: recommendation.scenario_analysis || recommendation.problem_analysis || '',
investigation_approach: recommendation.investigation_approach || '',
critical_considerations: recommendation.critical_considerations || '',
recommended_tools: recommendation.recommended_tools?.filter((tool: any) => {
if (!validToolNames.has(tool.name)) {
console.warn(`AI recommended unknown tool: ${tool.name}`);
return false;
}
return true;
}) || [],
background_knowledge: recommendation.background_knowledge?.filter((concept: any) => {
if (!validConceptNames.has(concept.concept_name)) {
console.warn(`AI referenced unknown concept: ${concept.concept_name}`);
return false;
}
return true;
}) || []
};
} else {
validatedRecommendation = {
...recommendation,
// Ensure all new fields are included with fallbacks
problem_analysis: recommendation.problem_analysis || recommendation.scenario_analysis || '',
investigation_approach: recommendation.investigation_approach || '',
critical_considerations: recommendation.critical_considerations || '',
recommended_tools: recommendation.recommended_tools?.filter((tool: any) => {
if (!validToolNames.has(tool.name)) {
console.warn(`AI recommended unknown tool: ${tool.name}`);
return false;
}
return true;
}).map((tool: any, index: number) => ({
...tool,
rank: tool.rank || (index + 1),
suitability_score: tool.suitability_score || 'medium',
pros: Array.isArray(tool.pros) ? tool.pros : [],
cons: Array.isArray(tool.cons) ? tool.cons : []
})) || [],
background_knowledge: recommendation.background_knowledge?.filter((concept: any) => {
if (!validConceptNames.has(concept.concept_name)) {
console.warn(`AI referenced unknown concept: ${concept.concept_name}`);
return false;
}
return true;
}) || []
};
}
console.log(`[AI Query] Mode: ${mode}, User: ${userId}, Query length: ${sanitizedQuery.length}, Tools: ${validatedRecommendation.recommended_tools.length}, Concepts: ${validatedRecommendation.background_knowledge?.length || 0}`);
return new Response(JSON.stringify({
success: true,
mode,
taskId,
recommendation: validatedRecommendation,
query: sanitizedQuery
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('AI query error:', error);
return apiServerError.internal('Internal server error');
}
};