diff --git a/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte new file mode 100644 index 0000000..08787a5 --- /dev/null +++ b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte @@ -0,0 +1,714 @@ + + +
+ {#if feedback} +
+
+ {feedback.mensagem} +
+ +
+ {/if} + +
+
+
Eventos monitorados
+
{totais?.eventos ?? 0}
+
Últimas 6h
+
+
+
Críticos
+
{totais?.criticos ?? 0}
+
Escalonados imediatamente
+
+
+
Bloqueios ativos
+
{totais?.bloqueiosAtivos ?? 0}
+
IPs e domínios isolados
+
+
+
Sensores ativos
+
{totais?.sensoresAtivos ?? 0}
+
Edge, OT e honeypots
+
+
+ +
+
+
+
+

Layerchart Threat Matrix

+

+ Correlação temporal entre DDoS, SQLi, ataques avançados e bloqueios automáticos. +

+
+
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + DDoS + + + SQL Injection + + + ATA Advanced + +
+
+ +
+ {#each severidadesDisponiveis as severidade} + + {/each} +
+
+ +
+

Ações rápidas

+
{ + event.preventDefault(); + aplicarMedidaIp('forcar_blacklist', ipManual); + }} + > + + +
+ + +
+
+ +
Regras de Porta
+
+
+ + +
+ +
+ + Bloqueio temporário? + {#if temporario} + + {/if} +
+ + +
+
+
+ +
+
+
+

Feed de eventos e ameaças

+
+ {#each Object.entries(attackLabels).slice(0, 8) as [tipo, label]} + + {/each} +
+
+
+ {#if eventosFiltrados.length === 0} +

Nenhum evento correspondente aos filtros.

+ {:else} + {#each eventosFiltrados as evento} +
+
+
+
+ {severityLabels[evento.severidade]} + {attackLabels[evento.tipoAtaque]} + {evento.status} +
+ {formatarData(evento.timestamp)} +
+

{evento.descricao}

+
+
+
+ Origem: + {evento.origemIp ?? 'n/d'} +
+
+ Destino: + {evento.destinoIp ?? 'n/d'}:{evento.destinoPorta ?? '--'} +
+
+
+
+ Protocolo: + {evento.protocolo ?? 'n/d'} +
+
+ Tags: + {evento.tags?.join(', ') ?? '—'} +
+
+
+
+ + +
+
+
+ {/each} + {/if} +
+
+ +
+
+

Lista Negra Inteligente

+
    + {#if ipCriticos.length === 0} +
  • Nenhum IP crítico listado.
  • + {:else} + {#each ipCriticos as registro} +
  • +
    +

    {registro.indicador}

    +

    + Score: {registro.reputacao} • Ocorrências: {registro.ocorrencias} +

    +
    + +
  • + {/each} + {/if} +
+
+ +
+

Regras de Portas Monitoradas

+
+ {#if regras.length === 0} +

Nenhuma regra cadastrada.

+ {:else} + {#each regras as regra} +
+
+ + {regra.porta}/{regra.protocolo.toUpperCase()} + + {regra.acao} +
+

+ Severidade mínima: {severityLabels[regra.severidadeMin]} +

+ {#if regra.expiraEm} +

+ Expira em: {new Date(regra.expiraEm).toLocaleString('pt-BR', { hour12: false })} +

+ {/if} +
+ {/each} + {/if} +
+
+ +
+

Relatórios refinados

+
+ + + + + +
+
+
+
+
+ + + diff --git a/apps/web/src/routes/(dashboard)/ti/+page.svelte b/apps/web/src/routes/(dashboard)/ti/+page.svelte index a3d7ef3..5969f63 100644 --- a/apps/web/src/routes/(dashboard)/ti/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/+page.svelte @@ -1,485 +1,487 @@
-
-
-
-
-
- - Tecnologia da Informação - -

- Sistemas de Informação -

-

- Acesso restrito para gerenciamento de solicitações de acesso ao - sistema, configuração de permissões e monitoramento técnico das - operações do SGSE. -

-
-
-
-

Status

-

Operacional

-
-
-

- Última atualização -

-

Agora mesmo

-
-
-
- Monitoramento em tempo real. - SGSE -
-
-
-
+
+
+
+
+
+ + Tecnologia da Informação + +

+ Sistemas de Informação +

+

+ Acesso restrito para gerenciamento de solicitações de acesso ao sistema, configuração de + permissões e monitoramento técnico das operações do SGSE. +

+
+
+
+

Status

+

Operacional

+
+
+

Última atualização

+

Agora mesmo

+
+
+
+ Monitoramento em tempo real. + SGSE +
+
+
+
-
- {#each featureCards as card (card.title)} -
-
-
-
- - {#each iconPaths[card.icon] as path (path.d)} - - {/each} - -
-
-

- {card.title} -

-

- {card.description} -

-
-
+
+ {#each featureCards as card (card.title)} +
+
+
+
+ + {#each iconPaths[card.icon] as path (path.d)} + + {/each} + +
+
+

+ {card.title} +

+

+ {card.description} +

+
+
- {#if card.highlightBadges} -
- {#each card.highlightBadges as badge (badge.label)} - {#if badge.variant === "solid"} - {badge.label} - {:else} - - {badge.label} - - {/if} - {/each} -
- {/if} + {#if card.highlightBadges} +
+ {#each card.highlightBadges as badge (badge.label)} + {#if badge.variant === 'solid'} + {badge.label} + {:else} + + {badge.label} + + {/if} + {/each} +
+ {/if} -
- {#if card.href && !card.disabled} - - {card.ctaLabel} - - {:else} - - {/if} -
-
- {/each} -
+
+ {#if card.href && !card.disabled} + + {card.ctaLabel} + + {:else} + + {/if} +
+
+ {/each} +
-
-
-
-
- - - -
-
-

Área Restrita

-

- Esta área é exclusiva da equipe de Tecnologia da Informação. Garanta - que apenas usuários autorizados acessem o Painel Administrativo e - mantenha suas credenciais em segurança. -

-
-
-
+
+
+
+
+ + + +
+
+

Área Restrita

+

+ Esta área é exclusiva da equipe de Tecnologia da Informação. Garanta que apenas usuários + autorizados acessem o Painel Administrativo e mantenha suas credenciais em segurança. +

+
+
+
diff --git a/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte b/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte new file mode 100644 index 0000000..158c064 --- /dev/null +++ b/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte @@ -0,0 +1,30 @@ + + + + Cibersecurity SGSE • Wizcard TI + + +
+
+
+

Cibersecurity • SGSE

+

+ Wizcard de Segurança Avançada +

+

+ Detecta DDoS, SQLi, ataques avançados e comportamentos anômalos em tempo real. + Permite bloquear IPs/portas, gerar relatórios refinados, configurar políticas e + manter a operação do SGSE blindada. +

+
+ Voltar para TI +
+ + +
+ + + diff --git a/cibersecurity-after-restart.png b/cibersecurity-after-restart.png new file mode 100644 index 0000000..3a1c3e2 Binary files /dev/null and b/cibersecurity-after-restart.png differ diff --git a/cibersecurity-error-500.png b/cibersecurity-error-500.png new file mode 100644 index 0000000..b691cbf Binary files /dev/null and b/cibersecurity-error-500.png differ diff --git a/cibersecurity-page-current.png b/cibersecurity-page-current.png new file mode 100644 index 0000000..3a1c3e2 Binary files /dev/null and b/cibersecurity-page-current.png differ diff --git a/cibersecurity-working.png b/cibersecurity-working.png new file mode 100644 index 0000000..e867d2b Binary files /dev/null and b/cibersecurity-working.png differ diff --git a/packages/backend/convex/_generated/api.d.ts b/packages/backend/convex/_generated/api.d.ts index b863aa0..0609ed5 100644 --- a/packages/backend/convex/_generated/api.d.ts +++ b/packages/backend/convex/_generated/api.d.ts @@ -39,6 +39,7 @@ import type * as preferenciasNotificacao from "../preferenciasNotificacao.js"; import type * as pushNotifications from "../pushNotifications.js"; import type * as roles from "../roles.js"; import type * as saldoFerias from "../saldoFerias.js"; +import type * as security from "../security.js"; import type * as seed from "../seed.js"; import type * as simbolos from "../simbolos.js"; import type * as solicitacoesAcesso from "../solicitacoesAcesso.js"; @@ -87,6 +88,7 @@ declare const fullApi: ApiFromModules<{ pushNotifications: typeof pushNotifications; roles: typeof roles; saldoFerias: typeof saldoFerias; + security: typeof security; seed: typeof seed; simbolos: typeof simbolos; solicitacoesAcesso: typeof solicitacoesAcesso; diff --git a/packages/backend/convex/crons.ts b/packages/backend/convex/crons.ts index e3789d9..6961cba 100644 --- a/packages/backend/convex/crons.ts +++ b/packages/backend/convex/crons.ts @@ -32,6 +32,19 @@ crons.interval( {} ); +crons.interval( + "expirar-bloqueios-ip-automaticos", + { minutes: 5 }, + internal.security.expirarBloqueiosIpAutomaticos, + {} +); + +crons.interval( + "sincronizar-threat-intel", + { hours: 2 }, + internal.security.atualizarThreatIntelFeedsInternal, + {} +); export default crons; diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts index 2b5291f..dfd8885 100644 --- a/packages/backend/convex/schema.ts +++ b/packages/backend/convex/schema.ts @@ -7,6 +7,115 @@ export const simboloTipo = v.union( ); export type SimboloTipo = Infer; +export const ataqueCiberneticoTipo = 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") +); +export type AtaqueCiberneticoTipo = Infer; + +export const severidadeSeguranca = v.union( + v.literal("informativo"), + v.literal("baixo"), + v.literal("moderado"), + v.literal("alto"), + v.literal("critico") +); +export type SeveridadeSeguranca = Infer; + +export const statusEventoSeguranca = v.union( + v.literal("detectado"), + v.literal("investigando"), + v.literal("contido"), + v.literal("falso_positivo"), + v.literal("escalado"), + v.literal("resolvido") +); +export type StatusEventoSeguranca = Infer; + +export const sensorSegurancaTipo = v.union( + v.literal("network"), + v.literal("endpoint"), + v.literal("application"), + v.literal("gateway"), + v.literal("ot"), + v.literal("honeypot") +); +export type SensorSegurancaTipo = Infer; + +export const sensorSegurancaStatus = v.union( + v.literal("ativo"), + v.literal("inativo"), + v.literal("degradado"), + v.literal("manutencao") +); +export type SensorSegurancaStatus = Infer; + +export const threatIntelTipo = v.union( + v.literal("open_source"), + v.literal("commercial"), + v.literal("internal"), + v.literal("gov"), + v.literal("research") +); + +export const threatIntelFormato = v.union( + v.literal("json"), + v.literal("stix"), + v.literal("csv"), + v.literal("text"), + v.literal("custom") +); + +export const acaoIncidenteTipo = 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") +); + +export const acaoIncidenteStatus = v.union( + v.literal("pendente"), + v.literal("executando"), + v.literal("concluido"), + v.literal("falhou") +); + +export const reportStatus = v.union( + v.literal("pendente"), + v.literal("processando"), + v.literal("concluido"), + v.literal("falhou") +); + export default defineSchema({ todos: defineTable({ text: v.string(), @@ -670,7 +779,8 @@ export default defineSchema({ v.literal("nova_mensagem"), v.literal("mencao"), v.literal("grupo_criado"), - v.literal("adicionado_grupo") + v.literal("adicionado_grupo"), + v.literal("alerta_seguranca") ), conversaId: v.optional(v.id("conversas")), mensagemId: v.optional(v.id("mensagens")), @@ -938,4 +1048,217 @@ export default defineSchema({ }) .index("by_ticket", ["ticketId", "ativo"]) .index("by_responsavel", ["responsavelId", "ativo"]), + + // Sistema de Segurança Cibernética + networkSensors: defineTable({ + nome: v.string(), + tipo: sensorSegurancaTipo, + status: sensorSegurancaStatus, + escopo: v.optional(v.string()), + ipMonitorado: v.optional(v.string()), + hostname: v.optional(v.string()), + regioes: v.optional(v.array(v.string())), + portasMonitoradas: v.optional(v.array(v.number())), + protocolos: v.optional(v.array(v.string())), + capacidades: v.optional(v.array(v.string())), + ultimaSincronizacao: v.number(), + ultimoHeartbeat: v.optional(v.number()), + latenciaMs: v.optional(v.number()), + errosConsecutivos: v.optional(v.number()), + agenteVersao: v.optional(v.string()), + notas: v.optional(v.string()), + }) + .index("by_tipo", ["tipo"]) + .index("by_status", ["status"]) + .index("by_hostname", ["hostname"]), + + ipReputation: defineTable({ + indicador: v.string(), + categoria: v.union( + v.literal("ip"), + v.literal("dominio"), + v.literal("hash"), + v.literal("email") + ), + reputacao: v.number(), // -100 (malicioso) até 100 (confiável) + severidadeMax: severidadeSeguranca, + whitelist: v.boolean(), + blacklist: v.boolean(), + ocorrencias: v.number(), + primeiroRegistro: v.number(), + ultimoRegistro: v.number(), + bloqueadoAte: v.optional(v.number()), + origem: v.optional(v.string()), + comentarios: v.optional(v.string()), + classificacoes: v.optional(v.array(v.string())), + ultimaAcaoId: v.optional(v.id("incidentActions")), + }) + .index("by_indicador", ["indicador"]) + .index("by_reputacao", ["reputacao"]) + .index("by_blacklist", ["blacklist"]) + .index("by_whitelist", ["whitelist"]), + + portRules: defineTable({ + porta: v.number(), + protocolo: v.union( + v.literal("tcp"), + v.literal("udp"), + v.literal("icmp"), + v.literal("quic"), + v.literal("any") + ), + acao: v.union( + v.literal("permitir"), + v.literal("bloquear"), + v.literal("monitorar"), + v.literal("rate_limit") + ), + temporario: v.boolean(), + severidadeMin: severidadeSeguranca, + duracaoSegundos: v.optional(v.number()), + expiraEm: v.optional(v.number()), + criadoPor: v.id("usuarios"), + atualizadoPor: v.optional(v.id("usuarios")), + criadoEm: v.number(), + atualizadoEm: v.number(), + notas: v.optional(v.string()), + tags: v.optional(v.array(v.string())), + listaReferencia: v.optional(v.id("ipReputation")), + }) + .index("by_porta_protocolo", ["porta", "protocolo"]) + .index("by_acao", ["acao"]) + .index("by_expiracao", ["expiraEm"]), + + threatIntelFeeds: defineTable({ + nomeFonte: v.string(), + tipo: threatIntelTipo, + formato: threatIntelFormato, + url: v.optional(v.string()), + ativo: v.boolean(), + prioridade: v.union( + v.literal("baixa"), + v.literal("media"), + v.literal("alta"), + v.literal("critica") + ), + ultimaSincronizacao: v.optional(v.number()), + entradasProcessadas: v.optional(v.number()), + errosConsecutivos: v.optional(v.number()), + autenticacaoNecessaria: v.optional(v.boolean()), + configuracao: v.optional( + v.object({ + tokenId: v.optional(v.id("_storage")), + escopo: v.optional(v.string()), + }) + ), + criadoPor: v.id("usuarios"), + atualizadoPor: v.optional(v.id("usuarios")), + criadoEm: v.number(), + atualizadoEm: v.number(), + }) + .index("by_tipo", ["tipo"]) + .index("by_ativo", ["ativo"]) + .index("by_prioridade", ["prioridade"]), + + securityEvents: defineTable({ + referencia: v.string(), + timestamp: v.number(), + tipoAtaque: ataqueCiberneticoTipo, + severidade: severidadeSeguranca, + status: statusEventoSeguranca, + descricao: v.string(), + origemIp: v.optional(v.string()), + origemRegiao: v.optional(v.string()), + origemAsn: v.optional(v.string()), + destinoIp: v.optional(v.string()), + destinoPorta: v.optional(v.number()), + protocolo: v.optional(v.string()), + transporte: v.optional(v.string()), + sensorId: v.optional(v.id("networkSensors")), + detectadoPor: v.optional(v.string()), + mitreTechnique: 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()), + }) + ), + 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()), + }) + ), + 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()), + }) + ), + correlacoes: v.optional(v.array(v.id("securityEvents"))), + referenciasExternas: v.optional(v.array(v.string())), + tags: v.optional(v.array(v.string())), + criadoPor: v.optional(v.id("usuarios")), + atualizadoEm: v.number(), + }) + .index("by_referencia", ["referencia"]) + .index("by_timestamp", ["timestamp"]) + .index("by_tipo", ["tipoAtaque", "timestamp"]) + .index("by_severidade", ["severidade", "timestamp"]) + .index("by_status", ["status", "timestamp"]), + + incidentActions: defineTable({ + eventoId: v.id("securityEvents"), + tipo: acaoIncidenteTipo, + origem: v.union(v.literal("automatico"), v.literal("manual")), + status: acaoIncidenteStatus, + executadoPor: v.optional(v.id("usuarios")), + detalhes: v.optional(v.string()), + resultado: v.optional(v.string()), + relacionadoA: v.optional(v.id("ipReputation")), + criadoEm: v.number(), + atualizadoEm: v.number(), + }) + .index("by_evento", ["eventoId", "status"]) + .index("by_tipo", ["tipo", "status"]), + + reportRequests: defineTable({ + solicitanteId: v.id("usuarios"), + filtros: v.object({ + dataInicio: v.number(), + dataFim: v.number(), + severidades: v.optional(v.array(severidadeSeguranca)), + tiposAtaque: v.optional(v.array(ataqueCiberneticoTipo)), + incluirIndicadores: v.optional(v.boolean()), + incluirMetricas: v.optional(v.boolean()), + incluirAcoes: v.optional(v.boolean()), + }), + status: reportStatus, + resultadoId: v.optional(v.id("_storage")), + observacoes: v.optional(v.string()), + criadoEm: v.number(), + atualizadoEm: v.number(), + concluidoEm: v.optional(v.number()), + erro: v.optional(v.string()), + }) + .index("by_status", ["status"]) + .index("by_solicitante", ["solicitanteId", "status"]) + .index("by_criado_em", ["criadoEm"]), }); diff --git a/packages/backend/convex/security.ts b/packages/backend/convex/security.ts new file mode 100644 index 0000000..22750d4 --- /dev/null +++ b/packages/backend/convex/security.ts @@ -0,0 +1,1225 @@ +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; + } +}); +