# Relatório — Ataque de Fuzzing no THC LLM Space (Julho/2026) ## Contexto Servidor THC LLM (Hugging Face Space, https://hulktoigo-thcllm.hf.space) sob ataque de fuzzing automatizado há cerca de 4 dias, em horários específicos do dia. Padrão: scanner batendo em rotas conhecidas de vazamento de config (.env, .git, actuator, streamlit, graphql, path traversal, etc). ## Evolução do ataque 1. Fase inicial: IP único martelando repetidamente as mesmas rotas. 2. Fase atual: rotação de IP (botnet/proxy) — cada IP gasta uma cota de requisições, é banido, e o bot pula pro próximo IP. Isso sozinho gera volume suficiente pra saturar o worker único do Space (free tier). ## Proteções implementadas em app.py (nesta ordem histórica) ### PROTEÇÃO 1 — já existia antes deste incidente docs_url/redoc_url/openapi_url desativados em produção (só ativos com THC_DEBUG=true), evitando que scanners mapeiem as rotas via /openapi.json. ### PROTEÇÃO 2 — já existia antes deste incidente Middleware `block_known_fuzzing_patterns` com lista `_FUZZING_PATTERNS` (.env, .git, .streamlit, actuator, graphql, path traversal codificado etc.) bloqueando com 400 antes de consumir modelo/RAG. ### PROTEÇÃO 3 — adicionada neste incidente - `get_real_ip(request)`: lê X-Forwarded-For antes de request.client.host, porque HF Spaces expõe IPs internos do proxy (10.x.x.x / 10.16.x.x / 10.20.x.x) no client.host — não é o IP público real do atacante. - Rate limiting em memória por IP real: janela deslizante de 60s, máximo 60 requisições, ban de 600s (10 min) ao estourar. - `_is_authenticated_thc_key(request)`: chamadas com header X-THC-Key válido (comparado contra THC_MASTER_CLI_KEY ou validado via verify_api_key do módulo auth) pulam TODO o rate limit e ban — nunca é o atacante anônimo. - Ban rápido específico para padrão de fuzzing: contador separado e mais agressivo (`_fuzz_hits`), 5 hits em janela de 30s já bane o IP por 600s — em vez de esperar o limite geral de 60 req/min. Isso corta o desperdício de ~55 requisições por IP rotacionado. CONFIRMADO funcionando nos logs (ban disparando em ~1s contra IPs novos). ### Correção de segurança relacionada (não é sobre o ataque em si) THC_SESSION_SECRET (usada pelo SessionMiddleware pra assinar o cookie de sessão do login OAuth) estava com fallback para string vazia — corrigido para RuntimeError no boot se ausente. Causou outage curto no primeiro deploy porque a secret ainda não estava cadastrada nos Secrets do Space; resolvido gerando com `secrets.token_hex(32)` e cadastrando separada da THC_MASTER_CLI_KEY (são segredos diferentes, propósitos diferentes — sessão web vs autenticação de API). ## Estado atual (confirmado por teste real) - Endpoint /v1/messages (formato Anthropic): funcionando perfeitamente, autenticado, mesmo durante ataque ativo — resposta JSON completa testada. - Endpoint /v1/messages/stream (SSE): funcionando perfeitamente, streaming completo testado ponta a ponta (message_start → content_block_delta* → message_stop), autenticado, durante ataque ativo. - Bloqueio de fuzzing: confirmado ativo (400 em rotas conhecidas). - Ban rápido: confirmado ativo, disparando em ~1s por IP rotacionado. - Exceção X-THC-Key: confirmada ativa (chamadas autenticadas não recebem 429 mesmo com volume de ataque simultâneo). ## PENDÊNCIA ABERTA — não resolvida ainda Teste em loop rápido (10 chamadas sequenciais, 1s de intervalo) contra /v1/chat/completions (formato OpenAI) autenticado com X-THC-Key mostra padrão intermitente: status 200 alternado com status 000 (conexão não completa / timeout / reset — não é resposta HTTP nenhuma, então não é rate limit nem fuzzing block, que sempre retornariam 400/429). Hipóteses ainda não investigadas: 1. Worker único do Space (free tier, sem --workers) saturado processando volume do ataque, mesmo com a exceção de rate limit já pulando a lógica de contagem/ban para chamadas autenticadas — a saturação pode estar acontecendo em nível de SO/rede antes mesmo do código da aplicação. 2. Throttling na camada de infraestrutura/CDN do próprio Hugging Face, independente do código do app.py. 3. Comportamento do curl em loop com keep-alive de conexão. Próximo passo de diagnóstico sugerido: rodar o mesmo teste em loop com `curl -v` pra capturar o erro real da conexão (timeout vs reset vs DNS), repetir em horário sem pico de ataque para comparar, e verificar se o mesmo padrão se repete em /v1/messages sob a mesma carga (o teste anterior foi interrompido cedo demais para confirmar). ## Decisão em aberto Se a saturação persistir mesmo com as proteções de código, o próximo passo sai do escopo de código e vai para: upgrade de hardware do Space (mais CPU/workers) ou colocar uma camada de proteção externa (ex: Cloudflare grátis) na frente do Space antes do tráfego chegar no container. ## Nota adicional (fora do escopo do ataque) O endpoint /v1/messages não tem Depends(get_current_user) como os endpoints de imagem/áudio têm — passa só pelo middleware de rate limit/fuzzing, sem exigir autenticação formal. Verificar se isso é intencional (compatibilidade com SDK) ou lacuna a fechar depois que o ataque atual estiver sob controle.