CondoShield
Condomínio com dezenas de câmeras, DVR e controle de acesso ligados na internet, e ninguém sabe quais estão expostos.
O CondoShield organiza o parque de IoT e CFTV do condomínio em três camadas: o site (o condomínio), os dispositivos daquele site e as varreduras que produzem achados. Tudo é tenant-scoped sob RLS FORCE, e o alvo da varredura vem sempre dos dispositivos cadastrados pelo próprio tenant — nunca de um endereço digitado livremente na requisição.
A varredura é passiva e conservadora por desenho. Antes de qualquer pacote, o guard de SSRF resolve o host e recusa qualquer IP privado, loopback, link-local ou de metadata de nuvem, incluindo o 169.254.169.254. Dispositivo marcado como não exposto à internet nem chega a ser tentado. Um dispositivo interno bloqueado é contado nas estatísticas como bloqueado — isso é o comportamento correto, não uma falha: o que está atrás do roteador só é alcançável por um appliance de borda, que não faz parte deste código. O motor é resiliente por construção: falha em um dispositivo vira uma entrada de erro e não derruba os demais nem a varredura.
O achado tem impressão digital estável derivada de dispositivo, categoria e local. Re-observar atualiza a linha existente em vez de criar duplicata, e só achado realmente novo acima do piso de severidade gera alerta. O score de risco é determinístico: peso da categoria multiplicado pelo fator de severidade, limitado entre 0 e 100. Trechos de evidência já saem redigidos pelo motor do Scan, que mascara credencial, chave PEM e JWT. O crédito segue hold-then-capture: reservado ao pedir, e o pipeline do worker captura no sucesso e libera em qualquer falha, com um beat cross-tenant que ceifa varredura travada em execução e devolve a reserva. Na prática, enquanto faltar o ramo do relay (ver limites), a varredura não sai de 'queued': a reserva fica aberta — nem capturada, nem devolvida.
Capacidades em operação
Cada item corresponde a funcionalidade que existe no código e é alcançável por um usuário com o escopo correto.
-
Inventário por site
CRUD de sites (nome, endereço, administradora) e cadastro, leitura e edição de dispositivos (tipo, IP, MAC, fabricante, modelo, firmware, exposto à internet, status) — não há rota para remover um dispositivo: ele só sai em cascata, quando o site é apagado. Paginação por cursor. Quinze rotas ao todo, todas sob escopo próprio. Quinze rotas ao todo, todas sob escopo próprio.
-
Guard de SSRF obrigatório e fail-closed
Reusa resolve_public_ips e run_passive_checks do módulo Scan. Host que resolve para IP interno é recusado antes de qualquer pacote e contabilizado como bloqueado. Concorrência limitada a seis dispositivos por vez.
-
Achados deduplicados por impressão digital
Upsert com ON CONFLICT em (tenant, fingerprint): re-observação atualiza last_seen, severidade, título e evidência em vez de duplicar a linha.
-
Score de risco determinístico
Valor de 0 a 100 calculado por peso de categoria (credencial padrão 90, acesso anômalo 80, firmware vulnerável 75, exposição 60, porta insegura 50, sem criptografia 40) vezes o multiplicador de severidade.
-
Triagem com transições validadas
new pode ir para reviewed, confirmed ou dismissed; reviewed para confirmed ou dismissed; confirmed e dismissed são terminais. Transição inválida é recusada.
-
Crédito com hold-then-capture
Reserva no pedido (preço padrão de 6 créditos, configurável), com Idempotency-Key e rate-limit por IP e tenant na rota de varredura. A captura no sucesso e a liberação na falha estão escritas no pipeline do worker, mas hoje não chegam a acontecer: sem o ramo do relay, a varredura não sai de 'queued' e a reserva permanece aberta (ver limites).
-
Reaper de varredura travada
Beat cross-tenant na fila default marca como falha a varredura presa em execução além do teto e libera o hold pendente, com guarda idempotente e linha de auditoria.
Entregáveis
- Inventário de dispositivos por condomínio, com marcação do que está exposto à internet
- "Lista de varreduras com o pedido registrado — hoje sempre em 'queued' e sem estatística, porque o motor não chega a rodar (ver limites)" e "Fila de achados, vazia enquanto a varredura não executar: o contrato já entrega severidade, categoria, score de risco e trecho de evidência redigido quando houver achado".
- Fila de achados com severidade, categoria, score de risco e trecho de evidência já redigido
O que este módulo não faz
A varredura não chega a executar no estado atual do código. O POST /sites/{id}/scans cria o registro em 'queued', reserva o crédito e emite condoshield.scan.requested no outbox — mas o relay em worker/tasks/outbox.py::_enqueue não tem ramo para esse tópico: o evento é marcado como publicado e cai no caso default, sem consumidor. A task condoshield.scan existe, está registrada na fila scanning e delega ao pipeline correto, porém ninguém a enfileira. Resultado: a varredura fica em 'queued' e o reaper só ceifa as que estão em 'running', então o hold de crédito permanece aberto. Falta um ramo no relay. Não existe descoberta de dispositivos: o inventário é cadastrado à mão por POST /devices, não há varredura de rede que encontre câmeras sozinha. O motor produz apenas duas das seis categorias do vocabulário — o mapeamento traduz para exposicao_internet e sem_criptografia; credencial_padrao, firmware_vulneravel, acesso_anomalo e porta_insegura existem no schema e no peso do score, mas nenhum código as gera. Dispositivo de LAN não é avaliado: sem appliance de borda, o que está atrás do roteador fica fora, e o appliance é gancho externo que não existe no repositório. O campo modules do corpo é aceito e gravado, mas o motor não o lê: não há como escolher o que rodar. Varredura agendada não existe — o vocabulário aceita 'scheduled', toda criação grava 'manual' e nenhum beat cria varredura. O resumo executivo por IA está escrito em condoshield/ai.py e não tem chamador nem rota; o router sequer importa o módulo, então a task nem é registrada no gateway. O alerta de achado alto é uma linha de auditoria, não notificação: nada é enviado ao síndico.
Declaramos o limite porque software de segurança que promete tudo não é auditável. Se o que falta aqui é o que você precisa, o serviço consultivo cobre — ou dizemos que não cobre.
Conecta com
- app.scan.passive (guard de SSRF, checagens HTTP/TLS/DNS e redação de evidência)
- app.billing (hold, capture e release de créditos)
- app.core.outbox (fato de domínio condoshield.scan.requested)
- Celery: task condoshield.scan na fila isolada scanning e beat condoshield.reap na fila default
- app.core.ratelimit e app.api.idempotency na rota de varredura
- RLS FORCE nas tabelas condoshield_* (migration 0024)
Controle de acesso
O acesso é concedido por escopo, não por perfil genérico. Quem só precisa ler não recebe permissão de escrita, e a barreira é o banco de dados, não a interface.
- condoshield:site:read
- condoshield:site:write
- condoshield:device:read
- condoshield:device:write
- condoshield:scan:read
- condoshield:scan:write
- condoshield:finding:read
- condoshield:finding:write
Quer ver funcionando?
Demonstração com dado do seu ambiente, não com base de exemplo. É a única forma de saber se serve.