Este documento lista as principais vulnerabilidades e riscos de segurança identificados no backend (NestJS), frontend (Angular) e serviços auxiliares deste projeto, com base na análise do código-fonte presente no repositório.
- Stack backend: NestJS, Knex, PostgreSQL, JWT, WebSockets, integrações com WhatsApp (Uazapi e Meta), N8N, Supabase S3.
- Stack frontend: Angular 21, SPA com autenticação via JWT armazenado no
localStorage. - Modelo de segurança esperado: autenticação por JWT, multi-tenant por
account_id, rate limiting global viaThrottlerModule, webhooks autenticados por secrets.
- Estado atual:
- O backend (
backend/), o frontend (frontend/) e o webhook proxy (webhook-proxy/) estão no mesmo repositório (monorepo).
- O backend (
- Recomendação de separação de repositórios:
- Backend (NestJS):
- Criar um repositório dedicado apenas para o serviço de API/worker (código atual de
backend/), com seu próprio ciclo de CI/CD, versionamento e hardening.
- Criar um repositório dedicado apenas para o serviço de API/worker (código atual de
- Frontend (Angular):
- Criar um repositório separado para o SPA (código atual de
frontend/), isolando dependências de build frontend e pipeline de deploy estático.
- Criar um repositório separado para o SPA (código atual de
- Webhook Proxy (Cloudflare Worker):
- Manter o código de
webhook-proxy/em um terceiro repositório, focado apenas no worker de proxy de webhooks (KV, tenants, rotas/webhooke/tenants).
- Manter o código de
- Essa separação permite:
- Isolar superfícies de ataque (cada serviço com permissões mínimas necessárias).
- Aplicar políticas de segurança, revisão de código e deploy independentes para backend, frontend e proxy de webhooks.
- Backend (NestJS):
Apesar de existirem boas práticas (uso de helmet, CORS restritivo em produção, secrets em variáveis de ambiente, rate limiting), há vulnerabilidades graves que permitem:
- Escalada de privilégios
- Acesso ou modificação indevida de dados de outros tenants
- Comprometimento total do sistema em caso de vazamento de uma única chave
- Exposição de senhas em texto claro no banco de dados
- Encontrado (Crítico):
- Endpoints de WhatsApp (
/whatsapp/*) totalmente sem autenticação, permitindo operações sensíveis usando apenasusername. - API de permissões (
/permissions/:userId) sem controle de papel, permitindo que qualquer usuário autenticado altere permissões de qualquer outro. - Chave de API global (
API_KEY) que gera um “usuário” comrole: 'admin'eis_super_admin: true, usada emJwtOrApiKeyGuard. - Usuário especial
username === 'dev'emPermissionsServicecom poderes de super admin implícitos sobre boards.
- Endpoints de WhatsApp (
- Bom/OK:
- Fluxo de login usa
AuthGuard('local')+bcrypt+ JWT com expiração. - Estratégia JWT valida se o usuário está ativo.
- Fluxo de login usa
- Ações recomendadas:
- Colocar
@UseGuards(AuthGuard('jwt'))(ou guard específico) em todos os endpoints que hoje dependem apenas deusername. - Centralizar verificação de papel/escopo em um guard ou decorator (
RolesGuard) e aplicá-lo a rotas administrativas. - Remover ou limitar fortemente o “usuário dev” e a chave
API_KEYglobal, adotando chaves escopadas e auditadas.
- Colocar
- SQL Injection:
- O backend usa Knex com bindings em praticamente todos os pontos; onde há
raw, os parâmetros vêm de constantes ou bindings seguros. - Campos de busca (
searchem estatísticas e cards) usam.whereILikecom interpolação aplicada antes da query, fazendo com que Knex trate como parâmetro. - Não há uso direto de strings concatenadas em
whereRawcom input do usuário.
- O backend usa Knex com bindings em praticamente todos os pontos; onde há
- NoSQL / Command Injection:
- Não há uso de ORMs NoSQL nem de
child_process,eval,Function,exec,spawnno backend.
- Não há uso de ORMs NoSQL nem de
- Conclusão:
- Nenhum vetor óbvio de SQL/NoSQL/Command Injection foi identificado na análise estática.
- Risco residual depende da correta configuração da conexão e de futuros
raw(); recomenda‑se manter a regra de nunca concatenar input de usuário em SQL cru.
- Frontend (Angular):
- Não há uso de
innerHTML,DomSanitizer.bypassSecurityTrustHtmlou padrões semelhantes. - As páginas usam binding Angular padrão (
{{ }}e property binding), que já escapam HTML por padrão.
- Não há uso de
- Backend:
- API é JSON, sem construção manual de HTML a partir de input do usuário.
- Vetores indiretos:
- Conteúdos provenientes de contatos/mensagens podem conter texto arbitrário; se no futuro forem renderizados como HTML (por ex. via
[innerHTML]), poderão abrir XSS armazenado. - O uso de
localStoragepara JWT aumenta o impacto de um XSS, mesmo que hoje não haja vetores óbvios.
- Conteúdos provenientes de contatos/mensagens podem conter texto arbitrário; se no futuro forem renderizados como HTML (por ex. via
- Conclusão:
- Não foi identificado XSS direto no código atual, mas qualquer mudança futura que introduza
innerHTMLou renderização de HTML vindo do backend deve ser cuidadosamente auditada.
- Não foi identificado XSS direto no código atual, mas qualquer mudança futura que introduza
- Situação atual:
- A autenticação é feita por JWT em header
Authorization: Bearer, não por cookies. - Não há uso de cookies de sessão nem de CSRF tokens no backend.
- A autenticação é feita por JWT em header
- Impacto:
- Com o modelo atual (sem cookies de autenticação), o risco de CSRF clássico é baixo.
- Se no futuro o JWT for movido para cookie HttpOnly (recomendado para proteção contra XSS), será necessário adicionar proteção CSRF (tokens, SameSite adequado, etc.).
- Conclusão:
- Não há CSRF clássico hoje devido ao modelo de autenticação, mas o tema deve ser revisitado se o mecanismo de sessão mudar.
- Positivas:
- Uso de
helmetcom vários headers de segurança ativados. contentSecurityPolicydesativado apenas em dev; em produção segue o default do Helmet.CORS_ORIGIN='*'é explicitamente bloqueado em produção.ThrottlerModuleconfigurado globalmente com três buckets (short, medium, long).- Webhooks N8N e Meta validados via secretos (
N8N_WEBHOOK_SECRET,META_WHATSAPP_VERIFY_TOKEN,META_APP_SECRET).
- Uso de
- Problemas:
- Ausência de
ValidationPipeglobal – qualquer payload passa sem validação estruturada. @SkipThrottle()aplicado em webhooks, deixando-os sem rate limit em nível de aplicação.- Websocket gateway com CORS dependente de
CORS_ORIGIN; configuração incorreta pode abrir conexões de domínios não confiáveis.
- Ausência de
- Conclusão:
- Existem boas configurações base, mas a ausência de validação global e a desativação de throttling em rotas críticas são pontos fracos.
- O que foi verificado:
- Foi feita apenas inspeção estática dos
package.json(backend, frontend ewebhook-proxy). - As versões usadas (NestJS 11, Angular 21, socket.io 4.8.x, axios 1.13.x, etc.) são relativamente recentes.
- Foi feita apenas inspeção estática dos
- Limitações:
- Não foi possível rodar
npm audit/pnpm audit/ scanners SCA neste ambiente (sem acesso à base de vulnerabilidades em tempo real). - Portanto, não há garantia de que não existam CVEs conhecidas afetando as versões atuais.
- Não foi possível rodar
- Recomendações:
- Rodar em ambiente de CI / dev:
npm audit --omit=devno backend e frontend.npm outdatedpara avaliar updates menores/patch.
- Considerar o uso de ferramentas SCA contínuas (Dependabot, Renovate, Snyk, etc.).
- Rodar em ambiente de CI / dev:
- Identificado:
- Senhas de usuários em texto claro (
visible_password) no banco. users.instance_token(token de instância do WhatsApp Uazapi) armazenado em texto claro viaUsersService.updateInstanceToken.- Tokens de WhatsApp Meta:
whatsapp_meta_connections.access_tokenarmazenado em texto claro no banco.META_SYSTEM_USER_TOKENmantido apenas em variável de ambiente (OK), mas usado extensivamente em chamadas externas.
- Logs contendo:
- IDs de usuários, usernames, roles.
- Mensagens de erro completas de APIs externas (podendo conter detalhes internos).
- Senhas de usuários em texto claro (
- Riscos:
- Vazamento de backups ou dumps de banco expõem:
- Senhas reais (por causa de
visible_password). - Tokens de integrações com WhatsApp/Uazapi/Meta.
- Senhas reais (por causa de
- Vazamento de backups ou dumps de banco expõem:
- Ações recomendadas:
- Remover
visible_passworde resetar senhas dos usuários afetados. - Criptografar em repouso tokens de integrações (campo criptografado na tabela, com chave de criptografia gerenciada).
- Reduzir a verbosidade de logs em produção e mascarar campos sensíveis.
- Remover
- Casos principais:
- Atualização de canal de contatos (
updateContactChannel) sem verificação deaccount_id. - API de permissões permitindo auto‑elevação de privilégios.
- Endpoints WhatsApp expondo operações críticas baseadas apenas em
username. - Possíveis consultas/atualizações em outros services que usam somente
idsemaccount_id(risco multi‑tenant).
- Atualização de canal de contatos (
- Conclusão:
- Há múltiplas quebras de controle de acesso, tanto horizontais (entre usuários da mesma conta) quanto verticais (seller → admin).
- É necessário um esforço de revisão sistemática das queries e de quem pode chamar cada endpoint.
- Positivo:
- Uso de
bcryptcom salt para hash de senhas na colunapassword. - Uso de HTTPS/TLS pressuposto na infra (não gerenciado diretamente pelo código).
- Uso de
- Problemas:
- Armazenamento em texto claro de:
- Senhas (
visible_password). - Tokens de acesso a integrações (WhatsApp Meta, Uazapi).
- Senhas (
- Não há criptografia em repouso desses segredos no banco.
- Armazenamento em texto claro de:
- Recomendações:
- Manter apenas hashes irreversíveis para senhas.
- Introduzir camada de criptografia simétrica para tokens sensíveis no banco, com rotação de chaves.
- Pontos positivos:
AllExceptionsFiltercentraliza e estrutura logs de erro.ActivityLogsServiceregistra eventos de negócio relevantes (criação/remoção de anexos, ações em cards etc.).- Diversos serviços logam operações importantes (WhatsApp, webhooks, worker/proxy).
- Possíveis melhorias:
- Não há camadas explícitas de:
- Detecção de tentativas repetidas de login (além do Throttler genérico).
- Alertas em caso de falhas de autenticação repetidas ou erros críticos (integrado a sistemas de monitoramento).
- Logs podem conter dados sensíveis se não forem sanitizados antes de ir para provedores externos.
- Não há camadas explícitas de:
- Conclusão:
- Logging existe e é razoavelmente estruturado, mas faltam:
- Integração com ferramentas de observabilidade/alertas.
- Uma política clara de mascaramento de dados sensíveis e retenção de logs.
- Logging existe e é razoavelmente estruturado, mas faltam:
-
Armazenamento de senhas em texto claro (
visible_password)- O serviço de usuários e o serviço de sistema armazenam a senha original em campo
visible_passwordno banco:UsersService.createecreateSellerinseremvisible_passwordcom a senha em texto claro.UsersService.updatePasswordatualizavisible_passwordcom a nova senha em texto claro.SystemService.createCustomerAdmintambém gravavisible_passwordcom a senha fornecida.
- Impactos:
- Qualquer acesso ao banco (DBA, invasor, vazamento de backup) revela instantaneamente as senhas reais dos usuários.
- Usuários que reutilizam senhas entre sistemas têm contas externas comprometidas.
- Quebra completa de requisitos de LGPD/GDPR e boas práticas de segurança.
- Gravidade: Crítica – deve ser removido imediatamente, migrando para armazenamento apenas do hash.
- O serviço de usuários e o serviço de sistema armazenam a senha original em campo
-
Endpoints WhatsApp sem autenticação (públicos)
WhatsappControllernão usa@UseGuards(AuthGuard('jwt'))nem outro guard.- Endpoints acessíveis anonimamente:
POST /whatsapp/connect– inicia/gera instância WhatsApp para umusername.GET /whatsapp/status– obtém status portokenouusername.PUT /whatsapp/ai-settings– altera flags de IA de um usuário porusername.PUT /whatsapp/test-numbers– altera números de teste porusername.GET /whatsapp/ai-settings,GET /whatsapp/account-settings,PUT /whatsapp/account-settings,PUT /whatsapp/professionals,PUT /whatsapp/payment-methods– expõem e permitem alterar configurações de conta com base apenas nousername.
- Impactos:
- Qualquer pessoa que saiba (ou consiga adivinhar)
usernamepode:- Iniciar/roubar conexão WhatsApp de uma loja.
- Ler configurações sensíveis de conta.
- Alterar configurações de IA, profissionais, formas de pagamento etc.
- Possível sequestro de conta e vazamento de dados de contatos/conversas via integrações.
- Qualquer pessoa que saiba (ou consiga adivinhar)
- Gravidade: Crítica – todos estes endpoints precisam ser protegidos por
AuthGuard('jwt')e validações de autorização poraccount_id.
-
Escalada de privilégios via API de permissões
PermissionsControllerestá protegido por JWT, porém sem checagem de papel (role):GET /permissions/:userId– qualquer usuário autenticado pode ler permissões de qualquer outro usuário.PUT /permissions/:userId– qualquer usuário autenticado pode atualizar permissões de qualquer usuário (incluindo si próprio).
- O
PermissionsService.updatePermissionsnão faz nenhum controle de quem está alterando, apenas grava as flags. - Impactos:
- Um usuário com papel
sellerpode se dar acesso total a outros painéis (viacan_view_other_boards,can_edit_other_boardsetc.). - Permite contornar qualquer lógica de autorização baseada nessas permissões.
- Um usuário com papel
- Gravidade: Crítica – é necessário restringir estes endpoints a
admin/owner/superadmin(e ainda assim com regras de negócio claras).
-
Chave de API global com privilégios de superadmin
ApiKeyStrategyautentica requisições via headerX-API-Keyusando um único valorprocess.env.API_KEY.- Em caso de sucesso:
- O usuário retornado tem
role: 'admin',is_super_admin: true,via_api_key: true. - Esse “usuário” tem acesso total aos recursos protegidos pelo guard
JwtOrApiKeyGuard.
- O usuário retornado tem
- Impactos:
- Vazamento da variável
API_KEY(log, CI, repositório, fornecedor) compromete toda a aplicação. - Não há rotação granular por cliente/integração; tudo depende de um único segredo.
- Vazamento da variável
- Gravidade: Alta – exigir chaves por tenant/integração, com escopo e rotação, ou substituir por OAuth/jwt de serviço.
-
Quebra de controle de acesso em atualização de canal de contato
ChannelsController.updateContactChannel(rotaPUT /channels/contact/:contactId) não usaaccount_iddo usuário autenticado.ChannelsService.updateContactChannelapenas:- Verifica se o
channelIdexiste. - Atualiza o contato (
contacts.id = contactId) sem checar se o contato pertence à mesma conta (account_id).
- Verifica se o
- Impactos:
- Usuário autenticado pode alterar o canal (
channel_id) de contatos de outros tenants, se souber ocontactId. - Embora IDs sejam UUIDs, podem ser obtidos via outras APIs/erros/logs.
- Usuário autenticado pode alterar o canal (
- Gravidade: Alta – precisa validar
contacts.account_id = req.user.account_idantes de atualizar.
-
Ausência de validação de entrada centralizada
- O projeto usa
class-validator/class-transformer, mas:- Não há
ValidationPipeglobal configurado emmain.ts. - A maioria dos controllers recebe
@Body()e@Query()como objetos “soltos” (tipados só em TypeScript ouany).
- Não há
- Impactos:
- Falta de validação de tipos, tamanhos, formatos e whitelisting de campos.
- Maior superfície para:
- Quebras lógicas (valores inesperados).
- DoS (payloads grandes em campos não controlados).
- Problemas de segurança em integrações a jusante (por exemplo, dados não sanitizados indo para terceiros).
- O uso de Knex com bindings minimiza SQL Injection, mas não elimina outros vetores.
- Gravidade: Média/Alta – recomendável habilitar
ValidationPipeglobal comwhitelisteforbidNonWhitelisted.
- O projeto usa
-
Endereços multi-tenant nem sempre filtrados por
account_id- Em vários serviços/controladores a conta é considerada (por exemplo,
BoardsService,StatisticsService,TasksService,CommentsServiceusamaccount_id). - Porém existem pontos onde a operação é feita apenas por
idsem forçaraccount_id, ou sem passar oaccount_idpara o service (por exemplo, alguns métodos deCardsService,ContactsService,ChannelsService). - Impactos:
- Risco de acesso/alteração de registros de outros tenants se o ID for conhecido.
- Dificulta garantir isolamento total de dados entre contas.
- Gravidade: Média – revisar sistematicamente se todos os SELECT/UPDATE/DELETE multi-tenant usam sempre
account_iddo usuário autenticado.
- Em vários serviços/controladores a conta é considerada (por exemplo,
-
Webhooks com rate limit desativado
WebhooksController(/webhooks/n8n) eWhatsappMetaWebhookController(/webhooks/meta/whatsapp) usam@SkipThrottle().- Autenticação:
- Webhooks N8N usam
WebhookGuardcom headerx-webhook-tokencomparado aN8N_WEBHOOK_SECRETusandotimingSafeEqual(bom). - Webhook Meta usa
META_WHATSAPP_VERIFY_TOKENna verificação de assinatura e tenta validarx-hub-signature-256com HMAC.
- Webhooks N8N usam
- Impactos:
- Ataques de DoS são possíveis se alguém descobre o endpoint (mesmo sem quebrar o segredo), pois não há rate limit adicional.
- Dependência forte da confidencialidade das variáveis de ambiente.
- Gravidade: Média – aceitável para webhooks de alta taxa, mas recomenda-se filtros adicionais (IP allowlist, camada WAF/CDN).
-
Logs potencialmente verbosos com dados sensíveis
AllExceptionsFilterloga detalhes de erros incluindo:- URL, método, mensagem, tipo de erro, e usuário (id, username, role).
- Serviços de WhatsApp logam respostas de erro completas da API externa.
- Impactos:
- Em ambientes de produção sem adequado gerenciamento de logs, informações sensíveis podem ir para provedores de log externos.
- Não detectei logs diretos de tokens/senhas, mas vale revisar formatos e reter o mínimo necessário.
- Gravidade: Baixa/Média – monitorar sanitização de logs e política de retenção/armazenamento.
-
Configuração de CORS dependente de variável de ambiente
main.ts:parseCorsOriginsimpedeCORS_ORIGIN='*'em produção (bom).- Em dev, permite
'*'ou origens padrãohttp://localhost:4200/4201.
- Impactos:
- Se
CORS_ORIGINfor configurado de forma muito ampla em produção (por exemplo, incluindo domínios terceiros não confiáveis), aumenta risco de ataques cross-origin (principalmente se no futuro usar cookies).
- Se
- Gravidade: Baixa – desde que
CORS_ORIGINseja bem gerenciado em produção.
-
Armazenamento de JWT em
localStorageAuthServicearmazena oaccess_tokenemlocalStorage:accessToken = signal<string | null>(localStorage.getItem('access_token'));localStorage.setItem('access_token', token);
- Outros serviços leem diretamente do
localStorage(vehicles.service,boards.service,features.serviceetc.). - Impactos:
- Em caso de qualquer XSS, o atacante pode ler o token e assumir a sessão do usuário.
- Tokens não são protegidos contra exfiltração como seriam em cookies HttpOnly.
- Gravidade: Alta em cenários com risco real de XSS – ideal migrar para cookies HttpOnly + SameSite + Secure, ou endurecer fortemente contra XSS.
-
Autorização confiando demais em guards de frontend
- Guards como
adminOrOwnerGuard(permission.guard.ts) usam apenas oroledecodificado do JWT no cliente. - O backend, contudo:
- Não valida papel em alguns endpoints sensíveis (por exemplo, permissões, alguns endpoints de sistema e WhatsApp).
- Impactos:
- Mesmo que a UI esconda botões e rotas, usuários mal-intencionados podem chamar APIs diretamente (via Postman, curl, etc.).
- A segurança não pode depender apenas de guards do frontend.
- Gravidade: Alta – o backend precisa replicar (e reforçar) a lógica de autorização que hoje só existe na UI.
- Guards como
-
Uso de
localStoragepara cache de feature flagsFeaturesServicesalvafeature_flagsemlocalStorage.- Impactos:
- Risco é menor (não são segredos), mas qualquer XSS pode alterar flags e mudar comportamento da UI (por exemplo, esconder áreas de visibilidade).
- Gravidade: Baixa – aceitável, desde que o backend continue validando permissão independentemente das flags.
-
Senhas
- Remover completamente o campo
visible_passworddo código e do banco. - Criar migração para limpar esse campo e, idealmente, excluir a coluna.
- Remover completamente o campo
-
Autenticação e Autorização
- Proteger
WhatsappController(e qualquer outro controller sensível) comAuthGuard('jwt'). - Em todos os endpoints administrativos/sistema (permissões, system bootstrap, WhatsApp oficial, etc.), checar:
- Papel:
admin/owner/is_super_admin. account_idcoerente com o recurso acessado.
- Papel:
- Restringir
PermissionsController.updatePermissionse similares a perfis autorizados.
- Proteger
-
Multi-tenant
- Padronizar todas as consultas/atualizações para sempre incluírem
account_iddo usuário autenticado. - Revisar especialmente services que recebem apenas
id(comoupdateContactChannel).
- Padronizar todas as consultas/atualizações para sempre incluírem
-
Chaves e Webhooks
- Substituir a chave única
API_KEYpor um modelo de múltiplas chaves por tenant/integração com escopo e revogação. - Para webhooks com
@SkipThrottle, considerar:- Proteção adicional via IP allowlist, WAF ou API Gateway.
- Monitoramento ativo de volume e abuso.
- Substituir a chave única
-
Validação de Entrada
- Habilitar
ValidationPipeglobal com:whitelist: trueforbidNonWhitelisted: truetransform: true
- Criar DTOs com
class-validatorpara todos os endpoints que recebem body/query sensíveis.
- Habilitar
-
Frontend
- Considerar migrar de
localStoragepara cookies HttpOnly/SameSite para o JWT, ou aplicar CSP rigorosa e mitigação forte de XSS. - Tratar guards apenas como camada de UX; toda autorização real deve ser replicada/forçada no backend.
- Considerar migrar de
O projeto já possui diversas boas práticas (Helmet, Throttler, CORS configurável, uso de JWT, secrets via .env), mas as vulnerabilidades críticas destacadas (principalmente senhas em texto claro, endpoints WhatsApp públicos, escalada de privilégios via permissões e chave de API global superadmin) expõem a aplicação a comprometimento total em caso de exploração.
As correções sugeridas acima devem ser priorizadas em ordem de gravidade, começando pelas que podem levar à tomada completa do ambiente ou vazamento massivo de dados.