Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
// Include root and application-level tsconfigs so ESLint can run type-aware rules
project: [
'tsconfig.json',
'tsconfig.src.json',
'apps/dashboard/tsconfig.json',
'apps/web/tsconfig.json',
],
project: ['tsconfig.json', 'apps/dashboard/tsconfig.json', 'apps/web/tsconfig.json'],
project: ['tsconfig.json'],
tsconfigRootDir: __dirname,
Expand All @@ -25,6 +31,8 @@ module.exports = {
overrides: [
{
// Files that are intentionally outside any tsconfig — lint without type-aware rules

files: ['observability/**/*.ts', 'prisma/**/*.ts', 'env.d.ts', 'src/**/*.ts', 'src/**/*.tsx'],
files: ['observability/**/*.ts', 'prisma/**/*.ts', 'env.d.ts'],
// Files outside all tsconfigs — lint without type-aware rules
files: [
Expand Down
14 changes: 10 additions & 4 deletions apps/backend/src/modules/governance/governance.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
// Use runtime require for PrismaClient to avoid TypeScript type export issues in the
// environment where the generated client types may not be present during linting.
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { PrismaClient } = require('@prisma/client');
import { ProposalDto, VoteDto, GovernanceEventDto } from './interfaces/governance.interface';

@Injectable()
export class GovernanceService {
private prisma: PrismaClient;
// Prisma client instance - using `any` for the private field to avoid tight coupling
// with generated client types during linting/build in different environments.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private prisma: any;

constructor() {
this.prisma = new PrismaClient();
Expand Down Expand Up @@ -62,8 +68,8 @@ export class GovernanceService {
proposalId: dto.proposalId,
voter: dto.voter,
transactionHash: dto.transactionHash,
// Prisma expects JSON; cast unknown to any here after validation upstream
metadata: (dto.metadata as any) || {},
// Prisma expects JSON; use the provided metadata or fall back to empty object
metadata: dto.metadata ?? {},
},
});
return event;
Expand Down
7 changes: 5 additions & 2 deletions src/modules/ai/ai.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import { ThreatSummary, Severity } from './interfaces/threat-summary.interface';
export class AIService {
constructor(private providers: AIProvider[] = []) {}

async summarize(event: any): Promise<ThreatSummary[]> {
// The service accepts unknown external events; providers are responsible for
// narrowing/validating the shape before use. This keeps the service flexible
// while avoiding unsafe `any` usage.
async summarize(event: unknown): Promise<ThreatSummary[]> {
const results = await Promise.all(this.providers.map(p => p.analyzeThreat(event)));
return results;
}

async bestSummary(event: any): Promise<ThreatSummary | null> {
async bestSummary(event: unknown): Promise<ThreatSummary | null> {
const summaries = await this.summarize(event);
if (summaries.length === 0) return null;

Expand Down
2 changes: 1 addition & 1 deletion src/modules/ai/interfaces/threat-summary.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ export interface ThreatSummary {
indicators?: string[];
recommendedActions?: string[];
confidence?: number; // 0..1
raw?: any;
raw?: unknown;
}
2 changes: 1 addition & 1 deletion src/modules/ai/providers/ai-provider.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { ThreatSummary } from '../interfaces/threat-summary.interface';

export interface AIProvider {
name: string;
analyzeThreat(event: any): Promise<ThreatSummary>;
analyzeThreat(event: unknown): Promise<ThreatSummary>;
healthCheck?(): Promise<boolean>;
}
53 changes: 38 additions & 15 deletions src/modules/ai/providers/openai.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,44 @@ export class OpenAIProvider implements AIProvider {
name = 'openai';
constructor(private apiKey: string) {}

async analyzeThreat(event: any): Promise<ThreatSummary> {
// Minimal, efficient implementation: lightweight local heuristic + optional remote call.
// For now provide a deterministic lightweight summary so the framework is usable without network.
async analyzeThreat(event: unknown): Promise<ThreatSummary> {
// Narrow the external event safely using lightweight guards below.
const e = this.normalizeEvent(event);

const title = event.title || event.alert || 'Security event';
const description = event.description || JSON.stringify(event).slice(0, 200);
const title = e.title ?? e.alert ?? 'Security event';
const description = e.description ?? safeStringify(e).slice(0, 200);

// Simple heuristic severity mapping
const severity = this.heuristicSeverity(event);
const severity = this.heuristicSeverity(e);
const score = this.heuristicScore(severity);

return {
title,
description,
severity,
score,
indicators: this.extractIndicators(event),
indicators: this.extractIndicators(e),
recommendedActions: this.suggestRemediations(severity),
confidence: 0.5,
raw: event,
};
}

async healthCheck(): Promise<boolean> {
// 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();
// Lightweight event normalization: ensure we have an object with string keys.
private normalizeEvent(event: unknown): Record<string, unknown> {
if (event && typeof event === 'object' && !Array.isArray(event))
return event as Record<string, unknown>;
return { raw: event };
}

private heuristicSeverity(
event: Record<string, unknown>,
): 'low' | 'medium' | 'high' | 'critical' {
const raw = event['severity'];
const s = (typeof raw === 'string' ? raw : String(raw || '')).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';
Expand All @@ -54,11 +62,16 @@ export class OpenAIProvider implements AIProvider {
}
}

private extractIndicators(event: any): string[] {
private extractIndicators(event: Record<string, unknown>): 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}`);
const addIfString = (k: string, prefix: string) => {
const v = event[k];
if (typeof v === 'string' && v.length > 0) indicators.push(`${prefix}:${v}`);
};

addIfString('ip', 'ip');
addIfString('user', 'user');
addIfString('filename', 'file');
return indicators;
}

Expand All @@ -74,3 +87,13 @@ export class OpenAIProvider implements AIProvider {
return ['Monitor and gather additional context'];
}
}

// Helper: safe stringify for unknown values
function safeStringify(v: unknown): string {
try {
if (typeof v === 'string') return v;
return JSON.stringify(v ?? '');
} catch {
return String(v ?? '');
}
}
9 changes: 9 additions & 0 deletions tsconfig.src.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"types": ["node"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Loading