Regras de correlação
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.
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:
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_secondsetimestamp_fieldsão ignorados. Agrupar porgroup_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:
| Aspecto | Batch | Inflight |
|---|---|---|
| Gatilho | Fim de busca federada | Cada evento no pipeline |
| Latência | Delay até próxima busca | Tempo real |
| Janela | Sim, window_seconds | Não, ignorado |
| Min. count | Sim, min_count | Não, ignorado (1 por evento) |
| Group by | Agrupa dentro da busca | Agrupa dentro do ciclo de coleta |
| Operadores | eq, ne, contains, gt/lt/gte/lte | +in, nin, exists |
| Uso típico | Investigação pós-coleta | Detecçã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:
| Operador | Significado | Exemplo | Notas |
|---|---|---|---|
eq | Igual | { "field": "event.type", "op": "eq", "value": "auth_failed" } | Padrão, pode omitir op. Compara strings; case-sensitive. |
ne | Diferente | { "field": "user.name", "op": "ne", "value": "admin" } | Vacuidade: campo ausente ≠ admin. Auto-injeta exists=true. |
contains | Contém (substring) | { "field": "message", "op": "contains", "value": "error" } | Busca a string dentro da string do evento. |
gt, lt, gte, lte | Maior, 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 raw → user → name. 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:
- Agrupa os eventos do resultado por um campo (ex.:
source.ip, usando notação de caminho com ponto). - Filtra (opcional) — apenas eventos que atendem aos critérios da cláusula
where. - Conta eventos dentro de uma janela de tempo (ex.: últimos 5 minutos), usando o campo de timestamp configurado.
- Dispara — quando a contagem ≥
min_count, emite uma Detecção. - Suprime — não dispara novamente pela mesma chave de agrupamento durante a janela de supressão (ex.: 1 hora).
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:
| Campo | Valor |
|---|---|
| Nome | Múltiplas falhas de login do mesmo IP |
| group_by_field | source.ip |
| min_count | 5 |
| window_seconds | 300 (5 minutos) |
| timestamp_field | event.timestamp (obrigatório para a janela valer) |
| where | [{field: "event.category", op: "eq", value: "authentication"}] |
| suppression_window_seconds | 3600 (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" }
]
}
- Cada perna é avaliada como uma regra comum no ciclo de coleta da fonte dela (o
streamda perna vira uma cláusula a mais). O caminho por evento continua puro: nada de estado nem I/O ali. - 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.
- 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. - 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.
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.
- Cada evento é avaliado pelo matcher como qualquer regra em voo; nada de estado nem I/O ali.
- 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.
- 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 comDETECTION_LIFECYCLE_EVENTS). - 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 emabsence_unobservable. Quando owherefixa_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.
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ção | Permissão | Quem tem |
|---|---|---|
| Ver lista de regras | query.run | Viewer, Operator, Engineer, Admin |
| Criar / Editar / Excluir regra | query.save | Engineer, 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).
| Coluna | O que mostra |
|---|---|
| Nome | Identificador da regra (ex.: "Múltiplas falhas de login"). |
| Status | Kill 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 / Prioridade | Severidade da Detecção e eval_priority (ordem de avaliação em voo). |
| Agrupar por | Campo de agrupamento; numa sequência, o selo sequência · N pernas. |
| Agendamento / SIEM | Cadência da busca própria (lote) e se a Detection sai como evento (voo). |
| Mín. eventos / Janela | Contagem e janela em segundos. |
| Ações | Mé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:
- 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.
- 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. - 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 operadoresem,não emeexistesó 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.nameno Okta,normalized.actor.user.nameno 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
- Clique em + Nova regra.
- Dê nome à regra e escolha o modo (e, em voo, o tipo).
- Percorra o fluxo: fonte, filtros, agrupamento, janela, detecção. Use Testar contra amostras antes de salvar.
- 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
- Clique em Editar na linha da regra. O editor abre com o fluxo dela já montado.
- 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, ouin/ninsem array, ou campofieldausente.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
wheresuportam 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_priorityda regra (ordem:eval_prioritydecrescente,idcrescente como desempate). Toda regra nasce comeval_priority = 0, então sem nenhuma prioridade definida o corte fica sendo o histórico — as de menorid, isto é, as mais antigas — e a regra que você acabou de escrever é a primeira a ficar de fora. Suba aeval_prioritydela para fixá-la — no campo Prioridade de avaliação da seção Detecção do editor, ou peloPATCHda regra na API. O worker emite umwarninga 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_jsonde 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_CLAUSESnã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.envfaz 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 quegroup_by_fieldtem cardinalidade muito alta (ex.: um ID único por evento). Ajuste a regra ou revise o agrupamento.
- Muitas regras? Suba a
eval_prioritydas que precisam rodar (campo Prioridade de avaliação do editor, ouPATCHna 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 poruser.ide tem milhares de usuários por ciclo, cada um gera uma chave diferente. Considere agrupar poruser.departmentou 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
-
Abra Detecta → Correlação.
-
Clique em + Nova regra.
-
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
-
Clique Salvar.
-
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
- Abra Detecta → Detecções.
- Filtre por status "open".
- Clique em uma Detecção com
source = "correlation". - No detalhe, veja a regra que a gerou e os eventos que acionaram.
- Mude o status para "acknowledged" (investigando) ou "closed" (resolvido/falso positivo).
Ajustar uma regra que gera muitos falsos positivos
- Abra Detecta → Correlação.
- Clique em editar na regra problemática.
- 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.
- Aumente
min_count(ex.: de 5 para 10). - Ou reduza
window_secondspara exigir uma concentração maior em menos tempo. - Ou adicione um filtro mais específico na cláusula
where. - 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.