Camada de IA
Forks assíncronos: modelo ONNX local, LLM opcional e enriquecimento externo
Camada de IA
O caminho quente do Sentry (regras → heurísticas → rotas → scan → score →
policy → escalonamento) é síncrono e rápido (µs por evento). A IA nunca
está nesse caminho: cada estágio de IA roda como um fork ao lado do
pipeline e alimenta o resultado de volta por
Pipeline::rescore_from,
que só pode elevar o score — nunca rebaixar. Um evento limpo no hot path
continua limpo, custe o que custar a IA.
O contrato rescore_from
Todo estágio de IA reentra no pipeline pela mesma porta:
- Os sinais extras somam peso ao score existente (com saturação em 100) — nenhum fork subtrai risco.
- O verdict é re-derivado do novo nível (Medium → RateLimit, High → Challenge, Critical → Block).
- IPs da lista never-ban (
[real_ip]trusted_ips/trusted_lists) nunca são elevados, não importa o que o fork alegue. - Se o verdict não muda, o fork termina em silêncio. Se muda, o evento é atualizado no Postgres e as actions aplicáveis são re-dispatchadas (webhook, Cloudflare, blocklist etc.).
Modos: fork | inline | shadow
| Modo | Comportamento |
|---|---|
fork (default) | Assíncrono e não-bloqueante; concorrência limitada por semáforo (concurrency). |
inline (só [ai]) | Avaliado antes da persistência/actions; o hot path espera o modelo. |
shadow | Avalia, calcula o re-score e apenas loga would change verdict — nunca atualiza o evento nem dispara actions. Zero efeito de enforcement; ideal para medir precisão antes de ligar o modo de verdade. |
Mecânica compartilhada dos forks
Os três forks (AiFork,
LlmFork
e IpLookupFork)
compartilham a mesma espinha:
- Gatilho (trigger) — cada fork decide se roda olhando o resultado do hot
path. IA local:
trigger = "above_score"(default,min_score = 20),"always"ou"quarantine_only". LLM: score ≥only_above(30) ou verdict Quarantine (controle de custo). Lookup externo: banda cinza — score ≥trigger_above(25) com verdict ≠ Block, ou o evento carrega algum sinal deon_signals. - Semáforo de concorrência —
[ai] concurrency = 4,[llm] concurrency = 4; o lookup externo usa semáforo fixo de 2. Chamadas além do limite ficam na fila, nunca derrubam o throughput do hot path. - Cache por hash do payload — a chave é o hash de
client_ip + path + query + user_agent(payload_hash), com TTLcache_ttl_secs(300 s em[ai]e[llm]); o lookup externo cacheia por IP (cache_ttl_secs, 86400 s). Erros de rede/API não são cacheados — a próxima chamada retenta. - Observabilidade — duração por fork em
sentry_fork_rescore_duration_seconds{fork="ai"|"llm"|"ip_lookup"}.
Os quatro estágios de IA
| Estágio | Config | Gatilho | Sinal emitido | Peso |
|---|---|---|---|---|
| Modelo ONNX local | [ai] | score ≥ min_score (20) | AnomalousPayload | signal_weight = 25 (override [scorer.weights] anomalous_payload) |
| LLM remoto | [llm] | score ≥ only_above (30) ou Quarantine | LlmMalicious | risk_score × confidence |
| Lookup externo | [ip_lookup] | banda cinza ou on_signals | ExternalReputation | escala com o score do provedor (máx. 40) |
| Bots verificados | [bot_verification] | UA alega crawler | SpoofedBot (só se falsificado) | 35 |
Os dois primeiros são cobertos em Modelo ONNX local e LLM Provider. Os outros dois vêm abaixo.
Lookup externo de reputação — [ip_lookup]
Complementa as feeds pull-based: em vez de importar uma blocklist inteira, um
IP individual é consultado num provedor quando o pipeline cai na banda
cinza — score elevado, mas ainda sem veredito acionável — ou quando o
evento carrega um sinal suspeito configurado. A resposta reentra por
rescore_from (que só eleva).
- Contrato: trait
IpLookupProvider(check(ip) -> IpLookupResult); implementaçãoAbuseIpDbLookupcontra o AbuseIPDBGET /api/v2/check(janela de 30 dias, campoabuseConfidenceScore). - O sinal
ExternalReputationescala com a confiança do provedor: 25% ≈ +10 de peso, 100% ≈ +40. - Roda depois dos forks AI/LLM — um score elevado pós-LLM conta para a banda de gatilho. IPs já em Block não são consultados (o lookup só eleva; gastaria quota à toa).
- Proteções: cache por IP com TTL (default 86400 s) + cota
max_per_hour(500/h default — folga para o free tier do AbuseIPDB, 1000/dia). - Chave da API via env, nunca no TOML:
key_env(defaultSENTRY_ABUSEIPDB_KEY).
[ip_lookup]
enabled = true
provider = "abuseipdb"
key_env = "SENTRY_ABUSEIPDB_KEY"
trigger_above = 25 # banda cinza: score local a partir daqui
on_signals = ["sensitive_path"] # alarga o gatilho p/ portadores destes sinais
cache_ttl_secs = 86400
max_per_hour = 500
timeout_secs = 10Chaves e defaults em IpLookupConfig;
wiring do fork em
build_ip_lookup_fork.
Bots verificados via rDNS — [bot_verification]
Um User-Agent alegando ser crawler é trivial de falsificar. O Sentry valida a
alegação do mesmo jeito que os engines recomendam (botverify.rs):
- O UA contém o padrão de um engine verificável —
claimed_engine: Googlebot, bingbot, Slurp, Baiduspider ou YandexBot. - O PTR do IP do cliente termina em um dos domínios do engine
(
googlebot.com,search.msn.com, …). - O hostname do PTR resolve de volta ao IP original — mata spoof de PTR.
O resultado BotStatus
fica em cache por IP: Verified(engine) (TTL cache_ttl_secs, 3600 s),
Spoofed (TTL curto failed_ttl_secs, 600 s — bots se recuperam rápido de
instabilidades) ou Unknown.
- Verified → bypass do JS challenge da edge (bots de busca não executam
JavaScript): o
challenge_gateconsulta o verificador e libera com o contadorbot_bypass(with_bot_verifier). - Spoofed → sinal
SpoofedBotcom peso 35 (SPOOFED_BOT_WEIGHT; override via[scorer.weights] spoofed_bot). - Unknown → trata-se como não-verificado, nunca como spoofed: um erro de resolução DNS é distinto de PTR vazio, então uma outage de DNS não marca um bot genuíno como falso.
O core não faz I/O: o DNS é injetado pela trait BotDnsResolver (o daemon
usa hickory via HickoryDns).
Cache miss marca o evento como Unknown e coloca o IP na fila
do BotVerifier
— um worker em background drena até batch_size (16) verificações por ciclo,
fora do hot path.
[bot_verification]
enabled = true
cache_ttl_secs = 3600 # TTL do resultado verificado (por IP)
failed_ttl_secs = 600 # TTL do resultado spoofed (curto)
timeout_ms = 2000 # timeout de uma rodada PTR+A
batch_size = 16 # verificações por ciclo do workerChaves e defaults em BotVerificationConfig;
wiring no daemon em
daemon.rs.
Inspecionar um IP sem o daemon (usa o resolver do sistema):
sentry bots check 66.249.66.1 --ua "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"