SentrySentry
Rules Engine

DSL de Match

Referência completa da mini-linguagem declarativa das expressões de match

DSL de Match

A DSL de match é uma mini-linguagem declarativa usada no sentry.toml ([[rules.custom]] e [[policy.override]]) e na CLI (sentry rules add --match, sentry rules test). O parser é um recursive-descent que traduz o texto para a árvore RuleMatch avaliada pelo rules engine.

Gramática

expr     := or_expr
or_expr  := and_expr ('OR' and_expr)*
and_expr := not_expr ('AND' not_expr)*
not_expr := 'NOT' not_expr | primary
primary  := '(' expr ')' | atom
atom     := key op value
  • Operadores lógicos são palavras em MAIÚSCULAS: AND, OR, NOT. O tokenizer só reconhece a forma uppercase — and vira parte de um valor e vira erro de parse.
  • NOT tem a maior precedência, depois AND, depois OR. Parênteses agrupam e sobrepõem a precedência.
  • Valores podem ser palavras nuas (terminam em espaço, parêntese ou aspas) ou strings entre aspas ("...") — obrigatório para padrões com espaço, regex ou =.

Condições

ChaveVariação de RuleMatchO que casa
ipIpIP literal, CIDR (10.0.0.0/8) ou range (10.0.0.1-10.0.0.99). v4 e v6.
asnAsnNúmero do AS (u32). Só casa quando o evento tem ASN (enrichment geo).
countryCountryCódigo ISO 3166-1 alpha-2, comparação case-insensitive.
pathPathPath do request URL-decodificado (ver operadores abaixo).
methodMethodGET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, CONNECT, TRACE. Método desconhecido vira Other.
statusStatusCódigo HTTP exato. Só casa quando o status é conhecido (na edge inline o evento é publicado depois da resposta).
queryQueryQuery string inteira, com operadores de string.
header.<nome>HeaderHeader por nome (case-insensitive) + operador de string. Todo token http_* do log_format nginx vira header (ex.: header.cf-connecting-ip).
ua (aliases user_agent, useragent)UserAgentUser-Agent + operador de string.
bodyBodyCorpo da requisição quando capturado (fontes inline).
upload_filenameUploadFilename(F10) dispara quando qualquer parte multipart declara um filename que casa com o operador.
protocolProtocolhttp, http3, tcp, udp, tls, other (parse case-insensitive).
tls_ja3TlsFingerprintHash JA3 (case-insensitive). Só em eventos de handshake TLS (edge inline).
tls_ja4TlsFingerprintHash JA4 (case-insensitive).
tls_sniTlsSniSNI do ClientHello + operador de string. SNI ausente nunca casa (a edge sinaliza TlsSniMismatch à parte).
reputationReputationTier de reputação do IP (valores abaixo).
bot_verifiedBotVerifiedResultado da verificação rDNS de bots (valores abaixo).
rateRateContador de eventos por janela e escopo (sintaxe abaixo).
timeTimeJanela de horário (ver ressalva abaixo).

Operadores de string

Aplicáveis a ua, query, body, upload_filename, tls_sni e header.<nome>:

OperadorEfeito
= valor / eq / equalsIgualdade exata.
contains valorSubstring.
starts_with valor / startswithPrefixo.
regex "<padrão>"Regex (Rust regex, linear-time). Regexes são compiladas 1× por processo e cacheadas; padrão inválido nunca casa.
in "a" "b" "c"Casa contra qualquer valor da lista.

Forma inline chave=valor (sem operador) equivale a equals.

Operadores de path

FormaEfeito
path=/admin/*Se contém * → glob (* = qualquer sequência, ? = 1 char; case-insensitive); senão igualdade exata.
path regex "\.env"Regex sobre o path decodificado (use (?i) para ignorar caixa).
path starts_with /admin (ou startswith, prefix)Prefixo.
path contains wp-loginAçúcar para regex com o valor escapado.

O path é URL-decodificado antes do match (%2e%2e%2f = ../, + = espaço), então encoding não burla regras. O decode acontece 1× por avaliação, compartilhado entre todas as condições path.

Valores de reputation

unknown, clean, suspicious, malicious, datacenter, vpn (aliases proxy, vpn_proxy), tor, authorized, promiscuous.

  • authorized — scanner contratado pela organização (ex.: fornecedor de pentest); não gera sinal — combine com uma regra allow.
  • promiscuous — scanner que publica os dados de recon para qualquer um; cada hit rende um pequeno sinal PromiscuousScanner (peso 10).

Os tiers vêm das reputation feeds ([[rules.feeds]] kind = "ip" com tier = "...").

Valores de bot_verified

ValorCasa quando…
true / verifiedrDNS confirmado (BotStatus::Verified).
false / spoofedUA alega crawler mas o forward-resolve falhou (Spoofed).
engineVerificado como aquele engine: google, bing, yahoo, baidu, yandex.

Pendente/desconhecido nunca casa — verificação só existe com [bot_verification] habilitado (opt-in).

rate

rate <count>/<janela> [escopo]
  • Janela: 60s, 5m, 1h (default 60s).
  • Escopo: per_ip (default), per_asn, per_path, global.
  • Requer o rate-limit backend do daemon (InMemoryRateLimiter ou Redis) — sem backend, a condição nunca casa. O bucket inclui os parâmetros da condição, então duas regras com thresholds iguais compartilham contador e thresholds diferentes ficam independentes.
  • per_asn sem ASN no evento e per_path fora de HTTP nunca casam.

time

time inside(09:00-18:00 America/Sao_Paulo)
time outside(09:00-18:00 America/Sao_Paulo)

A sintaxe parseia mas a avaliação ainda não implementa janelas de horário — a condição nunca casa hoje (reservada para evolução futura). Não dependa dela para enforcement.

Datasets JA3 (ja3 in)

A membership em conjuntos de fingerprints JA3 (RuleMatch::Ja3In) não é expressa na DSL textual: ela nasce de datasets ([[rules.feeds]] kind = "ja3" ou DB-backed via sentry datasets import --kind ja3), que compilam a lista inteira numa única regra. Ver Default Rule Packs.

Erros de parse

O parser retorna DslError: token inesperado, valor faltando, parêntese sem fechar, número/IP inválido, chave desconhecida ou expressão vazia. Em [[rules.custom]] um erro derruba só a regra culpada (o nome aparece na mensagem); em [[policy.override]] o erro falha o carregamento do config.

Exemplos por categoria

[[rules.custom]]
name = "allow internal monitoring"
priority = 1
match = 'ip=10.0.0.0/8'
action = "allow"
log_level = "silent"

[[rules.custom]]
name = "block admin from RU"
priority = 10
match = 'country=RU AND path=/admin/*'
action = "block"

[[rules.custom]]
name = "block scanner UA on login"
priority = 12
match = 'ua regex "(?i)(sqlmap|nikto|nuclei)" AND path starts_with /login'
action = "block"

[[rules.custom]]
name = "block spoofed Googlebot"
priority = 12
match = 'bot_verified = "false"'
action = "block"

[[rules.custom]]
name = "flag odd cf-connecting-ip outside US"
priority = 20
match = 'header.cf-connecting-ip contains "1.2.3.4" AND NOT country=US'
action = "challenge"

[[rules.custom]]
name = "block double-extension uploads"
priority = 15
match = 'upload_filename contains ".php.jpg"'
action = "block"

[[rules.custom]]
name = "block known scanner JA3"
priority = 18
match = 'tls_ja4 = "t13d1516h2_8daaf6152771_e5627efa2ab1"'
action = "block"

[[rules.custom]]
name = "rate-limit 404 storms per IP"
priority = 15
match = 'status=404 AND rate 10/60s per_ip'
action = "rate_limit"

Uso na CLI

sentry rules add --name "block admin from RU" \
    --match 'country=RU AND path=/admin/*' --action block --priority 10

sentry rules test --path /admin --method GET --ip 1.2.3.4 --ua "sqlmap/1.0"

sentry rules test avalia a expressão contra um evento hipotético (regras de rate sem backend e time nunca casam na simulação) — valide antes de promover para enforce.

On this page