diff --git a/src/modules/ai/README.md b/src/modules/ai/README.md new file mode 100644 index 0000000..c79f3a4 --- /dev/null +++ b/src/modules/ai/README.md @@ -0,0 +1,28 @@ +# AI Module + +This minimal AI framework provides: + +- A provider abstraction (`providers/ai-provider.interface.ts`) for plugging in different AI/threat-analysis backends. +- A lightweight example provider (`providers/openai.provider.ts`) that implements deterministic heuristics so teams can use the framework without network access. +- A simple `AIService` that aggregates provider results and selects a best summary. +- Config helper at `config/ai.config.ts` to choose providers at runtime. + +## Usage + +Create a service instance via `createAIService()` in `ai.module.ts` or construct `new AIService([new OpenAIProvider(apiKey)])`. + +## Examples + +- See `providers/openai.provider.ts` for required provider API (`analyzeThreat(event)`). +- Summaries implement the `ThreatSummary` interface in `interfaces/threat-summary.interface.ts`. + +## Design notes + +- Keep provider interface small and explicit so new providers (Azure, local models, LLMs) can be added. +- The `OpenAIProvider` currently uses local heuristics. Implementers can extend it to call external LLM APIs and enrich `confidence` and `description` fields. + +## Acceptance + +- Provider abstraction implemented. +- `ThreatSummary` interface documented. +- Configurable provider selection implemented via `AIConfig`. diff --git a/src/modules/ai/ai.module.ts b/src/modules/ai/ai.module.ts new file mode 100644 index 0000000..3883264 --- /dev/null +++ b/src/modules/ai/ai.module.ts @@ -0,0 +1,15 @@ +import { AIService } from './ai.service'; +import { AIProvider } from './providers/ai-provider.interface'; +import { OpenAIProvider } from './providers/openai.provider'; +import { AIConfig } from './config/ai.config'; + +export function createAIService(config: AIConfig = AIConfig.default()): AIService { + const providers: AIProvider[] = []; + + if (config.provider === 'openai') { + const c = config.providers.openai; + providers.push(new OpenAIProvider(c.apiKey)); + } + + return new AIService(providers); +} diff --git a/src/modules/ai/ai.service.ts b/src/modules/ai/ai.service.ts new file mode 100644 index 0000000..b6c8334 --- /dev/null +++ b/src/modules/ai/ai.service.ts @@ -0,0 +1,22 @@ +import { AIProvider } from './providers/ai-provider.interface'; +import { ThreatSummary, Severity } from './interfaces/threat-summary.interface'; + +export class AIService { + constructor(private providers: AIProvider[] = []) {} + + async summarize(event: any): Promise { + const results = await Promise.all(this.providers.map(p => p.analyzeThreat(event))); + return results; + } + + async bestSummary(event: any): Promise { + const summaries = await this.summarize(event); + if (summaries.length === 0) return null; + + const order: Record = { low: 1, medium: 2, high: 3, critical: 4 }; + summaries.sort( + (a, b) => order[b.severity] - order[a.severity] || (b.score || 0) - (a.score || 0), + ); + return summaries[0]; + } +} diff --git a/src/modules/ai/config/ai.config.ts b/src/modules/ai/config/ai.config.ts new file mode 100644 index 0000000..b2c59c7 --- /dev/null +++ b/src/modules/ai/config/ai.config.ts @@ -0,0 +1,24 @@ +export type AIProviderName = 'openai' | 'mock'; + +export interface OpenAIConfig { + apiKey: string; +} + +export interface AIConfigShape { + provider: AIProviderName; + providers: { openai: OpenAIConfig }; +} + +export class AIConfig implements AIConfigShape { + provider: AIProviderName; + providers: { openai: OpenAIConfig }; + + constructor(provider: AIProviderName, providers: { openai: OpenAIConfig }) { + this.provider = provider; + this.providers = providers; + } + + static default(): AIConfig { + return new AIConfig('openai', { openai: { apiKey: process.env.OPENAI_API_KEY || '' } }); + } +} diff --git a/src/modules/ai/interfaces/threat-summary.interface.ts b/src/modules/ai/interfaces/threat-summary.interface.ts new file mode 100644 index 0000000..a9a7500 --- /dev/null +++ b/src/modules/ai/interfaces/threat-summary.interface.ts @@ -0,0 +1,12 @@ +export type Severity = 'low' | 'medium' | 'high' | 'critical'; + +export interface ThreatSummary { + title: string; + severity: Severity; + score?: number; // 0..1 + description: string; + indicators?: string[]; + recommendedActions?: string[]; + confidence?: number; // 0..1 + raw?: any; +} diff --git a/src/modules/ai/providers/ai-provider.interface.ts b/src/modules/ai/providers/ai-provider.interface.ts new file mode 100644 index 0000000..fe06e4c --- /dev/null +++ b/src/modules/ai/providers/ai-provider.interface.ts @@ -0,0 +1,7 @@ +import { ThreatSummary } from '../interfaces/threat-summary.interface'; + +export interface AIProvider { + name: string; + analyzeThreat(event: any): Promise; + healthCheck?(): Promise; +} diff --git a/src/modules/ai/providers/openai.provider.ts b/src/modules/ai/providers/openai.provider.ts new file mode 100644 index 0000000..d5c6234 --- /dev/null +++ b/src/modules/ai/providers/openai.provider.ts @@ -0,0 +1,76 @@ +import { AIProvider } from './ai-provider.interface'; +import { ThreatSummary } from '../interfaces/threat-summary.interface'; + +export class OpenAIProvider implements AIProvider { + name = 'openai'; + constructor(private apiKey: string) {} + + async analyzeThreat(event: any): Promise { + // Minimal, efficient implementation: lightweight local heuristic + optional remote call. + // For now provide a deterministic lightweight summary so the framework is usable without network. + + const title = event.title || event.alert || 'Security event'; + const description = event.description || JSON.stringify(event).slice(0, 200); + + // Simple heuristic severity mapping + const severity = this.heuristicSeverity(event); + const score = this.heuristicScore(severity); + + return { + title, + description, + severity, + score, + indicators: this.extractIndicators(event), + recommendedActions: this.suggestRemediations(severity), + confidence: 0.5, + raw: event, + }; + } + + async healthCheck(): Promise { + // If API key configured, assume provider can be used; otherwise still usable in local-only mode. + return typeof this.apiKey === 'string' && this.apiKey.length > 0; + } + + private heuristicSeverity(event: any): 'low' | 'medium' | 'high' | 'critical' { + const s = (event.severity || '').toString().toLowerCase(); + if (s.includes('crit') || s === '4') return 'critical'; + if (s.includes('high') || s === '3') return 'high'; + if (s.includes('medium') || s === '2') return 'medium'; + return 'low'; + } + + private heuristicScore(sev: string): number { + switch (sev) { + case 'critical': + return 0.95; + case 'high': + return 0.8; + case 'medium': + return 0.5; + default: + return 0.2; + } + } + + private extractIndicators(event: any): string[] { + const indicators: string[] = []; + if (event.ip) indicators.push(`ip:${event.ip}`); + if (event.user) indicators.push(`user:${event.user}`); + if (event.filename) indicators.push(`file:${event.filename}`); + return indicators; + } + + private suggestRemediations(sev: string): string[] { + if (sev === 'critical') + return [ + 'Isolate affected hosts', + 'Rotate credentials', + 'Initiate incident response playbook', + ]; + if (sev === 'high') return ['Block indicators', 'Notify on-call', 'Collect forensic artifacts']; + if (sev === 'medium') return ['Investigate logs', 'Raise ticket for review']; + return ['Monitor and gather additional context']; + } +}