Auditoria de Pontos de Melhoria¶
Data: 2026-06-02
Escopo: Auditoria READ-ONLY do branch
develop, sistema em producao (cliente Highlas).Stack auditada: Django 5.2 + DRF + Celery + Next.js 15 + PostgreSQL + Redis + Channels.
Foram encontrados 30 achados distribuidos pelas categorias. Itens ja mapeados (HMAC, cooldown, opt-out, codigos Meta, quality rating, expiracao de surveys) nao foram repetidos — vide roadmap-gaps.md e diretrizes-meta.md.
Legenda: 🔴 ALTA · 🟡 MEDIA · 🟢 BAIXA
Resumo Executivo¶
Achados por severidade: - 🔴 ALTA: 11 (S-01, S-02, S-03, S-04, P-01, P-02, P-03, C-01, C-02, C-03, O-01, O-02) - 🟡 MEDIA: 16 - 🟢 BAIXA: 7
Top 5 prioridades para o cliente em producao:
- S-02 — Tornar
MessageLogread-only via API (15min, evita destruicao de auditoria) - P-03 + C-01 — Implementar idempotencia via
wa_message_idno webhook (mensagens duplicadas e o bug que mais frustra cliente final) - S-04 — Adicionar autorizacao ao
MediaProxyView+ remover JWT da querystring - C-02 + P-02 — Mover envio Meta para Celery com retry/backoff (combina performance + confiabilidade)
- O-01 + O-02 — Health check + metricas/Sentry (visibilidade antes que o cliente descubra problemas)
Esforco total estimado para resolver os 11 achados ALTA: ~4–5 dias de trabalho focado.
Arquivos criticos com maior densidade de problemas:
| Arquivo | Achados |
|---|---|
backend/whatsapp/views.py | S-01, S-02, S-06, P-02, P-03, C-01 |
backend/whatsapp/services/meta_graph_service.py (945 linhas) | C-02, M-01 |
backend/whatsapp/services/survey_service.py (721 linhas) | C-01, M-01 |
backend/chat/views.py — MediaProxyView | S-04 |
backend/app/settings.py | S-05, S-09, S-10 |
backend/responses/serializers.py | P-01 |
backend/responses/tasks.py | P-06, C-02 |
backend/contacts/serializers.py | S-07 |
1. SEGURANCA¶
S-01 🔴 Default inseguro do META_VERIFY_TOKEN permite verificacao de webhook por terceiros¶
- Local:
backend/whatsapp/views.py:269 - Descricao:
verify_token = getattr(settings, 'META_VERIFY_TOKEN', 'my_verify_token_12345'). Se o.envficar semMETA_VERIFY_TOKEN(deploy mal configurado, rotacao esquecida), o webhook fica aberto a verificacao com um token publico conhecido. - Impacto real: Atacante consegue assumir o webhook na Meta apontando para seu proprio dominio, e/ou enviar payloads forjados se descobrir a URL.
- Sugestao: Trocar para
getattr(settings, 'META_VERIFY_TOKEN', None)e retornar 500/403 explicitamente se for vazio. Falhar fechado, nao aberto. - Esforco: 15min
S-02 🔴 MessageLogViewSet permite POST/PUT/PATCH/DELETE sobre logs de auditoria¶
- Local:
backend/whatsapp/views.py:97–120(http_method_names inclui post/put/patch/delete) - Descricao: Logs de auditoria sao editaveis e deletaveis via API. O proprio docstring admite "Recomenda-se restringir edicoes a administradores" — mas isso nunca foi feito.
- Impacto real: Operador (ou conta comprometida) pode apagar/alterar evidencia de conversas com cliente — destruindo o valor LGPD/disputa que motiva o log existir.
- Sugestao: Trocar
ModelViewSetporReadOnlyModelViewSet(ou pelo menos restringirhttp_method_names = ['get', 'head', 'options']). Se precisar editar manualmente, fazer pelo Django Admin com log de auditoria nativa. - Esforco: 15min
S-03 🔴 Webhook Meta nao valida assinatura HMAC e aceita qualquer payload externo¶
- Local:
backend/whatsapp/views.py:254–450 - Descricao: Ja mapeado como pendente, mas vale ressaltar o impacto agravado: Como o endpoint cria
MessageLog, disparaConversation, abre WebSocket broadcast e roteia paraSurveyService/ChatService, qualquer terceiro que descubra a URL pode injetar mensagens falsas, abrir conversas ficticias, alterar status de mensagens (statuses) e poluir auditoria. - Sugestao: Implementar
X-Hub-Signature-256HMAC validation antes de qualquer side-effect. - Esforco: 2h
S-04 🔴 MediaProxyView expoe download de qualquer midia da Meta para qualquer usuario autenticado¶
- Local:
backend/chat/views.py:279–364 - Descricao: O endpoint
/api/v1/chat/media/<media_id>/autentica o usuario, mas nao verifica autorizacao aomedia_id. Qualquer usuario com JWT valido pode baixar qualquer midia desde que descubra/adivinhe ummedia_id. Alem disso, autenticacao via querystring?token=faz o JWT vazar para logs do nginx (access_log), historico de browser, Referer e CDN. - Impacto real: Vazamento de midia confidencial (anexos LGPD-sensiveis) + vazamento de JWT em logs.
- Sugestao: (1) Validar que o
media_idaparece em pelo menos umMessageLogque o operador tenha permissao de ver. (2) Encurtar o token: gerar uma URL assinada de curta duracao ao inves de aceitar JWT por querystring. (3) Configurar nginx para mascarar o querystringtoken=nos logs. - Esforco: 4h
S-05 🟡 JWT access token com vida util de 24h e alto demais para um console operacional¶
- Local:
backend/app/settings.py:215–221 - Descricao:
ACCESS_TOKEN_LIFETIME=24h. Combinado comlocalStorage(XSS-rouvavel) e ausencia de rotacao fina, qualquer XSS rouba um token valido por ate 24h sem possibilidade de revogacao (nao ha blacklist do access — so do refresh). - Impacto real: Em caso de XSS ou comprometimento de maquina, atacante tem 24h de janela.
- Sugestao: Reduzir
ACCESS_TOKEN_LIFETIMEpara 15-60min e confiar na rotacao do refresh (ja configurada). Considerar mover refresh parahttpOnlycookie para mitigar XSS. - Esforco: 1h
S-06 🟡 search_fields = ['phone', 'payload'] permite varredura full-text do payload JSON¶
- Local:
backend/whatsapp/views.py:117 - Descricao: Pesquisar pelo campo
payload(JSONField) fazILIKE %term%sobre o JSON serializado. Sem indice (nao ha GIN/jsonb_path_ops), com a tabelaMessageLogcrescendo rapido isso vira sequential scan custoso e expoe interno de payload (ex. operador pode buscar tokens/IDs internos sem precisar listar mensagens). - Impacto real: (1) Performance que degrada exponencialmente; (2) Operador pode "garimpar" message_ids da Meta, run_ids internos, IDs de templates, etc.
- Sugestao: Remover
payloaddesearch_fields. Se precisar, criar campos extraidos (ex:message_text) e indexar. - Esforco: 30min
S-07 🟡 ContactImportSerializer valida apenas extensao, nao MIME type real do arquivo¶
- Local:
backend/contacts/serializers.py:65–75 - Descricao: Validacao por sufixo do nome (
.csv,.xlsx). Atacante pode uploadpayload.exerenomeado paradata.csv. Se openpyxl falhar abrindo, levanta exception generica. - Impacto real: Risco moderado em contexto de import autenticado, mas viola o proprio padrao do
CLAUDE.md("Uploads: validar MIME type, nao apenas extensao"). Tambem pode causar DoS se enviar Excel com formula bomb ou XML expansion. - Sugestao: Usar
python-magicoumagic.from_buffer(file.read(2048))para checar MIME real. Limitar tamanho do arquivo (atualmente sem limite). ConfigurarMAX_UPLOAD_SIZE. - Esforco: 1h
S-08 🟡 Upload de midia em SendMediaView confia no content_type declarado pelo cliente¶
- Local:
backend/chat/views.py:259(mime_type=file.content_type or 'application/octet-stream') ebackend/chat/services.py:101 - Descricao: O validador (
if mime_type not in type_config['mimes']) usa o MIME enviado pelo browser, que e trivialmente forjavel. Possivel enviar binario arbitrario comContent-Type: image/jpege a Meta aceita/cobra/banca de risco fica no nosso lado. - Sugestao: Detectar MIME real via
python-magicantes de upload para Meta. Bonus: scan de antivirus para document/video (sandbox). - Esforco: 1h
S-09 🟢 Falta de CSP, Permissions-Policy, Referrer-Policy¶
- Local:
backend/app/settings.py:319–327 - Descricao: Apenas HSTS, nosniff, e XFO (via middleware padrao). Sem CSP,
Referrer-Policy,Permissions-Policy,Cross-Origin-Opener-Policy. O CLAUDE.md cita CSP como expectativa. - Impacto real: Sem CSP, XSS no frontend executa scripts de qualquer origem.
- Sugestao: Adicionar
django-cspou middleware custom com CSP estrito ao proprio dominio + dev tools de Next. AdicionarReferrer-Policy: strict-origin-when-cross-origin,Permissions-Policy: camera=(), microphone=(self)(precisa de mic para audio). - Esforco: 2h
S-10 🟡 Rate limiting global muito frouxo, AnonRateThrottle de 100/h em endpoints publicos¶
- Local:
backend/app/settings.py:133–141 - Descricao: O webhook e
csrf_exempt+@require_http_methods+ sem throttle (nao passa pela DRF stack).TokenObtainPairView(login) tambem nao tem throttle especifico — qualquer um pode tentar 100 logins/hora por IP. Endpoints autenticados tambem: 1000/h por user. Operador comprometido pode disparar runs/import em volume. - Sugestao: Adicionar
ScopedRateThrottleem endpoints criticos: login (5/min),start_survey(30/min),send-media(10/min). Para webhook, throttle por IP em middleware (uma vez validado HMAC, pode-se ser mais frouxo). - Esforco: 2h
2. PERFORMANCE¶
P-01 🔴 N+1 em SurveyRunSerializer.get_progress_percentage / get_total_questions no detail¶
- Local:
backend/responses/serializers.py:114–124eresponses/views.py:182 - Descricao: No
retrieve, oprefetch_related('answers__question', 'survey__questions')e feito, masget_progress_percentagechamaobj.survey.questions.count()que dispara nova query (nao usa o prefetch —count()reconta no DB). Mesma coisa paraobj.answers.count(). Em listas com muitos runs, isso e amplificado. - Impacto real: Em lista de 20 runs paginados, 20 queries extras de count. Para
analytics_overview, idem. - Sugestao: Anotar
_total_questions=Count('survey__questions')noget_querysetdo list, ou usarlen(prefetched_questions)em vez de.count(). - Esforco: 30min
P-02 🔴 evolution_webhook e meta_webhook fazem trabalho pesado SINCRONO antes de responder 200¶
- Local:
backend/whatsapp/views.py:280–440e455–520 - Descricao: O webhook deve responder em <3s (proprio CLAUDE.md). Atualmente: chama
SurveyService.process_incoming_message(varias queries + logica),MetaGraphService.send_message(HTTP outbound!),send_question_messageetc — tudo sincrono dentro do handler. Se a Meta API estiver lenta (acontece) o webhook timeout = retry, criando duplicacao. - Impacto real: Sob latencia da Meta, ha (1) timeout da Meta -> retry -> mensagens duplicadas; (2) usuario recebe mensagem com atraso; (3) workers HTTP travados.
- Sugestao: Webhook deve apenas: validar HMAC, persistir
MessageLog(in), e disparar tarefa Celery para processar. Resposta 200 imediata. (Combinar com S-03.) - Esforco: 4h
P-03 🔴 Webhook NAO e idempotente — mesma message.id da Meta cria multiplos MessageLog e potencialmente respostas duplicadas¶
- Local:
backend/whatsapp/views.py:399–435 - Descricao: A Meta faz retry se webhook responder !=2xx em 3s ou se confirmacao demorar. O codigo cria
MessageLogsem deduplicacao porwa_message_id(campo existe no modelMessageLog.wa_message_idmas nao e preenchido para mensagensin). Isso permite: registrar 2x a resposta "SIM" de um usuario, processar 2x = enviar pergunta 2 duas vezes, salvarSurveyAnswer2x (este ultimo e mitigado porunique_together('run','question')+update_or_create, mas o efeito do_proceed_to_next_questionocorre 2x). - Impacto real: Cliente recebe a mesma pergunta 2-3 vezes -> frustracao + custo Meta + parece bug. Idempotencia e requisito explicito.
- Sugestao: Capturar
message.idda Meta na criacao doMessageLog(emwa_message_id). Antes de processar, fazerMessageLog.objects.filter(wa_message_id=mid).exists()-> se sim, retornar 200 sem processar. - Esforco: 1h
P-04 🟡 mediaproxy cacheia midia inteira em Redis — risco de OOM¶
- Local:
backend/chat/views.py:328–349 - Descricao: Midias podem ter ate 100MB (PDFs). Cacheado em Redis (
maxmemory 512mb,allkeys-lru). Algumas midias grandes podem evicar todo o cache da aplicacao (que serve sessoes!). Pior: serializado/deserializado JSON ou pickle a cada hit. - Impacto real: Eviction massiva -> sessoes dropam, performance cai. Sessoes estao em
defaultcache (SESSION_CACHE_ALIAS = 'default'). - Sugestao: Usar disk-cache para midia (volume Docker) ou stream direto da Meta sem armazenar. Separar cache de sessoes em outro Redis DB.
- Esforco: 2h
P-05 🟡 Signal broadcast_message_log e sincrono dentro do post_save da request HTTP¶
- Local:
backend/whatsapp/signals.py:13–62 - Descricao: Toda criacao de
MessageLogdisparaasync_to_sync(channel_layer.group_send)— uma RTT a Redis (channels), serializacao, etc — dentro do hot path do webhook. Sob carga, isso vira gargalo. - Sugestao: Disparar o broadcast em uma task Celery
send_realtime_update.delay(message_id). - Esforco: 1h
P-06 🟡 expire_stale_runs faz 5 querysets .count() + 5 .update() separados — sem transaction.atomic¶
- Local:
backend/responses/tasks.py:94–208 - Descricao: Entre o
count()e oupdate()outra worker pode mover registros. Nao e catastrofico (no pior caso conta menos do que atualiza), mas o relatorio no log fica inconsistente. Tambem: 8 queries onde 1 update comQ()por regra resolveria. - Sugestao: Envolver em
transaction.atomice usar unico UPDATE comQ(...) | Q(...) | Q(...). Para o relatorio, fazer um.count()antes do update unico. - Esforco: 1h
P-07 🟡 Conversation.unread_count += 1 em handle_incoming_message e race-condition-prone¶
- Local:
backend/chat/services.py:163–170 - Descricao: Padrao classico "read-modify-write" sem
F(). Duas mensagens simultaneas -> contador incrementa so 1 vez. Em alta concorrencia, conta perde. - Sugestao:
Conversation.objects.filter(pk=...).update(unread_count=F('unread_count')+1)ouselect_for_update. - Esforco: 15min
P-08 🟢 MessageLog.payload (JSONField) sem indice GIN apesar de ser pesquisado¶
- Local:
backend/whatsapp/models.py:57e indexes ao redor - Descricao: Filtros usam
payload__message_id=...(linha 307 de views.py),payload__type__in=[...](linha 149). Sem GIN, full scan. - Sugestao: Para Postgres, adicionar
GinIndex(fields=['payload'])via migration. Considerar promovermessage_id(do payload) para coluna propria ja que e consultado. - Esforco: 1h
P-09 🟢 RunNewClient.tsx usa useState + useEffect para data fetching ao inves de TanStack Query¶
- Local:
frontend/src/components/runs/RunNewClient.tsx:58–84 - Descricao: Viola padrao explicito do CLAUDE.md ("nunca useState + useEffect manual"). Nao ha cache, refetch, dedup. Header.tsx, SurveyNewClient.tsx, AnalyticsClient.tsx provavelmente repetem o padrao.
- Sugestao: Migrar para
useQuery(ja existem hooks emhooks/queries/). - Esforco: 2h por componente
3. CONFIABILIDADE¶
C-01 🔴 try/except Exception muito amplo em handlers criticos — 22 ocorrencias encontradas¶
- Local: Varios, exemplos:
backend/whatsapp/views.py:446,whatsapp/services/survey_service.py:72,86,111,124,chat/views.py:319 - Descricao: Captura generica de
Exceptionem pontos criticos. No webhook, qualquer erro viraJsonResponse({'error': '...'}, status=500), mas a Meta nunca vai retentar porque o handler retornou. Resultado: mensagem perdida, log diz "erro generico", nada actionable. Emprocess_incoming_message, retornarNonesilencia falhas. - Impacto real: Falhas silenciosas em producao. Cliente envia "SIM" e nada acontece — ninguem sabe.
- Sugestao: Capturar excecoes especificas (
requests.Timeout,requests.ConnectionError,Contact.DoesNotExist). Para o resto, deixar propagar para Sentry/similar. Webhook deve retornar 500 para a Meta retentar quando falha e transitoria (network, DB lock), mas idempotencia (P-03) e pre-requisito. - Esforco: 4h
C-02 🔴 Sem retry/backoff para Meta API — falha = mensagem perdida¶
- Local:
backend/whatsapp/services/meta_graph_service.py:36–88e demais metodos - Descricao:
requests.post(..., timeout=30). Se Meta retornar 5xx ou timeout,(False, None)e retornado e a mensagem e perdida sem retry. Nao ha circuit breaker — sob outage da Meta, continuamos batendo (e gastando rate limit). - Impacto real: Pesquisa fica travada (cliente respondeu "9" para NPS mas a proxima pergunta nao chega), sem alerta. Idem perda silenciosa.
- Sugestao: Usar
requestscomurllib3.Retry(status_forcelist=[502,503,504], backoff_factor=1, total=3) outenacity. Para envios criticos, mover para Celery task comautoretry_for=(RequestException,)eretry_backoff=True. - Esforco: 4h
C-03 🔴 start_survey cria multiplas runs sequencialmente sem transacao — falha parcial deixa estado inconsistente¶
- Local:
backend/surveys/views.py:408–481 - Descricao: Para cada telefone:
Contact.get_or_create-> criaSurveyRun->meta_service.send_initial_survey_message(HTTP!) -> se falhar,survey_run.delete(). Mas se o processo for interrompido entrecreatee a tentativa de envio (ex: timeout HTTP, OOM, container kill), a run fica comopendingsemsent_at, esperando expiracao de 24h. - Impacto real: Em bulk de 100 contatos, falha do worker no meio deixa metade enviadas, metade pendentes silenciosas.
- Sugestao: Mover envio para Celery tasks, criar
SurveyRun(status='pending')em transacao atomica, e a task lida com retry/envio. Permite tambem respeitar rate-limit da Meta cometaespacado. - Esforco: 4h
C-04 🟡 Conversation.get_or_create (chat/services.py:228) em alta concorrencia pode violar unique¶
- Local:
backend/chat/services.py:225–237 - Descricao: Duas mensagens simultaneas do mesmo telefone (entrega delivered + read em paralelo) podem disparar 2 get_or_create concorrentes — Postgres impoe unique, uma vai estourar
IntegrityErrornao tratado. - Sugestao:
try/except IntegrityError+ re-fetch, ouselect_for_updatecomtransaction.atomic. - Esforco: 30min
C-05 🟡 Cobertura de testes baixa em fluxos criticos¶
- Local:
backend/whatsapp/tests/esta vazio (so__init__.py) - Descricao: Webhook,
SurveyService,MetaGraphServicenao tem testes. Tem testes emchat/,responses/,surveys/,contacts/, mas o coracao do produto (webhook + survey logic) nao. - Impacto real: Qualquer refator (ex: implementar HMAC) tem alto risco de regredir o sistema em producao.
- Sugestao: Minimo: testes para
process_incoming_message(SIM/NAO/PARAR/resposta-numerica),_validate_answer(cadakind),_evaluate_conditions, webhook GET (verificacao), webhook POST (parsing Meta payload). - Esforco: 1 dia
C-06 🟡 Logs em strings concatenadas com f-string em logger.error(f'...{e}') perdem contexto estruturado¶
- Local: Toda a base — exemplos em
survey_service.py,meta_graph_service.py,views.py - Descricao: F-strings perdem o lazy evaluation do logging. Nao da para filtrar/agregar por phone, survey_id, run_id em ferramenta de log central. Conflita com requirement de "logs estruturados".
- Sugestao: Padronizar
logger.info("...", extra={'phone': phone, 'run_id': run.id})ou adotarstructlog. Pelo menos parar de usar f-strings em logs. - Esforco: 4h (gradual)
4. MANUTENIBILIDADE¶
M-01 🟡 Arquivos enormes — meta_graph_service.py (945 linhas), responses/views.py (956), survey_service.py (721)¶
- Local: varios
- Descricao:
MetaGraphServicemistura: envio de texto, midia, botoes, lista, template, parsing de contato.SurveyServicefaz parsing + validacao + fluxo + condicional + completion. Dificil testar isoladamente. - Sugestao: Quebrar:
MetaGraphClient(HTTP puro) +MetaMessageBuilder(formata payloads) +WebhookParser.SurveyService->SurveyFlowEngine+AnswerValidator. - Esforco: 1 dia
M-02 🟡 SurveyCreateSerializer.update e create duplicam ~50 linhas de logica¶
- Local:
backend/surveys/serializers.py:188–346 - Descricao: O bloco "primeiro passo cria perguntas, segundo passo resolve FKs e cria conditions" e literalmente repetido entre create e update.
- Sugestao: Extrair
_create_questions_for_survey(survey, questions_data)helper. - Esforco: 30min
M-03 🟡 Magic strings espalhadas: status, kinds, types de payload¶
- Local: varios —
'pending','in_progress','survey_accept','initial_invite','completion','chat','survey', etc - Descricao: Strings comparadas em multiplos arquivos sem constantes centralizadas. Renomear um status quebra silenciosamente o codigo.
- Sugestao: TextChoices Django no model + import em todos os usos. Aplicar para
payload.type,source,button_idids. - Esforco: 4h
M-04 🟡 Import dentro de funcao em hot path (lazy imports)¶
- Local: Webhook tem ~6 imports locais dentro do handler (
from chat.services import ChatService,from channels.layers import get_channel_layer, etc.). Idem em signals, services. - Descricao: Faz sentido em alguns casos (circular import), mas multiplos imports sao feitos a cada request — overhead pequeno mas mensuravel + acopla manutencao. Em
services/survey_service.pyhafrom whatsapp.models import MessageLogdentro de cada metodo. - Sugestao: Mover para top do arquivo onde nao ha circular. Para circular, refatorar (M-01).
- Esforco: 1h
M-05 🟢 TypeScript any espalhado, especialmente em formularios e graficos¶
- Local: Lista completa em busca anterior —
RunNewClient.tsx,AnalyticsClient.tsx,SurveyFormWithQuestions.tsx, etc. - Descricao: ~30 ocorrencias de
: anyem codigo de producao. Recharts callbacks ((entry: any)) e aceitavel (lib mal tipada). Maspayload: any,result: any,fail: anyem fluxos de dominio e evitavel. - Sugestao: Tipar pelo menos as fronteiras de API (ex: tipos de respostas de
start_survey). AdicionarnoImplicitAny: trueno tsconfig (se nao estiver). - Esforco: 4h
M-06 🟢 ~22 console.log deixados em codigo de producao¶
- Local:
frontend/src/components/forms/SurveyFormWithQuestions.tsx(varios, com emoji),RunNewClient.tsx,Header.tsx, etc. - Descricao: Logs de debug (com emojis) deixados apos desenvolvimento. Poluem o console em producao.
- Sugestao: Remover ou trocar por um util
debug()que checaprocess.env.NODE_ENV === 'development'. Adicionar regrano-consoleno eslint (ja tem eslint configurado). - Esforco: 30min
M-07 🟢 Arquivo RunsClient.tsx.tmp esquecido no repo¶
- Local:
frontend/src/components/runs/RunsClient.tsx.tmp - Descricao: Backup acidental.
- Sugestao: Deletar.
- Esforco: 5min
M-08 🟢 Tipos de hint inconsistentes — Python 3 union syntax e Optional misturados¶
- Local: Em varios services, alguns retornos usam
tuple[bool, str | None](Python 3.10+) e outrosOptional[str] - Descricao: OK funcionalmente, mas inconsistente. Nao ha
mypyconfigurado. - Sugestao: Configurar
mypy --strictparawhatsapp/services/,responses/, e padronizar gradualmente. - Esforco: 1 dia
5. UX / UI¶
U-01 🟡 Tokens JWT em localStorage — vulneravel a XSS¶
- Local:
frontend/src/lib/api.ts:41–67 - Descricao: XSS injetado le o token. localStorage e o padrao facil mas inseguro. Combinado com S-05 (24h TTL), uma unica vulnerabilidade XSS da controle prolongado.
- Sugestao: Mover refresh para
httpOnlycookie + access em memoria (Redux/Context). Ou pelo menos isolar via Service Worker. - Esforco: 1 dia (refator de auth)
U-02 🟡 Mensagens de erro tecnicas vazando para usuario¶
- Local:
frontend/src/components/runs/RunNewClient.tsx:231–260(catchany=>fail.error_code),responses/views.py:518–520('Erro interno do servidor'),chat/views.py:191(return Response({'error': error_msg})retorna mensagem doValueErrordireto). - Descricao: Strings como "Falha ao enviar convite da pesquisa", "Erro ao processar webhook Meta", "ffmpeg erro:
" podem chegar ao usuario/operador sem traducao amigavel. - Sugestao: Mapear
error_codeno backend e traduzir no frontend. Nunca expor stack/stderr. - Esforco: 2h
U-03 🟡 Acessibilidade limitada — so ~12 aria-* no projeto inteiro¶
- Local:
frontend/src/components - Descricao: Para um console operacional com formularios complexos (SurveyFormWithQuestions com steps), 12 aria labels e pouco. Sem
aria-label, leitor de tela pode pular informacao chave (status de mensagens, botoes de acao em tabelas). - Sugestao: Auditar com
axe-coreou Lighthouse. Foco: botoes com icones sem label, modais semaria-modal+aria-labelledby, tabelas sem<caption>. - Esforco: 1 dia
U-04 🟢 loading.tsx global do Next sem skeleton — so spinner¶
- Local:
frontend/src/app/loading.tsx - Descricao: Spinner generico bloqueia toda a tela em rota. Skeleton por pagina da percepcao de velocidade.
- Sugestao: Skeletons especificos por tipo de pagina.
- Esforco: 2h
6. OBSERVABILIDADE¶
O-01 🔴 Sem health check endpoint no backend¶
- Local:
backend/app/urls.py— nao ha/healthzou/readyz - Descricao: docker-compose nao tem healthcheck no backend (tem para db, redis, docs). Sem
/healthzapropriado, o orchestrator nao detecta backend degradado (ex: DB caiu mas WSGI ainda responde). - Sugestao: Endpoint
/api/healthz/que verifica: DB connection, Redis ping, Celery beat heartbeat (timestamp recent). Adicionarhealthcheck:no docker-compose. - Esforco: 2h
O-02 🔴 Sem metricas de negocio expostas / monitoradas¶
- Local: Sistema todo — nao ha
prometheus_client,statsd, nem Sentry - Descricao: Nao tem como saber em real-time: taxa de resposta SIM vs NAO, mensagens falhadas por hora, latencia da Meta API, runs travadas. Tudo e "olho no log" depois.
- Sugestao: Minimo: middleware Sentry para erros + um endpoint
/metricsPrometheus com contadores basicos (survey_invites_sent_total,survey_responses_received_total{status="sim|nao|other"},meta_api_latency_seconds). Dashboard Grafana. - Esforco: 1 dia
O-03 🟡 Nao ha alerting configurado para Celery tasks falhando¶
- Local:
responses/tasks.pyusaself.retry(exc=exc, countdown=...), mas apos 3 tentativas a tarefa morre silenciosa - Descricao: Em producao,
send_scheduled_surveysfalhando 3x = pesquisas agendadas nao saem, e ninguem sabe. - Sugestao:
on_failurecallback que envia email/webhook. Ou Sentry com integracao Celery. - Esforco: 1h
O-04 🟡 Log rotation 10MB x 5 = so 50MB de historia — muito curto para 1 sistema em producao¶
- Local:
backend/app/settings.py:355–376 - Descricao: Em alta carga (50k mensagens/dia), 50MB ≈ poucas horas. Investigar incidente de ontem fica inviavel.
- Sugestao: Aumentar
maxBytespara 50MB,backupCountpara 30. Idealmente shipar para log aggregator (Loki, ELK, CloudWatch). - Esforco: 30min
7. DEBT TECNICO¶
D-01 🟡 Codigo deprecated da Evolution API ainda presente¶
- Local:
backend/app/settings.py:428–431(config comentada como DEPRECATED mas mantida),backend/whatsapp/services/evolution_service.py(existe mas docker-compose desativa o servico),backend/whatsapp/views.py:455–520(evolution_webhookview ativa em URL) - Descricao: A rota
/api/v1/webhook/evolution/ainda esta ativa e exposta. Sem assinatura HMAC, sem cleanup, mas accessible. Um caminho a menos para auditar = um caminho a mais para atacante. - Sugestao: Se Evolution API foi de fato abandonada, remover a view, URL, service e settings. Se nao, documentar o porque de manter.
- Esforco: 1h
D-02 🟡 Backups JSON commitados no repo¶
- Local:
/backup_completo.json,/backup_contacts.json,/backup_surveys.json,/backend/backup_surveys.json - Descricao: 4 arquivos JSON na raiz do repo — alguns parecem dados de contatos reais. Risco LGPD (dados de clientes em git history mesmo apos delete).
- Sugestao: Verificar conteudo. Se forem dados reais: remover, rotacionar git history (
git filter-repo), e mover backups para storage offline (S3 + criptografia). - Esforco: 2h
D-03 🟡 .env real esta no diretorio (nao no git, mas presente)¶
- Local:
/backend/.env(confere com.gitignoreque protege) - Descricao:
.envesta na maquina com credenciais reais. Como o repo esta em/home/ubuntu/projetos/, qualquer comprometimento do userubuntuexpoe tudo. Nao ha mecanismo de secrets externos (Vault, Doppler, etc.). - Sugestao: Mover para secrets store. Restringir permissoes:
chmod 600 .env. - Esforco: 30min (chmod) | 1 dia (migrar para secrets manager)
D-04 🟢 Diretorio __pycache__, .pytest_cache, staticfiles versionados ou no working dir¶
- Local:
backend/.pytest_cache,backend/staticfiles - Descricao:
staticfilesexiste na arvore. Nao esta no git (gitignore inclui), mas a pasta foi criada com 30 jan e pode estar stale. - Sugestao: Limpar e ter pipeline que recria a cada deploy.
- Esforco: 15min
D-05 🟢 Permission.objects.all() sem paginacao retorna ~150+ permissoes¶
- Local:
backend/users/views.py:204–216—pagination_class = None - Descricao: Conforme tabela cresce (mais models), o endpoint vai retornar payload maior. Hoje aceitavel (~150).
- Sugestao: Manter sem paginacao mas adicionar
OPTIONS/cache HTTP (Cache-Control: private, max-age=3600) — permissoes mudam raramente. - Esforco: 30min
Sugestao de Ordem de Implementacao¶
Considerando dependencias entre achados, a ordem otima e:
Sprint 1 — Quick wins de seguranca (1 dia)¶
- S-02 (15min) —
MessageLogread-only - S-01 (15min) —
META_VERIFY_TOKENfalhar fechado - S-06 (30min) — remover
payloadde search_fields - M-07 (5min) — deletar
.tmpesquecido - D-03 (30min) —
chmod 600 .env - D-04 (15min) — limpar
staticfiles/.pytest_cache - C-04 (30min) — IntegrityError handling
- P-07 (15min) —
F()no unread_count - M-06 (30min) — remover console.log
Sprint 2 — Idempotencia + Async (2-3 dias)¶
- P-03 (1h) — wa_message_id no MessageLog in
- C-01 (4h) — refinar except clauses
- C-02 (4h) — retry/backoff Meta
- P-02 (4h) — webhook async via Celery
- C-03 (4h) — start_survey em Celery + transacional
Sprint 3 — Seguranca avancada (2 dias)¶
- S-03 (2h) — HMAC do webhook (depende de App Secret)
- S-04 (4h) — MediaProxyView authorization
- S-05 + U-01 (1 dia) — JWT TTL + httpOnly cookie
- S-07 + S-08 (2h) — MIME real em uploads
- S-09 (2h) — CSP + Permissions-Policy
- S-10 (2h) — Rate limiting fino
Sprint 4 — Observabilidade (1-2 dias)¶
- O-01 (2h) — health check + docker healthcheck
- O-02 (1 dia) — Sentry + Prometheus
- O-03 (1h) — alertas Celery
- O-04 (30min) — log rotation maior
- C-06 (4h) — logs estruturados
Sprint 5 — Performance (1 dia)¶
- P-01 (30min) — anotacoes em SurveyRun list
- P-04 (2h) — cache de midia em disco
- P-05 (1h) — broadcast async
- P-06 (1h) — atomicidade expire_stale_runs
- P-08 (1h) — indice GIN no payload
Sprint 6 — Manutenibilidade (2-3 dias, opcional)¶
- M-01 (1 dia) — quebrar arquivos gigantes
- M-02 (30min) — DRY no SurveyCreateSerializer
- M-03 (4h) — constantes para magic strings
- M-04 (1h) — imports no topo
- M-05 (4h) — eliminar TypeScript any
- C-05 (1 dia) — testes para fluxos criticos
- U-02 (2h) — error_code mapping
- U-03 (1 dia) — acessibilidade
Sprint 7 — Limpeza de debt (1 dia)¶
- D-01 (1h) — Evolution API cleanup
- D-02 (2h) — sanear backups JSON
- U-04 (2h) — skeletons
- M-08 (1 dia) — mypy strict
- P-09 (2h por componente) — migrar useState para TanStack Query
- D-05 (30min) — cache HTTP em permissions
Total estimado para o roadmap completo: ~15-20 dias de trabalho focado.
Para mais detalhes sobre cada gap arquitetural ja conhecido, vide roadmap-gaps.md e diretrizes-meta.md.