Saltar al contenido principal

Regras de correlação

Beta — avaliação sob demanda, não contínua

As regras de correlação estão em Beta. O motor não observa o pipeline de ingestão nem roda em agendamento próprio: uma regra só é avaliada ao final de uma busca federada (um QueryJob concluído), sobre os resultados daquela busca. Enquanto ninguém executar uma busca federada, nenhuma regra roda e nenhuma Detecção de correlação é criada. Não use estas regras como sua única cobertura de detecção.

Uma regra de correlação examina os resultados de uma busca federada — que podem vir de múltiplas fontes ao mesmo tempo — e abre uma Detecção (um alerta de análise) quando um padrão específico aparece naquele conjunto de resultados. Por exemplo: "se o mesmo IP falhar em fazer login 5 vezes em 5 minutos dentro dos resultados desta busca, abrir uma detecção".

Diferente dos eventos coletados das fontes (que você pesquisa pela busca de eventos), as Detecções vêm de análise: correlação, busca em tempo real ou agendamentos. Você gerencia aqui regras de tipo threshold — o tipo mais comum. Tipos mais avançados (sequência de eventos, agregações) estão no nosso roadmap.

Para acessar, use o menu Detecta → Correlação.

Quem pode ver: todos os perfis com acesso de leitura ou superior. Apenas Engineer e acima conseguem criar, editar ou excluir regras.

Quando usar

  • Materializar um padrão que você acabou de buscar. ≥5 tentativas de login falhadas da mesma origem em poucos minutos? Pode ser brute-force. Ao rodar a busca federada, a regra avalia o resultado e vira uma Detecção que você triagem na tela de Detecções.
  • Reduzir ruído. Em vez de alertar cada evento isolado (cada tentativa falhada é um alerta fraco), agrupe e alerte só quando o padrão fica suspeito.
  • Correlação sem query. Não precisa saber linguagem de busca federada. Preencha um formulário simples: campo para agrupar, limite mínimo, janela de tempo, filtros simples.

Modos de avaliação: batch vs inflight

Uma regra pode rodar em dois modos distintos, com diferenças críticas de latência e semântica.

Modo batch (padrão)

Você define a regra com eval_mode='batch'. Uma regra habilitada é avaliada somente quando uma busca federada termina (status finished ou partial), e somente sobre os resultados daquela busca. Não existe gatilho contínuo:

  • O pipeline de coleta não avalia regras de correlação. Eventos ingeridos não passam pelo motor.
  • Não há agendamento próprio (nenhuma entrada de scheduler cria buscas para alimentar as regras).
  • Rodar uma busca federada é a única forma de disparar a avaliação — via Detecta → Busca federada.

Consequência prática: o alcance temporal da sua regra é o alcance da busca. Uma regra de "10 falhas em 5 minutos" não vê nada que esteja fora do intervalo e dos filtros da busca que a acionou.

Beta — avaliação sob demanda, não contínua

Batch está em Beta pelo motivo acima: você precisa executar uma busca federada para a regra rodar. Não é monitoramento contínuo.

Modo inflight (avaliação em voo)

Você define a regra com eval_mode='inflight'. A regra é avaliada por evento, no pipeline de ingestão, antes do dado chegar ao SIEM. Uma Detection é emitida imediatamente quando o evento bate na regra — sem esperar por busca federada.

Vantagens: latência em tempo real, não depende de buscas manuais.

Limitações importantes:

Em voo, todo caminho parte da RAIZ do envelope

O motor em voo resolve group_by_field e o field de cada cláusula a partir da raiz do envelope, que tem exatamente três chaves: _centralops, normalized e raw. Um caminho que comece em qualquer outra coisa — source.ip, user.name, severity_id — resolve vazio em todo evento.

Desde a v2.9, uma regra em voo com group_by_field fora dessas raízes é recusada na compilação (group_by_root) e aparece com o selo Não avaliada na lista, em vez de contar match e nunca disparar.

No modo em lote é diferente: lá o motor roda sobre o resultado de uma busca federada, e os caminhos são os do documento retornado pelo SIEM.

  • Sem janela de tempo. Um inflight roda sobre um único evento por vez, logo window_seconds e timestamp_field são ignorados. Agrupar por group_by_field (ex.: normalized.src_endpoint.ip) tira vários eventos do mesmo IP no mesmo ciclo de coleta, mas não numa janela temporal no sentido de "últimas 5 minutos" — é "neste ciclo de coleta".
  • Sem contagem de threshold. Não existe min_count. Cada evento casado gera uma Detection (ou é descartado por supressão de dedup).
  • Operadores diferentes. Inflight suporta três operadores extras: in, nin, exists. Batch não tem esses.
  • Cardinalidade limitada. O máximo de chaves de dedup por regra por ciclo é 50. Se uma regra gera mais de 50 variações (ex.: 100 IPs diferentes num ciclo), os matches seguem contados mas nenhuma Detection nova é criada após a 50ª chave.

Resumo:

AspectoBatchInflight
GatilhoFim de busca federadaCada evento no pipeline
LatênciaDelay até próxima buscaTempo real
JanelaSim, window_secondsNão, ignorado
Min. countSim, min_countNão, ignorado (1 por evento)
Group byAgrupa dentro da buscaAgrupa dentro do ciclo de coleta
Operadoreseq, ne, contains, gt/lt/gte/lte+in, nin, exists
Uso típicoInvestigação pós-coletaDetecção urgente em tempo real

Autoria de regras — operadores e gotchas

Operadores de filtro

O campo where_json aceita um array de cláusulas, cada uma com formato:

{ "field": "nome.do.campo", "op": "operador", "value": valor }

Operadores suportados:

OperadorSignificadoExemploNotas
eqIgual{ "field": "event.type", "op": "eq", "value": "auth_failed" }Padrão, pode omitir op. Compara strings; case-sensitive.
neDiferente{ "field": "user.name", "op": "ne", "value": "admin" }Vacuidade: campo ausente ≠ admin. Auto-injeta exists=true.
containsContém (substring){ "field": "message", "op": "contains", "value": "error" }Busca a string dentro da string do evento.
gt, lt, gte, lteMaior, menor, etc.{ "field": "severity", "op": "gte", "value": 3 }Coagem lado esquerdo a float. "5" vs 3 funciona.
in (inflight)Está em lista{ "field": "normalized.src_endpoint.ip", "op": "in", "value": ["10.0.0.1", "10.0.0.2"] }JSON array, não CSV string. Rejeita "10.0.0.1,10.0.0.2".
nin (inflight)Não está em lista{ "field": "normalized.src_endpoint.ip", "op": "nin", "value": ["10.0.0.1"] }Vacuidade + allowlist. Auto-injeta exists=true.
exists (inflight)Campo existe{ "field": "user.id", "op": "exists", "value": true }True/false. Raro usar manualmente (auto-injetado).

Gotchas e armadilhas

in / nin exigem LISTA JSON

Errado:

{ "field": "normalized.src_endpoint.ip", "op": "in", "value": "10.0.0.1,10.0.0.2" }

Isto é rejeitado na compilação (bad_json). Se você quer múltiplos valores, use um array:

{ "field": "normalized.src_endpoint.ip", "op": "in", "value": ["10.0.0.1", "10.0.0.2"] }

Operadores negativos (ne, nin) casam por vacuidade — mas é seguro

Se um evento não tem o campo user.name, tanto ne quanto nin o deixam passar. Sem proteção, uma regra "não-admin" dispararia sobre eventos sem ID. O compilador fecha isto automaticamente injetando exists=true — você não vê nem precisa escrever manualmente. É transparente.

Comparação numérica é automática, mas case-sensitive

{ "field": "severity", "op": "gte", "value": 4 }

Se o evento tem "severity": "5" (string), isto funciona — o motor coage "5" a 5.0 antes de comparar. Mas:

{ "field": "event_id", "op": "eq", "value": 1 }

Se o evento tem "event_id": "1" (string), isto não funciona — eq compara strings e "1"1. Use "1" como valor.

Caminhos de campo usando ponto

{ "field": "raw.user.name", "op": "eq", "value": "alice" }

Navega rawusername. Se em algum nível não for dict, resolve para None (e eq não casa).

Arrays não são navegados

Se raw.events é uma lista, um path como raw.events.0.type não funciona — resolve para None. O motor não sabe entrar em arrays. Agrupe por um campo de topo, não por dentro de listas.

Como funciona: tipo threshold

Ao final de uma busca federada, a regra threshold segue este fluxo sobre os resultados retornados:

  1. Agrupa os eventos do resultado por um campo (ex.: source.ip, usando notação de caminho com ponto).
  2. Filtra (opcional) — apenas eventos que atendem aos critérios da cláusula where.
  3. Conta eventos dentro de uma janela de tempo (ex.: últimos 5 minutos), usando o campo de timestamp configurado.
  4. Dispara — quando a contagem ≥ min_count, emite uma Detecção.
  5. Suprime — não dispara novamente pela mesma chave de agrupamento durante a janela de supressão (ex.: 1 hora).
A janela só existe se houver campo de timestamp

A janela de tempo é aplicada apenas quando window_seconds > 0 e timestamp_field está preenchido. Se você deixar timestamp_field vazio, a janela é desligada e a regra passa a contar todos os eventos do grupo presentes no resultado da busca, sem qualquer recorte temporal — o que costuma gerar falso positivo. Não existe fallback para "timestamp de ingestão".

Se o campo estiver preenchido mas os valores forem inválidos ou ausentes nos eventos, o comportamento é fail-closed: a contagem vai a zero e a regra não dispara (em vez de virar silenciosamente "N em qualquer tempo"). Sempre preencha timestamp_field com um campo que exista de fato nos eventos daquela fonte.

Exemplo prático:

CampoValor
NomeMúltiplas falhas de login do mesmo IP
group_by_fieldsource.ip
min_count5
window_seconds300 (5 minutos)
timestamp_fieldevent.timestamp (obrigatório para a janela valer)
where[{field: "event.category", op: "eq", value: "authentication"}]
suppression_window_seconds3600 (1 hora)

Resultado: quando uma busca federada terminar, se o resultado contiver 5 ou mais eventos de autenticação do mesmo IP dentro de uma janela de 5 minutos, uma Detecção é aberta. A mesma regra não dispara novamente para aquele IP pelos próximos 60 minutos, mesmo que outras buscas sejam executadas.

Como funciona: tipo sequence (entre fontes, em voo)

Uma regra rule_type='sequence' com eval_mode='inflight' junta eventos de fontes diferentes pela mesma entidade dentro de uma janela: por exemplo, uma falha de MFA no Okta e um processo suspeito no Sophos para o mesmo usuário em 15 minutos. Cada fonte é uma perna, e cada perna diz onde está a chave de junção nela — é isso que resolve o fato de o Okta escrever normalized.user.name e o Sophos escrever normalized.actor.user.name.

{
"rule_type": "sequence",
"eval_mode": "inflight",
"window_seconds": 900,
"legs_json": [
{ "label": "okta_mfa_fail", "stream": "okta.system_log",
"where": [ { "field": "normalized.class_uid", "op": "eq", "value": 3002 },
{ "field": "normalized.status_id", "op": "eq", "value": 2 } ],
"join_path": "normalized.user.name" },
{ "label": "sophos_process", "stream": "sophos.siem_event",
"where": [ { "field": "normalized.class_uid", "op": "eq", "value": 2004 } ],
"join_path": "normalized.actor.user.name" }
]
}
  1. Cada perna é avaliada como uma regra comum no ciclo de coleta da fonte dela (o stream da perna vira uma cláusula a mais). O caminho por evento continua puro: nada de estado nem I/O ali.
  2. No fim do ciclo, cada perna casada é anotada no Redis sob a chave (regra, valor de junção), com um ponteiro para o evento e TTL igual à janela. A perna do Sophos pode chegar num ciclo, num worker e numa integração diferentes da perna do Okta.
  3. Dispara quando todas as pernas foram vistas para o mesmo valor dentro da janela. A Detecção nasce com um ponteiro por perna (unmapped.legs[] no evento 2004): id do evento, stream, plataforma e instante de cada um. Nenhum payload de cliente viaja.
  4. Fecha o estado: a próxima Detecção da mesma entidade exige pernas novas.

Limites: mínimo de 2 e máximo de INFLIGHT_MAX_LEGS (4) pernas; a janela é obrigatória e respeita INFLIGHT_MAX_WINDOW_SECONDS; sem Redis a regra não dispara (fail-closed, contado em sequence_unavailable); uma chave que ainda não fechou aparece em sequence_below. A v1 é um conjunto: a ordem entre as pernas não é exigida.

Disponibilidade

O motor está no Core. No console Enterprise, a sequência é montada no editor de regras (tipo Sequência entre fontes, uma pista por perna); a API expõe rule_type e legs.

Como funciona: tipo absence (ausência de evento, em voo)

Uma regra rule_type='absence' com eval_mode='inflight' alerta quando um evento que sempre chega deixa de chegar: o cliente que não criou o ponto de restauração de hoje, o host que parou de mandar heartbeat. Os dois motores anteriores só acordam quando um evento chega; a ausência precisa de um relógio, e o motor tem um.

{
"rule_type": "absence",
"eval_mode": "inflight",
"where": [
{ "field": "_centralops.event_type", "op": "eq", "value": "sophos.detection" },
{ "field": "normalized.metadata.event_code", "op": "eq", "value": "XDR-veeam-restorepointcreated" }
],
"group_by_field": "_centralops.customer_name",
"window_seconds": 93600,
"absence_forget_seconds": 604800,
"suppression_window_seconds": 21600
}

Os campos mudam de papel: where é o evento esperado, group_by_field é a chave vigiada (cada valor distinto é uma entidade que precisa aparecer), window_seconds é o prazo de silêncio tolerado (60 s a 7 dias — teto próprio, não os 3600 s da janela deslizante) e absence_forget_seconds diz depois de quanto silêncio a chave deixa de ser esperada (vazio = 3 × prazo). suppression_window_seconds é a cadência com que a mesma chave calada é reafirmada.

  1. Cada evento é avaliado pelo matcher como qualquer regra em voo; nada de estado nem I/O ali.
  2. No fim do ciclo, o flush grava no Redis o último avistamento de cada chave e um batimento do observador — mesmo num ciclo sem match. Presença nunca vira Detection.
  3. A cada minuto, um tique lê o hash de cada regra e decide: chave calada além do prazo (mais uma folga) vira Detection e sai como evento 2004 com finding_info.types = ["inflight", "absence"]; chave calada além do esquecimento sai da vigília sem alertar; chave que voltou fecha a própria Detection (e emite o 2004 de fechamento com DETECTION_LIFECYCLE_EVENTS).
  4. O tique só alerta quando prova que estava olhando. Batimento mais velho que ABSENCE_OBSERVER_MAX_AGE_SECONDS (15 min) ⇒ nenhum alerta para a regra, contado em absence_unobservable. Quando o where fixa _centralops.stream, o tique ainda confere o watermark do coletor: atraso além do prazo com teto atingido ⇒ regra segurada (absence_source_lagging). Redis fora em qualquer ponta ⇒ nenhum alerta.

A vigília é aprendida: uma chave passa a ser esperada ao ser vista pela primeira vez. Um Redis esvaziado zera a vigília sem alarme falso — a ausência nunca alerta sobre o que não aprendeu. Uma chave vista uma única vez e nunca mais alerta ao fim do prazo; para um host efêmero isso é ruído, e o esquecimento é o botão que separa os dois casos.

Limites: ABSENCE_MAX_KEYS_PER_RULE (5000) chaves vigiadas por regra (chaves novas além do teto são descartadas, absence_key_cap); ABSENCE_MAX_ALERTS_PER_TICK (200) Detections novas por tique, compartilhado entre as regras; a regra conta no teto de regras por ciclo como qualquer regra em voo. ABSENCE_AUTO_CLOSE=false desliga o fechamento automático.

Disponibilidade

O motor (presença no flush, decisão do tique) está no Core. O tique roda no beat do console Enterprise, que também expõe o tipo Ausência no editor de regras.

Permissões

AçãoPermissãoQuem tem
Ver lista de regrasquery.runViewer, Operator, Engineer, Admin
Criar / Editar / Excluir regraquery.saveEngineer, Admin

A tela

Lista de regras

Mostra todas as regras da sua organização. No topo, quatro contadores: total, habilitadas, em voo e não avaliadas (habilitadas que ficaram fora do teto por ciclo). A busca filtra por nome, descrição ou campo de agrupamento; ao lado dela, o filtro por modo (Todas / Em voo / Em lote).

ColunaO que mostra
NomeIdentificador da regra (ex.: "Múltiplas falhas de login").
StatusKill switch inline: habilitada (verde) ou desabilitada. O selo Não avaliada aparece na regra em voo que ficou fora do teto por ciclo ou cujo filtro não compila.
Severidade / PrioridadeSeveridade da Detecção e eval_priority (ordem de avaliação em voo).
Agrupar porCampo de agrupamento; numa sequência, o selo sequência · N pernas.
Agendamento / SIEMCadência da busca própria (lote) e se a Detection sai como evento (voo).
Mín. eventos / JanelaContagem e janela em segundos.
AçõesMétricas (contadores de 24 h), editar, remover.

O editor (Studio)

+ Nova regra e Editar abrem o mesmo editor, no lugar do catálogo. Ele tem três partes:

  1. Cabeçalho — nome, descrição, modo de avaliação (lote / voo) e, em voo, o tipo (limiar / sequência). Os dois modos ficam descritos lado a lado; o escolhido acende. Ao editar uma regra existente, trocar o modo mostra um aviso do que muda de semântica — avisa, não bloqueia.
  2. Fluxo — o grafo da regra, na ordem em que o motor roda: Fonte → Filtros → Agrupar → Janela → Detecção. Numa sequência, cada perna é uma pista (Fonte → Filtros → Junção) e todas convergem no nó Junção, seguido de Janela → Detecção; o nó tracejado Adicionar perna cria a próxima pista. Cada nó resume o que está configurado (ex.: 2 filtro(s), ≥ 5 em 300s), mostra a contagem de pendências quando o envio é barrado e, depois de um teste contra amostras, quantas casaram (12/50 casam). Clicar num nó leva à seção que o configura; as setas do teclado movem entre nós.
  3. Seções — uma por nó, sempre visíveis, na mesma ordem do fluxo. À direita, o painel Antes de salvar lista as pendências por seção (com atalho para cada uma) e os botões de salvar/cancelar.

O que cada seção configura:

  • Fonte — em lote, a busca que alimenta a regra: manual (só na busca federada) ou a cada N minutos com uma busca salva e a janela dela. Em voo, o stream de origem (_centralops.stream), escolhido entre os que a organização coleta; a escolha entra como o primeiro filtro e aparece também na lista de filtros — é uma verdade só, em duas vistas.
  • Filtros — as condições (campo · operador · valor). Os operadores em, não em e existe só existem em voo. Abaixo, Testar contra amostras roda a regra sobre uma amostra real do vendor e tipo escolhidos (pré-preenchidos pela fonte) e mostra, por cláusula, se o caminho resolveu e se o valor casou.
  • Agrupar — o campo de agrupamento: em lote, a chave da agregação; em voo, a chave de dedup da Detection. O seletor de campos vem do inventário da organização (GET /mappings/key-sources): cada sugestão diz se o caminho é mapeado, do catálogo OCSF ou um rótulo do envelope, e quais vendors o produzem; os caminhos do vendor da fonte escolhida sobem para o topo. Um caminho digitado que não consta no inventário recebe um aviso — a regra compila, mas pode nunca resolver.
  • Junção (sequência) — resume os caminhos de junção de cada perna e adiciona pernas. Em cada perna: rótulo, stream, o caminho de junção daquela fonte (ex.: normalized.user.name no Okta, normalized.actor.user.name no Sophos), os filtros e o teste contra amostras da própria perna.
  • Janela — mínimo de eventos e janela em segundos; em lote, o campo de timestamp (obrigatório sempre que houver janela). Em voo a janela vai de 10 s a 3600 s e usa o relógio de recepção.
  • Detecção — status, severidade OCSF, prioridade de avaliação, supressão e, em voo, o envio da Detection ao SIEM (evento OCSF 2004) e o teto de chaves por ciclo.

Criar uma regra

  1. Clique em + Nova regra.
  2. Dê nome à regra e escolha o modo (e, em voo, o tipo).
  3. Percorra o fluxo: fonte, filtros, agrupamento, janela, detecção. Use Testar contra amostras antes de salvar.
  4. Clique Criar regra. Se algo faltar, o nó correspondente acende e o painel Antes de salvar aponta a seção.

Em lote a regra passa a ser avaliada na próxima busca federada; em voo, a partir do próximo ciclo de coleta.

Editar uma regra

  1. Clique em Editar na linha da regra. O editor abre com o fluxo dela já montado.
  2. Modifique o que precisar e clique Salvar alterações.

Se a regra está habilitada, as mudanças valem a partir da próxima busca federada (lote) ou do próximo ciclo de coleta (voo) — nada é reavaliado retroativamente.

Ativar / Desativar

No botão de ações, escolha Ativar ou Desativar. Regras desativadas são ignoradas na avaliação.

Excluir uma regra

Clique no botão de ações e escolha Excluir. A confirmação é pedida antes de remover.

Detecções geradas

Quando uma regra dispara, é criada uma Detecção com:

  • Fonte: "correlation" (diferente de "scheduled_query" ou "live_query").
  • Severidade: a que você escolheu ao criar a regra.
  • Status: começa em "open" — você triagem em Detecções.
  • Chave de dedup: inclui a chave de agrupamento, garantindo que eventos do mesmo grupo não geram duplicatas enquanto a supressão estiver ativa.
  • Contagem (count): começa em 1 e é incrementada a cada nova avaliação da regra que reincide na mesma chave de dedup. Ela não reflete quantos eventos foram correlacionados — para isso, olhe os eventos no detalhe da Detecção. Veja Detecções.

Você vê as Detecções em Detecta → Detecções.

Diagnóstico: por que a regra não dispara

Sua regra está criada e habilitada, mas não gera Detections. Aqui estão as causas reais, em ordem de frequência.

1. Eval_mode errado

Você criou a regra em modo batch, mas esperava que ela rodasse em tempo real. Ou criou em inflight e esperava que ela rodasse ao fim da busca federada.

Diagnóstico: abra a regra. Confira o campo eval_mode (pode estar em abas ou settings). Mude conforme necessário.

  • Batch roda somente após uma busca federada terminar (você precisa executar uma busca manualmente).
  • Inflight roda no pipeline de coleta, por evento, em tempo real.

2. Regra desabilitada

O Status da regra é "Desativada" (cinza). Regras desativadas não são avaliadas.

Diagnóstico: na lista de regras, procure pelo ícone de status. Se estiver cinza, clique em ações → Ativar.

3. Modo batch: nenhuma busca executada

A regra está em batch, mas você nunca rodou uma busca federada que pudesse acioná-la.

Diagnóstico: vá em Detecta → Busca federada, execute uma busca que cubra as fontes e período de interesse, e aguarde conclusão. A regra é avaliada ao final.

4. Where_json não compila

A cláusula where tem um erro de sintaxe ou semântica que a torna inválida. A regra é rejeitada no boot.

Razões possíveis (procure no log de aplicação pela mensagem de rejeição):

  • bad_json: JSON malformado, ou operador numérico (gt/lt/gte/lte) com valor não-numérico, ou in/nin sem array, ou campo field ausente.
  • empty_where: array vazio ou sem nenhuma cláusula (em inflight, regra vazia dispararia 100% dos eventos).
  • unknown_op: operador não reconhecido (ex.: "op": "match", que não existe).
  • over_cap: mais de 10 cláusulas numa regra (limite inflight).

Diagnóstico: abra a regra, valide o JSON — use um validador de JSON online se necessário. Confira operadores: são apenas eq, ne, contains, gt, lt, gte, lte + in, nin, exists (inflight).

5. Modo batch: campo de timestamp vazio ou inválido

Você configurou uma janela (window_seconds > 0), mas deixou timestamp_field vazio ou apontando para um campo que não existe.

Diagnóstico: abra a regra. Confira timestamp_field — está preenchido? Se sim, ele existe de fato nos eventos daquela fonte? Execute uma investigação / busca federada para ver um evento de exemplo, verifique o caminho do campo.

Se o campo estiver realmente ausente ou vazio, a janela é desligada em silêncio e a regra passa a contar todos os eventos do grupo presentes no resultado da busca, sem recorte temporal — isto gera falso positivo, não falha de disparo, mas é um modo silencioso de "sua regra não faz o que você esperava".

6. Campo podado pelo mapeamento (raw_reduction)

A fonte é mapeada com um bloco raw_reduction que remove ou encurta o campo que sua regra tenta usar. Note que hoje o campo pode ter sido apagado por inteiro, não apenas truncado.

Diagnóstico: o bloco raw_reduction não é editável pelo editor visual de mapeamento — ele vive na definição JSON e é alterado por quem administra a plataforma. Peça a definição atual do mapeamento e verifique se o campo da sua regra cai em algum spec drop, keep_only, max_bytes ou drop_nulls. Se cair, ajuste o mapeamento ou reescreva a regra sobre um campo que sobrevive à poda. Veja Especificação da DSL.

7. Path atravessa array

Um campo do seu where está dentro de uma lista, ex.: raw.events.0.type.

Diagnóstico: confira a estrutura dos dados. Se o campo está dentro de um array (ex.: events é uma lista), o motor não sabe navegar dentro. Use um campo de topo, ou agrupe por um campo que não requer entrar na lista.

8. Group_by_field não resolve (inflight)

Modo inflight: você configurou group_by_field apontando para um campo que não existe ou está dentro de um array.

Diagnóstico: cada evento casado produz uma Detection usando group_by_field como chave. Se o campo não existe no evento, a Detection não é criada — em vez disso, você vê um erro group_by_unresolved nos logs. Verifique o campo, ou deixe em branco (NULL) se quer uma Detection por regra por ciclo.

9. Ausência: o observador estava parado

Uma regra de ausência habilitada que não alerta pode estar segurada de propósito: o último tique não encontrou o batimento do observador (worker parado, regra fora do teto por ciclo, Redis indisponível no flush) ou a fonte fixada estava com backlog. Confira o contador absence_unobservable / absence_source_lagging nas métricas da regra e o estado do último tique. Isso não é "a fonte está calada"; é "o motor não pôde afirmar nada".

Limites e roadmap

Limites gerais

  • Quota por organização: existe limite de regras por organização. Se receber erro 409 (Conflict), consulte o administrador.
  • Sem filtros complexos: os filtros where suportam operadores simples: eq, ne, contains, gt/lt/gte/lte. Para lógica avançada (OR, NOT, wildcards), use a Busca federada e analise manualmente.

Limites específicos do inflight

  • 50 regras por ciclo: máximo de regras inflight carregadas e avaliadas num ciclo de coleta. A carga ordena e corta no teto — sem offset, sem cursor e sem rotação. Não há ondas nem fila: o mesmo conjunto roda em todo ciclo e as excedentes nunca são avaliadas; esperar não resolve. Quem sobrevive ao corte é decisão sua, pelo campo eval_priority da regra (ordem: eval_priority decrescente, id crescente como desempate). Toda regra nasce com eval_priority = 0, então sem nenhuma prioridade definida o corte fica sendo o histórico — as de menor id, isto é, as mais antigas — e a regra que você acabou de escrever é a primeira a ficar de fora. Suba a eval_priority dela para fixá-la — no campo Prioridade de avaliação da seção Detecção do editor, ou pelo PATCH da regra na API. O worker emite um warning a cada ciclo truncado nomeando as regras cortadas, e a tela Detecta → Correlação marca a linha com o selo Não avaliada.
  • 10 cláusulas por regra: máximo de predicados no where_json de uma regra inflight. Uma regra com 11 cláusulas é rejeitada em compile-time.
  • 500 comparações por evento (orçamento validado no boot): o produto INFLIGHT_MAX_RULES_PER_CYCLE × INFLIGHT_MAX_WHERE_CLAUSES não pode passar de 500 — é o trabalho máximo do matcher no hot path. Não existe acumulador em tempo de execução: nenhuma regra é descartada por causa das cláusulas de outra, e o teto de cláusulas é medido regra a regra (ver o item acima). O guard é de boot — elevar os dois tetos além de 500 no .env faz a aplicação não subir, com o erro apontando qual reduzir.
  • 50 chaves de dedup por regra por ciclo: se uma regra gera mais de 50 variações (ex.: 100 IPs diferentes num ciclo usando group_by_field="normalized.src_endpoint.ip"), as primeiras 50 geram Detections, as demais não — mas todos os matches seguem sendo contados nos logs. Nenhum evento é perdido. Isto costuma indicar que group_by_field tem cardinalidade muito alta (ex.: um ID único por evento). Ajuste a regra ou revise o agrupamento.
O que fazer ao atingir tetos
  • Muitas regras? Suba a eval_priority das que precisam rodar (campo Prioridade de avaliação do editor, ou PATCH na API) — elas passam à frente no corte, e as demais continuam salvas e visíveis. Depois revise quais ainda fazem sentido e desabilite as menos importantes.
  • Muitas cláusulas? Simplifique a lógica do where — combine predicados ou use campos mais específicos.
  • Muitas chaves de dedup? Revise group_by_field — talvez esteja muito granular. Ex.: se agrupa por user.id e tem milhares de usuários por ciclo, cada um gera uma chave diferente. Considere agrupar por user.department ou deixar em branco (uma Detection por regra).

Roadmap

  • Batch: gatilho contínuo no pipeline (inflight já cobre isto). Agendamento próprio (sem precisar rodar busca manual).
  • Tipo: sequência ORDENADA (A → B → C; a v1 do tipo sequence é conjunto, sem ordem), agregações (soma, média), regras SQL custom.
  • Batch: sem timestamp_field: fallback automático para timestamp de ingestão (hoje a janela é desligada em silêncio).

Passo a passo

Criar uma regra para detectar brute-force

  1. Abra Detecta → Correlação.

  2. Clique em + Nova regra.

  3. Preencha:

    • Nome: "Brute-force SSH"
    • Descrição: "Múltiplas conexões SSH falhadas do mesmo IP"
    • Campo de agrupamento: normalized.src_endpoint.ip
    • Limite mínimo: 10
    • Janela de tempo: 300 (5 minutos)
    • Campo de timestamp: event.timestamp (sem ele a janela de 5 minutos não é aplicada)
    • Filtros: adicione event.action eq "ssh_login_failed"
    • Severidade: High
    • Supressão: 3600
  4. Clique Salvar.

  5. Abra Detecta → Busca federada e execute uma busca que cubra as fontes e o período de interesse.

Quando essa busca terminar, a regra é avaliada sobre os resultados dela: se houver 10 ou mais falhas de SSH do mesmo IP dentro de uma janela de 5 minutos, uma Detecção é criada. Repita a busca sempre que quiser reavaliar — a regra não roda sozinha.

Revisar e triagem de uma Detecção gerada

  1. Abra Detecta → Detecções.
  2. Filtre por status "open".
  3. Clique em uma Detecção com source = "correlation".
  4. No detalhe, veja a regra que a gerou e os eventos que acionaram.
  5. Mude o status para "acknowledged" (investigando) ou "closed" (resolvido/falso positivo).

Ajustar uma regra que gera muitos falsos positivos

  1. Abra Detecta → Correlação.
  2. Clique em editar na regra problemática.
  3. Confira primeiro o campo de timestamp. Se estiver vazio, a janela não está sendo aplicada e a regra conta todos os eventos do grupo no resultado da busca — essa é a causa mais comum de falso positivo. Preencha com um campo que exista nos eventos daquela fonte.
  4. Aumente min_count (ex.: de 5 para 10).
  5. Ou reduza window_seconds para exigir uma concentração maior em menos tempo.
  6. Ou adicione um filtro mais específico na cláusula where.
  7. Clique Salvar. As mudanças valem a partir da próxima busca federada.

Próximos passos

  • Quer triagem as Detecções geradas? Vá em Detecta → Detecções (Detecções).
  • Quer que suas regras rodem? Execute uma Detecta → Busca federada (Busca federada) — é o que dispara a avaliação das regras habilitadas.
  • Quer ver todos os eventos? Pesquise no destino que os recebeu. Veja onde pesquisar eventos.