Visão Geral de Segurança
Esta página descreve o modelo de segurança do EzyShield do ponto de vista do usuário. Para a análise de ameaças detalhada, veja o SECURITY-REVIEW interno (disponível no repositório ezy-shield).
Arquitetura
logs (SSH, Nginx)
↓
[ Collector ] — tail de arquivo, leitura do journald
↓
[ Parser ] — evento estruturado (IP, método, status)
↓
[ Rule Engine ] — scoring offline (roda sempre, sem rede)
↓
[ IA (opcional) ] — Anthropic/OpenAI/Ollama (só para eventos ambíguos)
↓
[ Decision Engine ] — decide ban/allow/defer
↓
[ Enforcer (com separação de privilégios) ] — aplica bans (nftables, Cloudflare)Princípio central: o daemon principal nunca detém privilégios elevados. Mutações de firewall só acontecem através de um binário separado, o ezyshield-enforcer, que detém CAP_NET_ADMIN.
Garantia de anti-lockout
O EzyShield tem uma regra dura: sua sessão SSH ativa e os CIDRs de admin nunca podem ser banidos, mesmo que casem com um padrão de ataque.
Antes de qualquer ban ser gravado no firewall, duas checagens independentes rodam em sequência:
- Checagem de allowlist: o IP alvo é comparado com a allowlist estática — sua
allowlistconfigurada, osadmin_cidrsdo policy.yaml e o peer SSH capturado quando o daemon iniciou. - Re-checagem de SSH ao vivo: o IP alvo é comparado com os peers SSH ativos agora — pela tabela de conexões do kernel (
/proc/net/tcpe/proc/net/tcp6, pontas remotas de conexões estabelecidas na(s) porta(s) do sshd — funciona sob systemd, sem depender de ambiente; portas lidas dosshd_config, fallback 22) e, em contextos interativos, pela variável de ambienteSSH_CLIENTatual.
Se qualquer uma das checagens casar, o ban é rejeitado.
Bans manuais (ezyshield ban) passam exatamente pelos mesmos guards — incluindo o rate limit de bans — e tentativas recusadas ficam registradas no audit log.
Isso é garantido em código, não por uma rule. Nenhum threshold mal configurado consegue te trancar fora.
A checagem roda duas vezes, em camadas independentes: o motor de decisão filtra primeiro, e um gate único na frente de todos os backends de enforcement re-checa cada ban e cada reconcile antes de chegar ao nftables ou a qualquer plataforma de edge. Mesmo um backend sem lógica própria de allowlist nunca recebe um endereço protegido — inclusive via um sync que o reintroduziria.
A re-checagem de SSH ao vivo protege uma conexão, não um endereço para sempre. Um bruteforcer que reconecta mais rápido do que a tabela de peers é relida mantém uma conexão estabelecida visível em toda avaliação, então cada tentativa dentro da janela do threshold é (corretamente) recusada. Para fechar a brecha em que uma rajada dessas nunca seria banida, um IP cujo ban foi recusado apenas por causa de uma conexão SSH ativa é reavaliado logo depois que essa conexão fecha — a partir do histórico de eventos ainda dentro da janela, pelos mesmos guards (allowlist, uma nova sondagem de peer SSH ao vivo, rate limit de bans). Se a conexão ainda estiver estabelecida no momento da reavaliação — uma sessão legítima do operador — a recusa simplesmente se repete e nada é banido. Um ban só pode resultar de uma checagem completa que não encontrou nenhuma conexão SSH estabelecida naquele instante; a sessão do operador continua imbanível enquanto estiver aberta.
Supremacia da allowlist
A allowlist é checada PRIMEIRO, antes de qualquer decisão do rule engine. Um IP na allowlist não pode ser banido por nenhuma rule, decisão de IA ou tentativa de ban manual.
allowlist:
- 10.0.0.0/8 # rede interna
- 198.51.100.7 # um host específico
admin_cidrs:
- 192.0.2.0/24 # faixas de admin, re-checadas antes de cada banLimite de taxa
Uma rule quebrada ou um feed envenenado não consegue banir a internet inteira. A trava max_bans_per_minute (default 30) rejeita bans excedentes com um erro explícito — nunca em silêncio, nunca descartando o limite. Bans de escalação que re-bloqueiam um IP cujo ban anterior terminou dentro da escalation_exempt_window (default 24h) ficam isentos dessa trava, para que um reincidente que já foi banido não volte a entrar enquanto o rate limit está saturado; bans de primeira ofensa sempre contam para a trava.
Resiliência da detecção
Detecção só serve se continuar rodando. Cada fonte de log (um tail de arquivo, um leitor de journalctl, um stream de container Docker) é vigiada por seu próprio supervisor. Se um collector sofre uma falha transitória — um arquivo de log momentaneamente ausente na inicialização, um logrotate cuja reabertura demora, o journald reiniciando e matando o journalctl — o supervisor reinicia esse collector automaticamente com backoff exponencial limitado, de modo que um contratempo operacional rotineiro não desative silenciosamente a detecção naquela fonte até o próximo restart do daemon.
O supervisor distingue uma falha real de um desligamento limpo: em SIGTERM/SIGINT (cancelamento de contexto) os collectors param e não são reiniciados, então o shutdown nunca entra em hot-loop. Um collector que falha repetidamente dispara uma notificação crítica nomeando a fonte, para que uma entrada permanentemente quebrada apareça para você em vez de tentar de novo em silêncio. As demais fontes de log continuam rodando o tempo todo — um collector com falha nunca derruba o pipeline.
Tratamento de segredos
Nenhum segredo aparece em:
- Arquivos de config (use a sintaxe
env:VAR_NAME) - Saída de log
- Mensagens de erro
- Prompts de IA
- Audit trail
Tokens de API são resolvidos uma única vez no startup e nunca impressos. Se um segredo for referenciado em um erro, o erro é reescrito para omiti-lo.
Segurança da IA
Quando a IA está habilitada para eventos ambíguos (scores dentro da ambiguous_band configurável):
- Validação de schema: a saída da IA é parseada em um tipo estruturado; respostas malformadas causam uma decisão de fallback.
- Clamp pela policy: a IA só pode sugerir dentro dos thresholds e durações de ban que você configurou. Ela não pode escalar além deles.
- Vínculo ao alvo: um veredito só pode nomear um IP que estava no lote analisado. Se o modelo alucinar (ou for comprometido) e nomear qualquer outro endereço, o veredito é descartado com um warning — a IA nunca escolhe o alvo do ban, apenas pontua IPs que os logs de fato observaram.
- Audit trail: todo veredito de IA (fonte, score, motivo) é persistido junto com o strike, então você pode auditar e sobrescrever se necessário.
- Sem prompt injection: linhas de log são passadas como dados, nunca interpoladas em instruções. O prompt é fixo e controlado.
Separação de privilégios
- Daemon principal (
ezyshield): roda como usuário sem privilégios, lê logs, toma decisões, comunica-se via unix socket - Enforcer (
ezyshield-enforcer): detém apenasCAP_NET_ADMIN, aceita um conjunto fixo e tipado de verbos (ping,add,del,list,flushe os verbos de allowlist), muta o nftables de forma segura e idempotente
O enforcer não é uma biblioteca. É um processo separado. O daemon principal não pode modificar o firewall diretamente.
Sem listeners de rede
O EzyShield não abre nenhum listener de rede para controle (o dashboard opcional faz bind apenas em um endereço loopback — 127.0.0.1 ou ::1 — e recusa qualquer outra coisa). O controle é via:
- CLI:
ezyshield ban,ezyshield list, etc. (somente local) - Unix socket:
/run/ezyshield/ezyshield.sock(permissões de filesystem)
Fluxo de dados
Toda conexão de saída que o EzyShield pode fazer — providers de AI, Cloudflare, atualizações de GeoIP, notifiers, ezyshield update — é opt-in e está documentada, com gatilho, payload e forma de desligar, na Referência de Fluxo de Dados. Aquela página também traz a configuração exata para rodar totalmente local, com zero tráfego de saída. Não há telemetria.
Audit trail
Toda ação é registrada em SQLite:
- Quando: timestamp
- O quê: IP, rule, score, decisão (ban/allow/defer)
- Por quê: nome da rule, resposta da IA (se a IA foi consultada)
Exportação para compliance:
ezyshield report --json > report.json # histórico por IP com evidênciasSincronização com o Cloudflare
Ao usar Cloudflare Lists:
- Sync idempotente: o EzyShield reconcilia sua visão com o Cloudflare no startup do daemon, a cada 5 minutos e sempre que bans expiram (adiciona entradas faltantes, remove as obsoletas)
- Fonte da verdade: a tabela
bans_activeno SQLite é a fonte da verdade. Se o EzyShield cair e reiniciar, ele restaura os bloqueios do Cloudflare a partir do DB. - Rules alheias preservadas: o EzyShield só toca na sua própria lista de IPs (
ezyshield_blocked) e nas rules de WAF que ele mesmo criou (marcadas pela descrição). Rules do Cloudflare criadas à mão ficam intactas.
Dry-run por padrão
armed: false é o default no policy.yaml. Enforcement é opt-in. Você precisa definir explicitamente armed: true para começar a bloquear.
Antes de armar, rode em dry-run por 24+ horas e revise as decisões.
Dependências
O EzyShield é distribuído como dois binários Go estáticos (ezyshield + o ezyshield-enforcer com separação de privilégios) com dependências mínimas de runtime:
- nftables do kernel Linux (para enforcement local)
- API do Cloudflare (opcional, TLS verificado)
- API do provedor de IA (opcional, TLS verificado)
Sem Python, sem Ruby, sem runtime Java. Sem inspeção de pacotes de terceiros. Sem módulos de kernel.
Modelo de ameaças
No escopo (protegemos contra):
- Tentativas de login SSH por força bruta
- Scanners de login de WordPress/Drupal
- Port scanners e enumeração de serviços
- Bots e scrapers HTTP
Fora do escopo:
- Exploits de kernel
- Chaves SSH comprometidas
- Bugs de lógica na camada de aplicação
- Ameaças internas (insider threats)
- Comprometimento do provedor de IA (assumimos que a API da Anthropic/OpenAI é confiável)
Compliance
O EzyShield mantém:
- Audit trail completo (consultável via SQL)
- Nenhum PII nos logs (apenas endereços IP)
- Limite de taxa para prevenir negação de serviço
- Allowlist para tráfego liberado
- Modo dry-run para testar antes do enforcement
Os dados desse audit trail podem sustentar requisitos de registro de requisições de SOC 2, ISO 27001 e GDPR — a compliance em si depende dos controles organizacionais que você constrói ao redor dele, não do EzyShield sozinho.
Reportando problemas de segurança
Encontrou uma vulnerabilidade? Abra um security advisory privado no GitHub (aba Security → Report a vulnerability) — veja o SECURITY.md. Não abra uma issue pública.