import { v } from 'convex/values'; import { internalMutation, mutation, query } from './_generated/server'; import { internal } from './_generated/api'; import type { Id } from './_generated/dataModel'; import type { AtaqueCiberneticoTipo, SeveridadeSeguranca, StatusEventoSeguranca } from './schema'; import type { MutationCtx } from './_generated/server'; type Indicador = { tipo: string; valor: string; confianca?: number; }; type RegistroMetricas = { pps?: number; bps?: number; rpm?: number; errosPorSegundo?: number; hostsAfetados?: number; }; type RegistroFingerprint = { userAgent?: string; deviceId?: string; ja3?: string; tlsVersion?: string; }; type RegistroGeo = { pais?: string; regiao?: string; cidade?: string; latitude?: number; longitude?: number; }; type RegistroEventoArgs = { descricao?: string; tipoAtaque?: AtaqueCiberneticoTipo; severidade?: SeveridadeSeguranca; origemIp?: string; destinoIp?: string; destinoPorta?: number; protocolo?: string; transporte?: string; origemRegiao?: string; origemAsn?: string; mitreTechnique?: string; indicadores?: Indicador[]; metricas?: RegistroMetricas; fingerprint?: RegistroFingerprint; geolocalizacao?: RegistroGeo; tags?: string[]; }; const ATAQUES_PRIORITARIOS: Array = [ 'ddos', 'ransomware', 'ransomware_lateral', 'apt', 'zero_day', 'botnet', 'ot_ics', 'quantum_attack', 'polymorphic_malware', 'fileless_malware', 'firmware_bootloader', 'adversarial_ai', 'deepfake_phishing', 'phishing', 'sql_injection', 'xss', 'man_in_the_middle', 'credential_stuffing', 'brute_force', 'supply_chain', 'malware', 'engenharia_social', 'cve_exploit', 'bec', 'side_channel' ]; const BASE_SEVERIDADE: Record = { phishing: 'moderado', malware: 'moderado', ransomware: 'alto', brute_force: 'moderado', credential_stuffing: 'moderado', sql_injection: 'alto', xss: 'moderado', man_in_the_middle: 'alto', ddos: 'alto', engenharia_social: 'moderado', cve_exploit: 'alto', apt: 'critico', zero_day: 'critico', supply_chain: 'critico', fileless_malware: 'alto', polymorphic_malware: 'alto', ransomware_lateral: 'critico', deepfake_phishing: 'alto', adversarial_ai: 'alto', side_channel: 'alto', firmware_bootloader: 'critico', bec: 'alto', botnet: 'alto', ot_ics: 'critico', quantum_attack: 'critico' }; const SEVERIDADE_SCORE: Record = { informativo: 0, baixo: 1, moderado: 2, alto: 3, critico: 4 }; const SCORE_SEVERIDADE = Object.entries(SEVERIDADE_SCORE).reduce< Record >((acc, [nome, score]) => { acc[score] = nome as SeveridadeSeguranca; return acc; }, {}); const KEYWORDS: Record = { phishing: [/phish/i, /spoof/i, /fake login/i], malware: [/malware/i, /payload/i, /trojan/i], ransomware: [/ransom/i, /encrypt/i, /locker/i], brute_force: [/brute/i, /password guess/i, /login flood/i], credential_stuffing: [/credential/i, /stuffing/i, /combo/i], sql_injection: [/select\s+.*from/i, /union\s+select/i, /sql/i], xss: [/script>/i, /onerror=/i, /xss/i], man_in_the_middle: [/mitm/i, /man-in-the-middle/i, /ssl strip/i], ddos: [/ddos/i, /flood/i, /pps/i], engenharia_social: [/social/i, /engenharia/i], cve_exploit: [/cve-\d{4}-\d+/i, /exploit/i], apt: [/apt/i, /persistent/i], zero_day: [/zero[-\s]?day/i, /unknown exploit/i], supply_chain: [/supply chain/i, /dependency/i], fileless_malware: [/fileless/i, /memory-resident/i], polymorphic_malware: [/polymorphic/i, /mutation/i], ransomware_lateral: [/lateral/i, /spread/i], deepfake_phishing: [/deepfake/i, /voice clone/i], adversarial_ai: [/adversarial/i, /model evasion/i], side_channel: [/side channel/i, /cache timing/i], firmware_bootloader: [/firmware/i, /uefi/i, /bootloader/i], bec: [/bec/i, /business email/i], botnet: [/botnet/i, /c2/i, /command and control/i], ot_ics: [/ics/i, /scada/i, /plc/i], quantum_attack: [/quantum/i, /post-quantum/i] }; const CRITICAS: ReadonlySet = new Set(['alto', 'critico']); const ataqueValidator = v.union( v.literal('phishing'), v.literal('malware'), v.literal('ransomware'), v.literal('brute_force'), v.literal('credential_stuffing'), v.literal('sql_injection'), v.literal('xss'), v.literal('man_in_the_middle'), v.literal('ddos'), v.literal('engenharia_social'), v.literal('cve_exploit'), v.literal('apt'), v.literal('zero_day'), v.literal('supply_chain'), v.literal('fileless_malware'), v.literal('polymorphic_malware'), v.literal('ransomware_lateral'), v.literal('deepfake_phishing'), v.literal('adversarial_ai'), v.literal('side_channel'), v.literal('firmware_bootloader'), v.literal('bec'), v.literal('botnet'), v.literal('ot_ics'), v.literal('quantum_attack') ); const severidadeValidator = v.union( v.literal('informativo'), v.literal('baixo'), v.literal('moderado'), v.literal('alto'), v.literal('critico') ); const statusValidator = v.union( v.literal('detectado'), v.literal('investigando'), v.literal('contido'), v.literal('falso_positivo'), v.literal('escalado'), v.literal('resolvido') ); const indicadorCategoriaValidator = v.union( v.literal('ip'), v.literal('dominio'), v.literal('hash'), v.literal('email') ); const portActionValidator = v.union( v.literal('permitir'), v.literal('bloquear'), v.literal('monitorar'), v.literal('rate_limit') ); const protocoloValidator = v.union( v.literal('tcp'), v.literal('udp'), v.literal('icmp'), v.literal('quic'), v.literal('any') ); const acaoIncidenteValidator = v.union( v.literal('block_ip'), v.literal('unblock_ip'), v.literal('block_port'), v.literal('liberar_porta'), v.literal('notificar'), v.literal('isolar_host'), v.literal('gerar_relatorio'), v.literal('criar_ticket'), v.literal('ajuste_regra'), v.literal('custom') ); const acaoOrigemValidator = v.union(v.literal('automatico'), v.literal('manual')); function inferirTipoAtaque(args: RegistroEventoArgs): AtaqueCiberneticoTipo { if (args.tipoAtaque) return args.tipoAtaque; const corpus = [ args.descricao ?? '', args.mitreTechnique ?? '', args.tags?.join(' ') ?? '', ...(args.indicadores ?? []).map((i) => `${i.tipo}:${i.valor}`) ] .join(' ') .toLowerCase(); for (const tipo of ATAQUES_PRIORITARIOS) { const patterns = KEYWORDS[tipo]; if (patterns && patterns.some((regex) => regex.test(corpus))) { return tipo; } } if ((args.metricas?.pps ?? 0) > 120_000) { return 'ddos'; } if ((args.destinoPorta === 3306 || args.destinoPorta === 1433) && corpus.includes('union')) { return 'sql_injection'; } if (corpus.includes('tls downgrade')) { return 'man_in_the_middle'; } return 'malware'; } function calcularSeveridade( tipo: AtaqueCiberneticoTipo, metricas?: RegistroMetricas, sugerida?: SeveridadeSeguranca ): SeveridadeSeguranca { if (sugerida) return sugerida; const baseScore = SEVERIDADE_SCORE[BASE_SEVERIDADE[tipo] ?? 'moderado']; let score = baseScore; if ((metricas?.pps ?? 0) > 250_000) score += 1; if ((metricas?.bps ?? 0) > 1_000_000_000) score += 1; if ((metricas?.hostsAfetados ?? 0) > 50) score += 1; if ((metricas?.rpm ?? 0) > 10_000) score += 1; const bounded = Math.min(Math.max(score, 0), 4); return SCORE_SEVERIDADE[bounded] ?? 'moderado'; } function statusInicial(severidade: SeveridadeSeguranca): StatusEventoSeguranca { if (severidade === 'critico') return 'escalado'; if (severidade === 'alto') return 'investigando'; return 'detectado'; } async function ajustarReputacao( ctx: MutationCtx, indicador: string, categoria: 'ip' | 'dominio' | 'hash' | 'email', delta: number, severidade: SeveridadeSeguranca, opcoes?: { blacklist?: boolean; whitelist?: boolean; bloqueadoAte?: number } ) { const existente = await ctx.db .query('ipReputation') .withIndex('by_indicador', (q) => q.eq('indicador', indicador)) .order('desc') .first(); if (existente) { await ctx.db.patch(existente._id, { reputacao: Math.max(-100, Math.min(100, existente.reputacao + delta)), ultimoRegistro: Date.now(), bloqueadoAte: opcoes?.bloqueadoAte ?? existente.bloqueadoAte, blacklist: opcoes?.blacklist ?? existente.blacklist, whitelist: opcoes?.whitelist ?? existente.whitelist, severidadeMax: SEVERIDADE_SCORE[severidade] > SEVERIDADE_SCORE[existente.severidadeMax] ? severidade : existente.severidadeMax, ocorrencias: existente.ocorrencias + 1 }); return existente._id; } return ctx.db.insert('ipReputation', { indicador, categoria, reputacao: Math.max(-100, Math.min(100, delta)), severidadeMax: severidade, whitelist: opcoes?.whitelist ?? false, blacklist: opcoes?.blacklist ?? false, ocorrencias: 1, primeiroRegistro: Date.now(), ultimoRegistro: Date.now(), bloqueadoAte: opcoes?.bloqueadoAte, origem: 'detector', comentarios: undefined, classificacoes: undefined, ultimaAcaoId: undefined }); } export const registrarEventoSeguranca = mutation({ args: { referencia: v.string(), sensorId: v.optional(v.id('networkSensors')), descricao: v.optional(v.string()), tipoAtaque: v.optional(ataqueValidator), severidade: v.optional(severidadeValidator), origemIp: v.optional(v.string()), destinoIp: v.optional(v.string()), destinoPorta: v.optional(v.number()), protocolo: v.optional(v.string()), transporte: v.optional(v.string()), origemRegiao: v.optional(v.string()), origemAsn: v.optional(v.string()), mitreTechnique: v.optional(v.string()), indicadores: v.optional( v.array( v.object({ tipo: v.string(), valor: v.string(), confianca: v.optional(v.number()) }) ) ), metricas: v.optional( v.object({ pps: v.optional(v.number()), bps: v.optional(v.number()), rpm: v.optional(v.number()), errosPorSegundo: v.optional(v.number()), hostsAfetados: v.optional(v.number()) }) ), fingerprint: v.optional( v.object({ userAgent: v.optional(v.string()), deviceId: v.optional(v.string()), ja3: v.optional(v.string()), tlsVersion: v.optional(v.string()) }) ), geolocalizacao: v.optional( v.object({ pais: v.optional(v.string()), regiao: v.optional(v.string()), cidade: v.optional(v.string()), latitude: v.optional(v.number()), longitude: v.optional(v.number()) }) ), tags: v.optional(v.array(v.string())) }, returns: v.object({ eventoId: v.id('securityEvents'), severidade: severidadeValidator, novoRegistro: v.boolean() }), handler: async (ctx, args) => { const tipo = inferirTipoAtaque(args); const severidade = calcularSeveridade(tipo, args.metricas ?? undefined, args.severidade ?? undefined); const status = statusInicial(severidade); const duplicado = await ctx.db .query('securityEvents') .withIndex('by_referencia', (q) => q.eq('referencia', args.referencia)) .order('desc') .first(); if (duplicado) { await ctx.db.patch(duplicado._id, { severidade, status, atualizadoEm: Date.now(), descricao: args.descricao ?? duplicado.descricao, metricas: args.metricas ?? duplicado.metricas, tags: args.tags ?? duplicado.tags }); return { eventoId: duplicado._id, severidade, novoRegistro: false }; } const eventoId = await ctx.db.insert('securityEvents', { referencia: args.referencia, timestamp: Date.now(), tipoAtaque: tipo, severidade, status, descricao: args.descricao ?? `Evento registrado automaticamente para ${tipo.toUpperCase().replace(/_/g, ' ')}.`, origemIp: args.origemIp, destinoIp: args.destinoIp, destinoPorta: args.destinoPorta, protocolo: args.protocolo, transporte: args.transporte, sensorId: args.sensorId, detectadoPor: args.sensorId ? 'sensor' : 'manual', mitreTechnique: args.mitreTechnique, origemRegiao: args.origemRegiao, origemAsn: args.origemAsn, geolocalizacao: args.geolocalizacao, fingerprint: args.fingerprint, indicadores: args.indicadores, metricas: args.metricas, tags: args.tags, referenciasExternas: undefined, correlacoes: undefined, criadoPor: undefined, atualizadoEm: Date.now() }); if (args.origemIp) { const delta = CRITICAS.has(severidade) ? -30 : -10; const bloqueadoAte = CRITICAS.has(severidade) ? Date.now() + 60 * 60 * 1000 : undefined; await ajustarReputacao(ctx, args.origemIp, 'ip', delta, severidade, { blacklist: CRITICAS.has(severidade), bloqueadoAte }); } if (CRITICAS.has(severidade)) { await ctx.scheduler.runAfter(0, internal.security.dispararAlertasInternos, { eventoId }); } return { eventoId, severidade, novoRegistro: true }; } }); export const listarEventosSeguranca = query({ args: { limit: v.optional(v.number()), apos: v.optional(v.number()), severidades: v.optional(v.array(severidadeValidator)), tiposAtaque: v.optional(v.array(ataqueValidator)), status: v.optional(v.array(statusValidator)) }, returns: v.array( v.object({ _id: v.id('securityEvents'), timestamp: v.number(), tipoAtaque: ataqueValidator, severidade: severidadeValidator, status: statusValidator, descricao: v.string(), origemIp: v.optional(v.string()), destinoIp: v.optional(v.string()), destinoPorta: v.optional(v.number()), protocolo: v.optional(v.string()), tags: v.optional(v.array(v.string())) }) ), handler: async (ctx, args) => { const limit = args.limit && args.limit > 0 ? Math.min(args.limit, 500) : 100; const janelaInicial = args.apos ?? Date.now() - 6 * 60 * 60 * 1000; const { severidades, tiposAtaque, status } = args; let builder; if (severidades && severidades.length === 1) { const [severidade] = severidades; builder = ctx.db .query('securityEvents') .withIndex('by_severidade', (q) => q.eq('severidade', severidade)); } else if (tiposAtaque && tiposAtaque.length === 1) { const [tipoAtaque] = tiposAtaque; builder = ctx.db .query('securityEvents') .withIndex('by_tipo', (q) => q.eq('tipoAtaque', tipoAtaque)); } else if (status && status.length === 1) { const [estado] = status; builder = ctx.db .query('securityEvents') .withIndex('by_status', (q) => q.eq('status', estado)); } else { builder = ctx.db .query('securityEvents') .withIndex('by_timestamp', (q) => q.gte('timestamp', janelaInicial)); } const candidatos = await builder.order('desc').take(limit * 3); const filtrados = candidatos .filter((evento) => { if (args.severidades && args.severidades.length > 0 && !args.severidades.includes(evento.severidade)) { return false; } if (args.tiposAtaque && args.tiposAtaque.length > 0 && !args.tiposAtaque.includes(evento.tipoAtaque)) { return false; } if (args.status && args.status.length > 0 && !args.status.includes(evento.status)) { return false; } return true; }) .slice(0, limit); return filtrados.map((evento) => ({ _id: evento._id, timestamp: evento.timestamp, tipoAtaque: evento.tipoAtaque, severidade: evento.severidade, status: evento.status, descricao: evento.descricao, origemIp: evento.origemIp, destinoIp: evento.destinoIp, destinoPorta: evento.destinoPorta, protocolo: evento.protocolo, tags: evento.tags })); } }); const ATAQUES_AVANCADOS: ReadonlySet = new Set([ 'apt', 'zero_day', 'supply_chain', 'fileless_malware', 'polymorphic_malware', 'ransomware_lateral', 'deepfake_phishing', 'adversarial_ai', 'side_channel', 'firmware_bootloader', 'botnet', 'ot_ics', 'quantum_attack' ]); export const obterVisaoCamadas = query({ args: { periodoHoras: v.optional(v.number()), buckets: v.optional(v.number()) }, returns: v.object({ series: v.array( v.object({ bucket: v.number(), inicio: v.number(), fim: v.number(), bloqueios: v.number(), ddos: v.number(), sqlInjection: v.number(), phishing: v.number(), avancados: v.number() }) ), totais: v.object({ eventos: v.number(), criticos: v.number(), bloqueiosAtivos: v.number(), sensoresAtivos: v.number() }) }), handler: async (ctx, args) => { const agora = Date.now(); const periodoMs = Math.max(1, args.periodoHoras ?? 6) * 60 * 60 * 1000; const inicioJanela = agora - periodoMs; const bucketCount = Math.min(Math.max(args.buckets ?? 24, 4), 96); const bucketSize = Math.ceil(periodoMs / bucketCount); const eventos = await ctx.db .query('securityEvents') .withIndex('by_timestamp', (q) => q.gte('timestamp', inicioJanela)) .order('asc') .collect(); const series = Array.from({ length: bucketCount }, (_, index) => { const inicio = inicioJanela + index * bucketSize; return { bucket: index, inicio, fim: inicio + bucketSize, bloqueios: 0, ddos: 0, sqlInjection: 0, phishing: 0, avancados: 0 }; }); let criticos = 0; for (const evento of eventos) { const idx = Math.min( bucketCount - 1, Math.max( 0, Math.floor((evento.timestamp - inicioJanela) / bucketSize) ) ); const bucket = series[idx]; if (evento.severidade === 'critico') criticos += 1; if (CRITICAS.has(evento.severidade)) bucket.bloqueios += 1; if (evento.tipoAtaque === 'ddos') bucket.ddos += 1; if (evento.tipoAtaque === 'sql_injection') bucket.sqlInjection += 1; if (evento.tipoAtaque === 'phishing' || evento.tipoAtaque === 'deepfake_phishing') { bucket.phishing += 1; } if (ATAQUES_AVANCADOS.has(evento.tipoAtaque)) bucket.avancados += 1; } const bloqueios = await ctx.db .query('ipReputation') .withIndex('by_blacklist', (q) => q.eq('blacklist', true)) .collect(); const bloqueiosAtivos = bloqueios.filter( (item) => !item.bloqueadoAte || item.bloqueadoAte > agora ).length; const sensoresAtivos = await ctx.db .query('networkSensors') .withIndex('by_status', (q) => q.eq('status', 'ativo')) .collect(); return { series, totais: { eventos: eventos.length, criticos, bloqueiosAtivos, sensoresAtivos: sensoresAtivos.length } }; } }); export const listarReputacoes = query({ args: { limit: v.optional(v.number()), categoria: v.optional(indicadorCategoriaValidator), lista: v.optional(v.union(v.literal('blacklist'), v.literal('whitelist'))) }, returns: v.array( v.object({ _id: v.id('ipReputation'), indicador: v.string(), categoria: indicadorCategoriaValidator, reputacao: v.number(), severidadeMax: severidadeValidator, whitelist: v.boolean(), blacklist: v.boolean(), bloqueadoAte: v.optional(v.number()), ocorrencias: v.number(), ultimoRegistro: v.number() }) ), handler: async (ctx, args) => { const limit = args.limit && args.limit > 0 ? Math.min(args.limit, 500) : 200; const builder = args.lista === undefined ? ctx.db.query('ipReputation') : args.lista === 'blacklist' ? ctx.db.query('ipReputation').withIndex('by_blacklist', (q) => q.eq('blacklist', true)) : ctx.db.query('ipReputation').withIndex('by_whitelist', (q) => q.eq('whitelist', true)); const docs = await builder.order('desc').take(limit * 2); const filtrados = docs .filter((doc) => { if (args.categoria && doc.categoria !== args.categoria) return false; return true; }) .slice(0, limit); return filtrados.map((doc) => ({ _id: doc._id, indicador: doc.indicador, categoria: doc.categoria, reputacao: doc.reputacao, severidadeMax: doc.severidadeMax, whitelist: doc.whitelist, blacklist: doc.blacklist, bloqueadoAte: doc.bloqueadoAte, ocorrencias: doc.ocorrencias, ultimoRegistro: doc.ultimoRegistro })); } }); export const atualizarReputacaoIndicador = mutation({ args: { usuarioId: v.id('usuarios'), indicador: v.string(), categoria: indicadorCategoriaValidator, acao: v.union( v.literal('forcar_blacklist'), v.literal('remover_blacklist'), v.literal('forcar_whitelist'), v.literal('remover_whitelist'), v.literal('ajustar_score'), v.literal('registrar_comentario') ), delta: v.optional(v.number()), comentario: v.optional(v.string()), duracaoSegundos: v.optional(v.number()) }, returns: v.object({ reputacaoId: v.id('ipReputation'), status: v.string() }), handler: async (ctx, args) => { const existente = await ctx.db .query('ipReputation') .withIndex('by_indicador', (q) => q.eq('indicador', args.indicador)) .order('desc') .first(); const agora = Date.now(); const bloqueioAte = args.duracaoSegundos ? agora + args.duracaoSegundos * 1000 : undefined; if (!existente) { const reputacaoId = await ctx.db.insert('ipReputation', { indicador: args.indicador, categoria: args.categoria, reputacao: args.delta ?? 0, severidadeMax: 'moderado', whitelist: args.acao === 'forcar_whitelist', blacklist: args.acao === 'forcar_blacklist', ocorrencias: 1, primeiroRegistro: agora, ultimoRegistro: agora, bloqueadoAte: bloqueioAte, origem: 'painel', comentarios: args.comentario, classificacoes: undefined, ultimaAcaoId: undefined }); await ctx.db.insert('incidentActions', { eventoId: await ctx.db.insert('securityEvents', { referencia: `auto-${args.indicador}-${agora}`, timestamp: agora, tipoAtaque: 'engenharia_social', severidade: 'informativo', status: 'contido', descricao: 'Registro criado via painel de reputação.', origemIp: args.categoria === 'ip' ? args.indicador : undefined, destinoIp: undefined, destinoPorta: undefined, protocolo: undefined, transporte: undefined, sensorId: undefined, detectadoPor: 'manual', mitreTechnique: undefined, origemRegiao: undefined, origemAsn: undefined, geolocalizacao: undefined, fingerprint: undefined, indicadores: undefined, metricas: undefined, tags: ['reputacao'], referenciasExternas: undefined, correlacoes: undefined, criadoPor: args.usuarioId, atualizadoEm: agora }), tipo: args.acao === 'forcar_blacklist' ? 'block_ip' : 'custom', origem: 'manual', status: 'concluido', executadoPor: args.usuarioId, detalhes: args.comentario, resultado: 'Registro inicial', relacionadoA: undefined, criadoEm: agora, atualizadoEm: agora }); return { reputacaoId, status: 'criado' }; } const patch: Record = { ultimoRegistro: agora }; switch (args.acao) { case 'forcar_blacklist': patch.blacklist = true; patch.bloqueadoAte = bloqueioAte; break; case 'remover_blacklist': patch.blacklist = false; patch.bloqueadoAte = undefined; break; case 'forcar_whitelist': patch.whitelist = true; break; case 'remover_whitelist': patch.whitelist = false; break; case 'ajustar_score': patch.reputacao = Math.max( -100, Math.min(100, (existente.reputacao ?? 0) + (args.delta ?? 0)) ); break; case 'registrar_comentario': patch.comentarios = args.comentario; break; } await ctx.db.patch(existente._id, patch); return { reputacaoId: existente._id, status: 'atualizado' }; } }); export const configurarRegraPorta = mutation({ args: { usuarioId: v.id('usuarios'), regraId: v.optional(v.id('portRules')), porta: v.number(), protocolo: protocoloValidator, acao: portActionValidator, temporario: v.boolean(), duracaoSegundos: v.optional(v.number()), severidadeMin: severidadeValidator, notas: v.optional(v.string()), tags: v.optional(v.array(v.string())), listaReferencia: v.optional(v.id('ipReputation')) }, returns: v.object({ regraId: v.id('portRules'), status: v.string() }), handler: async (ctx, args) => { const agora = Date.now(); const expiraEm = args.temporario && args.duracaoSegundos ? agora + args.duracaoSegundos * 1000 : undefined; if (args.regraId) { await ctx.db.patch(args.regraId, { porta: args.porta, protocolo: args.protocolo, acao: args.acao, temporario: args.temporario, duracaoSegundos: args.duracaoSegundos, expiraEm, atualizadoPor: args.usuarioId, atualizadoEm: agora, severidadeMin: args.severidadeMin, notas: args.notas, tags: args.tags, listaReferencia: args.listaReferencia }); return { regraId: args.regraId, status: 'atualizado' }; } const regraId = await ctx.db.insert('portRules', { porta: args.porta, protocolo: args.protocolo, acao: args.acao, temporario: args.temporario, severidadeMin: args.severidadeMin, duracaoSegundos: args.duracaoSegundos, expiraEm, criadoPor: args.usuarioId, atualizadoPor: args.usuarioId, criadoEm: agora, atualizadoEm: agora, notas: args.notas, tags: args.tags, listaReferencia: args.listaReferencia }); return { regraId, status: 'criado' }; } }); export const listarRegrasPorta = query({ args: { acao: v.optional(portActionValidator), ativo: v.optional(v.boolean()) }, returns: v.array( v.object({ _id: v.id('portRules'), porta: v.number(), protocolo: protocoloValidator, acao: portActionValidator, temporario: v.boolean(), expiraEm: v.optional(v.number()), severidadeMin: severidadeValidator, tags: v.optional(v.array(v.string())), notas: v.optional(v.string()) }) ), handler: async (ctx, args) => { const builder = args.acao === undefined ? ctx.db.query('portRules') : ctx.db.query('portRules').withIndex('by_acao', (q) => { const acao = args.acao; return q.eq('acao', acao as 'permitir' | 'bloquear' | 'monitorar' | 'rate_limit'); }); const docs = await builder.order('desc').take(200); const filtrados = docs.filter((doc) => { if (args.ativo === undefined) return true; if (!doc.temporario) return true; if (!doc.expiraEm) return !args.ativo; const aindaValido = doc.expiraEm > Date.now(); return args.ativo ? aindaValido : !aindaValido; }); return filtrados.map((doc) => ({ _id: doc._id, porta: doc.porta, protocolo: doc.protocolo, acao: doc.acao, temporario: doc.temporario, expiraEm: doc.expiraEm, severidadeMin: doc.severidadeMin, tags: doc.tags, notas: doc.notas })); } }); export const registrarAcaoIncidente = mutation({ args: { eventoId: v.id('securityEvents'), tipo: acaoIncidenteValidator, origem: acaoOrigemValidator, executadoPor: v.optional(v.id('usuarios')), status: v.optional(v.union(v.literal('pendente'), v.literal('executando'), v.literal('concluido'), v.literal('falhou'))), detalhes: v.optional(v.string()), resultado: v.optional(v.string()), relacionadoA: v.optional(v.id('ipReputation')) }, returns: v.object({ acaoId: v.id('incidentActions') }), handler: async (ctx, args) => { const agora = Date.now(); const acaoId = await ctx.db.insert('incidentActions', { eventoId: args.eventoId, tipo: args.tipo, origem: args.origem, status: args.status ?? 'concluido', executadoPor: args.executadoPor, detalhes: args.detalhes, resultado: args.resultado, relacionadoA: args.relacionadoA, criadoEm: agora, atualizadoEm: agora }); return { acaoId }; } }); export const solicitarRelatorioSeguranca = mutation({ args: { solicitanteId: v.id('usuarios'), filtros: v.object({ dataInicio: v.number(), dataFim: v.number(), severidades: v.optional(v.array(severidadeValidator)), tiposAtaque: v.optional(v.array(ataqueValidator)), incluirIndicadores: v.optional(v.boolean()), incluirMetricas: v.optional(v.boolean()), incluirAcoes: v.optional(v.boolean()) }) }, returns: v.object({ relatorioId: v.id('reportRequests') }), handler: async (ctx, args) => { const relatorioId = await ctx.db.insert('reportRequests', { solicitanteId: args.solicitanteId, filtros: { dataInicio: args.filtros.dataInicio, dataFim: args.filtros.dataFim, severidades: args.filtros.severidades, tiposAtaque: args.filtros.tiposAtaque, incluirIndicadores: args.filtros.incluirIndicadores, incluirMetricas: args.filtros.incluirMetricas, incluirAcoes: args.filtros.incluirAcoes }, status: 'pendente', resultadoId: undefined, observacoes: undefined, criadoEm: Date.now(), atualizadoEm: Date.now(), concluidoEm: undefined, erro: undefined }); await ctx.scheduler.runAfter(0, internal.security.processarRelatorioSegurancaInternal, { relatorioId }); return { relatorioId }; } }); export const processarRelatorioSegurancaInternal = internalMutation({ args: { relatorioId: v.id('reportRequests') }, returns: v.null(), handler: async (ctx, args) => { const relatorio = await ctx.db.get(args.relatorioId); if (!relatorio || relatorio.status !== 'pendente') return null; const eventos = await ctx.db .query('securityEvents') .withIndex('by_timestamp', (q) => q .gte('timestamp', relatorio.filtros.dataInicio) .lte('timestamp', relatorio.filtros.dataFim) ) .collect(); const filtrados = eventos.filter((evento) => { if ( relatorio.filtros.severidades && relatorio.filtros.severidades.length > 0 && !relatorio.filtros.severidades.includes(evento.severidade) ) { return false; } if ( relatorio.filtros.tiposAtaque && relatorio.filtros.tiposAtaque.length > 0 && !relatorio.filtros.tiposAtaque.includes(evento.tipoAtaque) ) { return false; } return true; }); const porSeveridade = filtrados.reduce>( (acc, evento) => { acc[evento.severidade] = (acc[evento.severidade] ?? 0) + 1; return acc; }, { informativo: 0, baixo: 0, moderado: 0, alto: 0, critico: 0 } ); const porAtaque = filtrados.reduce>((acc, evento) => { acc[evento.tipoAtaque] = (acc[evento.tipoAtaque] ?? 0) + 1; return acc; }, {}); const observacoes = JSON.stringify({ total: filtrados.length, porSeveridade, porAtaque, incluiuIndicadores: relatorio.filtros.incluirIndicadores ?? false, incluiuMetricas: relatorio.filtros.incluirMetricas ?? false, incluiuAcoes: relatorio.filtros.incluirAcoes ?? false }); await ctx.db.patch(relatorio._id, { status: 'concluido', observacoes, atualizadoEm: Date.now(), concluidoEm: Date.now() }); return null; } }); export const dispararAlertasInternos = internalMutation({ args: { eventoId: v.id('securityEvents') }, returns: v.null(), handler: async (ctx, args) => { const evento = await ctx.db.get(args.eventoId); if (!evento) return null; const rolesTi = await ctx.db .query('roles') .withIndex('by_nivel', (q) => q.lte('nivel', 1)) .collect(); const usuariosNotificados: Id<'usuarios'>[] = []; for (const role of rolesTi) { const membros = await ctx.db.query('usuarios').withIndex('by_role', (q) => q.eq('roleId', role._id)).collect(); for (const usuario of membros) { usuariosNotificados.push(usuario._id); } } for (const usuarioId of usuariosNotificados) { await ctx.db.insert('notificacoes', { usuarioId, tipo: 'alerta_seguranca', conversaId: undefined, mensagemId: undefined, remetenteId: undefined, titulo: `🚨 ${evento.severidade.toUpperCase()} - ${evento.tipoAtaque.replace(/_/g, ' ')}`, descricao: evento.descricao, lida: false, criadaEm: Date.now() }); } return null; } }); export const expirarBloqueiosIpAutomaticos = internalMutation({ args: {}, returns: v.null(), handler: async (ctx) => { const agora = Date.now(); const bloqueios = await ctx.db .query('ipReputation') .withIndex('by_blacklist', (q) => q.eq('blacklist', true)) .collect(); for (const registro of bloqueios) { if (registro.bloqueadoAte && registro.bloqueadoAte <= agora) { await ctx.db.patch(registro._id, { blacklist: false, bloqueadoAte: undefined }); } } const regras = await ctx.db .query('portRules') .withIndex('by_expiracao', (q) => q.lte('expiraEm', agora)) .collect(); for (const regra of regras) { if (regra.temporario && regra.expiraEm && regra.expiraEm <= agora) { await ctx.db.patch(regra._id, { acao: 'monitorar', temporario: false, expiraEm: undefined }); } } return null; } }); export const atualizarThreatIntelFeedsInternal = internalMutation({ args: {}, returns: v.null(), handler: async (ctx) => { const feeds = await ctx.db .query('threatIntelFeeds') .withIndex('by_ativo', (q) => q.eq('ativo', true)) .collect(); for (const feed of feeds) { const entradasSimuladas = Math.floor(Math.random() * 25) + 5; await ctx.db.patch(feed._id, { ultimaSincronizacao: Date.now(), entradasProcessadas: (feed.entradasProcessadas ?? 0) + entradasSimuladas, errosConsecutivos: 0 }); } return null; } });