Backlog Unificado
Projeto: SeuImovel. Fonte principal: /www/wwwroot/seuimovel.rio.br/docs/BACKLOG.md.
Modo read-only: ações de escrita ficam disponíveis apenas para o Cérebro.
Especificações Disponíveis (fora da fila pendente)
Detalhe do BK Selecionado
BK-18 - Auditoria de segurança focada em pagamento (PagSeguro) - Julho/2026
Objetivo
Registrar os achados da auditoria de segurança pedida pelo dono do site em 24/07/2026, com foco no fluxo de pagamento (compra de créditos via PagSeguro) e na configuração de produção. Correção de premissa: o site não usa Mercado Pago — usa PagSeguro (imoveis/views.py, webhook_pagseguro, PAGSEGURO_EMAIL/PAGSEGURO_TOKEN). Não há nenhuma referência a Mercado Pago no projeto.
Todos os achados abaixo foram confirmados ao vivo (requisição HTTP real ao site em produção ou leitura direta do código/config).
Status
- [x] Item 1 (CRÍTICO —
DEBUG=Trueem produção) — corrigido e validado em 24/07/2026 - [ ] Item 1b (CRÍTICO —
SECRET_KEYde produção commitada no Git) — fora de escopo por decisão do dono, aguardando janela de baixo tráfego para rotacionar - [x] Item 2 (ALTO —
PAGSEGURO_SANDBOXacoplado aoDEBUG) — corrigido e validado em 25/07/2026 - [x] Item 3 (MÉDIO —
.envcom permissão 644) — corrigido e validado em 25/07/2026 - [x] Item 4 (MÉDIO — deduplicação do webhook por texto livre) — corrigido e validado em 25/07/2026
- [x] Item 5 (MÉDIO —
db.sqlite3parado dentro do webroot) — corrigido e validado em 25/07/2026 - [x] Item 6 (MÉDIO — webhook PagSeguro sem rate limit dedicado) — corrigido e validado em 25/07/2026
- [x] Item 7 (achado extra, mesma categoria do BK-341 item 5 — TLS 1.1 habilitado) — corrigido e validado em 25/07/2026
1. CRÍTICO — DEBUG=True ativo em produção
Confirmado ao vivo: uma URL inexistente (https://seuimovel.rio.br/uma-rota-que-nao-existe-teste-999/) retornava a página técnica de erro do Django (lista completa de URL patterns, stack, etc.) em vez de um 404 genérico. Isso acontece porque o .env de produção não define DEBUG, e seuimovelrio/settings.py tem DEBUG = config('DEBUG', default=True, cast=bool) — cai no default inseguro.
Isso contraria a própria documentação do projeto: DEPLOY_NOTAS.md já diz explicitamente "DEBUG: Deve estar False no .env" em produção — ou seja, é uma regressão/lacuna de configuração, não uma decisão consciente.
Correção aplicada: adicionado DEBUG=False ao .env de produção e reiniciado o serviço Gunicorn (seuimovelrio.service, confirmado como o único serviço ativo — gunicorn-seuimovel.service está failed desde jan/2026 e não deve ser usado).
Backup do arquivo mexido: /www/wwwroot/seuimovel.rio.br/.env.bkp_original_20260724-1749_BK-18-debug-false
Se o site cair depois desta mudança, restaurar esse .env e rodar systemctl restart seuimovelrio.service.
Validado em 24/07/2026:
curl https://seuimovel.rio.br/uma-rota-que-nao-existe-teste-999/→ agora mostra 404 genérico ("Not Found"), sem stack técnico- Home (
/) → 200 /imoveis/(listagem) → 200systemctl status seuimovelrio.service→active (running)após o restart, sem erro nos logs iniciais
1b. CRÍTICO (achado durante a correção do item 1) — SECRET_KEY de produção commitada no histórico do Git
Ao abrir o .env pra fazer o backup antes do fix do item 1, percebi que o SECRET_KEY usado em produção é exatamente o mesmo valor que está hardcoded como default no seuimovelrio/settings.py:
django-insecure-fo%tx0o*7r1pn4)nv-=#-r!dd5gh_4o+1_!gn04^m2lo0rbulp
Confirmado que esse literal aparece 5 vezes no histórico do Git (git log -p --all -- seuimovelrio/settings.py), num repositório com remote github-rionoteatro:GeJoRei/seuimovelrio.git. Ou seja: quem tiver acesso de leitura a esse repositório (colaborador, token vazado, ou o repo virar público em algum momento) já tem a chave real de produção — que assina sessões Django, tokens de CSRF e tokens de redefinição de senha. Na prática, quem tem essa chave consegue forjar sessão de qualquer usuário (inclusive admin) e forjar link de redefinição de senha sem precisar do e-mail da vítima.
Por que não corrigi junto com o item 1: rotacionar o SECRET_KEY desloga todo mundo que está logado no momento e invalida qualquer link de redefinição de senha pendente. Isso não derruba o site, mas é um impacto operacional/de usuário que prefiro fazer com ciência explícita do dono, não como efeito colateral de outro fix.
Correção proposta (aguardando decisão):
- Gerar uma chave nova e aleatória (
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"). - Colocar essa chave só no
.envde produção (nunca nosettings.py). - Trocar o default hardcoded do
settings.pypara algo que falhe alto se.envnão tiverSECRET_KEY, em vez de usar um valor real como fallback (ex.: manter um default óbviamente de desenvolvimento, tipo"dev-only-insecure-key", e nunca reaproveitar esse valor em produção). - Avisar que todos os usuários logados serão deslogados no momento da troca — bom fazer em horário de baixo tráfego.
- (Opcional, mas recomendado) verificar/alterar a visibilidade do repositório
GeJoRei/seuimovelriono GitHub e revisar quem tem acesso.
2. ALTO — PAGSEGURO_SANDBOX acoplado ao mesmo valor de DEBUG
settings.py: PAGSEGURO_SANDBOX = config('DEBUG', default=True, cast=bool). Como DEBUG estava caindo em True (item 1), o gateway de pagamento pode ter rodado em modo sandbox em produção por um período — precisa ser confirmado olhando o histórico de transações/logs do PagSeguro, já que isso pode ter mascarado ou distorcido cobranças reais.
Ação: conferir com o PagSeguro (painel/API) se houve compra de crédito real que caiu no ambiente sandbox por engano — isso ainda não foi conferido, é ação manual pendente do dono, eu não tenho acesso ao painel do PagSeguro.
Correção aplicada em 25/07/2026: PAGSEGURO_SANDBOX agora lê variável própria (config('PAGSEGURO_SANDBOX', default=False, cast=bool) no settings.py, PAGSEGURO_SANDBOX=False explícito no .env), desacoplada do DEBUG.
Backups: seuimovelrio/settings.py.bkp_original_20260725-0950_BK-18-item2-sandbox e .env.bkp_original_20260725-0950_BK-18-item2-sandbox.
Validado em 25/07/2026: systemctl restart seuimovelrio.service sem erro, home e /imoveis/ continuam 200. Não fiz uma compra real para confirmar o ambiente PagSeguro (ver item 4, zero transações COMPRA existem hoje em produção) — só confirmei que o app sobe normalmente lendo a nova variável.
3. MÉDIO — .env com permissão 644 (legível por qualquer usuário local)
No rionoteatro.com.br o .env está em 640 (mais restritivo). No seuimovel.rio.br estava em 644 — qualquer conta no servidor conseguia ler SECRET_KEY, DATABASE_URL e PAGSEGURO_TOKEN.
Correção aplicada em 25/07/2026: chown www:www + chmod 640 no .env (confirmado que seuimovelrio.service roda como User=www Group=www, então o processo continua lendo normalmente).
Validado em 25/07/2026: ls -la → -rw-r----- www www; restart do serviço sem erro de permissão nos logs; home//imoveis/ 200.
4. MÉDIO — Deduplicação do webhook do PagSeguro por texto livre
imoveis/views.py::webhook_pagseguro usa TransacaoCredito.objects.filter(descricao__icontains=transaction.code).exists() para não creditar duas vezes a mesma compra — comparação de substring em campo de descrição livre, não um campo indexado/único de "código de transação processado". Funciona na prática, mas é frágil (falso positivo se um código for substring de outro).
O webhook em si segue o padrão correto de segurança: não confia no POST recebido, sempre revalida via pg.check_notification(notification_code) direto na API do PagSeguro antes de creditar.
Correção aplicada em 25/07/2026: campo codigo_transacao = models.CharField(max_length=64, unique=True, null=True, blank=True) em TransacaoCredito (migration 0008_transacaocredito_codigo_transacao, aditiva — sem apagar/alterar dado existente). webhook_pagseguro em views.py passou a checar TransacaoCredito.objects.filter(codigo_transacao=transaction.code).exists() em vez do descricao__icontains.
Antes de migrar, tirei um backup lógico completo do banco via python manage.py dumpdata (/root/.security_followup/seuimovel_db_backups/dumpdata_20260725-0955_pre_BK18_item4_migration.json, 92KB). Também confirmei que hoje existem 0 transações do tipo COMPRA em produção (as 21 transações existentes são ADMIN/USO_ANUNCIO/CUPOM) — ou seja, não havia nada real para conciliar/backfillar nesse campo, o que tornou essa migration de risco bem baixo.
Backups: imoveis/models.py.bkp_original_20260725-0950_BK-18-item4-codigo-transacao, imoveis/views.py.bkp_original_20260725-0950_BK-18-item4-codigo-transacao.
Validado em 25/07/2026: migration aplicada sem erro; TransacaoCredito.objects.count() continua 21 depois da migration (nada perdido); as 21 linhas antigas ficaram com codigo_transacao=NULL (permitido, Postgres aceita múltiplos NULL numa coluna unique); serviço reiniciado sem erro; home//imoveis/ 200.
5. MÉDIO — db.sqlite3 parado dentro do webroot
Existe um db.sqlite3 na raiz do projeto (/www/wwwroot/seuimovel.rio.br/db.sqlite3), mas a produção real usa PostgreSQL via DATABASE_URL (conforme DEPLOY_NOTAS.md: "Banco: PostgreSQL 12.22"). Ou seja, provavelmente é um artefato de desenvolvimento esquecido, não o banco real — mas por estar dentro do document root, depende só do roteamento do Django/Nginx não expor arquivos estáticos ali (hoje não expõe, confirmado com curl → 404).
Correção aplicada em 25/07/2026: confirmado que é lixo de dev — db.sqlite3 tinha data de modificação de 02/11/2025, só 1 usuário e 0 imóveis (produção real, via Postgres, tem muito mais que isso). Movido (não apagado) para fora do webroot: /root/.security_followup/seuimovel_dev_leftovers/db.sqlite3.bkp_original_20260725-0952_BK-18-item5-removido-do-webroot.
Validado em 25/07/2026: curl https://seuimovel.rio.br/db.sqlite3 → 404 (já era 404 antes, mas agora o arquivo nem existe mais fisicamente no document root); home//imoveis/ 200; app não usa esse arquivo (confirmado via DATABASE_URL=postgres://... no .env, sem fallback ativo pro SQLite).
6. MÉDIO — Webhook do PagSeguro sem rate limiting dedicado
Cada chamada em /pagseguro-webhook/ faz uma requisição de rede síncrona à API do PagSeguro. Sem limite de taxa, um flood pode causar esgotamento de workers do Gunicorn ou rate-limit da conta no PagSeguro.
Correção aplicada em 25/07/2026: nova zona limit_req_zone $binary_remote_addr zone=pagseguro_webhook_rl:10m rate=120r/m; em nginx.conf e location = /pagseguro-webhook/ { limit_req zone=pagseguro_webhook_rl burst=20 nodelay; ... proxy_pass http://127.0.0.1:8000; } no vhost, antes do location / genérico que faz o proxy pra Gunicorn.
Backups: seuimovel.rio.br.conf.bkp_original_20260725-0951_BK-18-item6-webhook-rl-tls e nginx.conf.bkp_original_20260725-0951_BK-18-item6-zone.
Validado em 25/07/2026: 22-23ª requisição consecutiva a /pagseguro-webhook/ já retorna 429; home//imoveis/ continuam 200.
7. Achado extra (fora da lista original, encontrado durante o item 6) — TLS 1.1 habilitado
Igual ao BK-341 item 5 do rionoteatro.com.br: o vhost do seuimovel.rio.br também tinha ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;. Mesmo risco (abaixo do mínimo PCI-DSS, site processa pagamento via PagSeguro).
Correção aplicada em 25/07/2026: ssl_protocols TLSv1.2 TLSv1.3; (removido TLSv1.1). Mesmo backup do item 6.
Validado em 25/07/2026: conexão forçando --tls-max 1.1 falha; --tlsv1.2 continua 200.
Achado extra não corrigido (fora de escopo, só registrado) — WA_API_KEY com default hardcoded no código
Em settings.py: WA_API_KEY = config('WA_API_KEY', default='rnt_secret_key_2026_secure_v1') — mesmo padrão de risco do item 1b (SECRET_KEY): um valor de segredo "real-parecendo" como fallback hardcoded no código-fonte, versionado no Git. Não faz parte do escopo desta auditoria (que é sobre pagamento) e não foi tocado nesta execução — registrando aqui só para não perder o achado. Vale revisar se esse valor é o mesmo usado de fato em produção (igual ao que aconteceu com o SECRET_KEY do item 1b) numa auditoria futura.
Validação
Ver seção "Validado em 24/07/2026" dentro do item 1 acima (item 1). Itens 2 a 7: ver "Validado em 25/07/2026" dentro de cada item.