Files
b2bcall/docs/SECURITY.md
B2BCall Bootstrap 80e72881b2 feat: Fase 9/10 — métricas, scripts operacionais, callback/wrap-up e aceite final
Fase 9 (segurança e produção):
- GET /api/metrics: endpoint Prometheus com métricas reais (chamadas,
  agentes, filas, CPS por campanha), protegido por permissão
- scripts/backup.sh, restore.sh, healthcheck.sh, install.sh, update.sh —
  testados contra o ambiente real (backup.sh e healthcheck.sh rodados de
  verdade; install.sh/update.sh validados por inspeção, ambiente atual já
  provisionado)
- POST /api/agent-console/dispose: aplica disposição de chamada de verdade
  (lacuna deixada aberta desde a Fase 6), com ações CALLBACK (agenda
  retorno) e DO_NOT_CALL (suprime automaticamente)
- apps/dialer-worker/src/callback-sweep.ts: reativa leads com callback
  vencido; GET /api/callbacks para consulta
- apps/dialer-worker/src/wrap-up-sweep.ts: transição automática
  WRAP_UP -> AVAILABLE + despausa real na fila do Asterisk. Exigiu corrigir
  main.ts para conectar ao AMI mesmo em DIALER_SIMULATION=true
  (DIALER_SIMULATION deve impedir só originação de chamada, não ações
  administrativas de fila)
- nftables revisado (sem alterações necessárias)
- Documentação completa: INSTALL, OPERATIONS, BACKUP_RESTORE, SECURITY,
  DATABASE, API, ASTERISK, OPENSIPS (não implementado, motivo
  documentado), TROUBLESHOOTING
- README.md e CHANGELOG.md reescritos

Fase 10 (testes e aceite):
- Quality gate completo executado: build/typecheck (7 workspaces), lint,
  46 testes unitários, docker compose config/ps, healthcheck — tudo verde
- Aceite de segurança (seção 92): 13 itens verificados ao vivo contra o
  sistema real, não só por inspeção de código
- Aceite Asterisk (seção 93): os 5 comandos executados e documentados,
  comunicação API->AMI->Asterisk validada
- docs/RELATORIO_FINAL.md: relatório final no formato da seção 96

Todos os fixtures de teste desta fase foram removidos/desativados ao
final. Credenciais de acesso entregues separadamente em CREDENCIAIS.txt
(fora do git, nunca versionado).
2026-08-27 19:16:14 -03:00

5.6 KiB

B2BCall — Segurança

Autenticação

  • Senhas com Argon2id (argon2 lib), nunca bcrypt/md5/sha.
  • JWT de acesso (curta duração, JWT_ACCESS_TTL) + refresh token opaco (hash SHA-256 armazenado, nunca o token em texto puro) com rotação: a cada uso, o refresh antigo é revogado e um novo par é emitido.
  • Cookies HttpOnly, SameSite=Lax, Secure quando HTTPS estiver ativo. SameSite=Lax é a mitigação primária de CSRF (o navegador não envia o cookie em requisições cross-site que não sejam navegação de topo) — não há token CSRF de double-submit adicional. Suficiente para o modelo de ameaça atual (rede privada, sem terceiros hospedando conteúdo que chame a API); reavaliar se o frontend algum dia rodar em uma origem publicamente diferente da API.
  • Rate limiting progressivo de login por IP via Redis (LoginThrottleService): backoff exponencial a cada bloco de tentativas falhas, nunca bloqueio permanente sem reset.
  • Resposta de "esqueci minha senha" sempre genérica, independente de o e-mail existir (evita enumeração de usuários).
  • Troca de senha revoga todas as sessões ativas do usuário.

Autorização (RBAC)

  • UserUserRoleRoleRolePermissionPermission.
  • Catálogo de permissões centralizado (packages/shared/src/permissions.ts) — nenhuma checagem de permissão usa string solta espalhada pelo código.
  • PermissionsGuard global, aplicado via @RequirePermissions(...) em cada rota que precisa (rotas sem decorator exigem apenas autenticação).
  • Ninguém pode alterar os próprios perfis de acesso (users.service.ts#update), mesmo tendo permissão de gerenciar usuários — bloqueia auto-elevação mesmo por um super_admin comprometido/mal configurado.

Criptografia de segredos

  • Trunk.secretEncrypted / Extension.sipPasswordEncrypted: AES-256-GCM (packages/shared/src/secret-crypto.ts), chave mestra SECRETS_MASTER_KEY (.env, nunca no banco, nunca versionada).
  • Nenhum segredo (senha de tronco, senha de ramal, tokens) é retornado em GET/PATCH — só uma vez, no momento da criação, quando aplicável.

Auditoria

  • AuditService registra toda ação sensível (login, criação/edição/exclusão de entidades, disposição de chamada, mudanças de compliance, etc.) com userId, ipAddress, userAgent, estado antes/depois.
  • Redação automática de campos sensíveis (SENSITIVE_KEYS) antes de persistir before/after — senha, secret, token nunca aparecem em texto puro no log de auditoria.
  • Logs estruturados JSON (nestjs-pino/pino) com request_id de correlação — mesma política de redação se aplica.

Rede

  • Único ponto exposto à LAN: Nginx, porta 80 (443 quando HTTPS configurado — docs/OPERATIONS.md).
  • Postgres/Redis nunca publicados fora de 127.0.0.1/rede Docker interna.
  • AMI (5038), ARI (8088/8089), SIP (5060/5061), RTP (10000-20000) bloqueados da interface LAN por nftables (infrastructure/nftables/b2bcall.nft), mesmo o Asterisk rodando em network_mode: host. SSH nunca bloqueado.
  • CORS: origin restrito a uma allowlist configurável (ALLOWED_ORIGINS), credentials: true só para essas origens.
  • Helmet (@fastify/helmet) ativo: X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, Cross-Origin-Opener-Policy, etc.

Validação de entrada

  • Todo DTO usa class-validator (ValidationPipe global, whitelist: true, forbidNonWhitelisted: true) — payload com campo não esperado é rejeitado, não ignorado silenciosamente.
  • Toda query ao banco via Prisma (parametrizado) — as únicas duas exceções ($queryRaw em lead-repository.ts e agent-call-binding.ts) usam apenas parâmetros tipados interpolados pelo próprio Prisma ($queryRaw\...${valor}...``), nunca concatenação de string.
  • Comandos de diagnóstico do Asterisk expostos via API usam allowlist explícita de comandos (GET /api/asterisk/diagnostic/allowed-commands) — nunca shell livre nem asterisk -rx "<input do usuário>" sem filtro.

Checklist de aceite de segurança (agente.md seção 92)

Item Status
Senhas com hash forte (Argon2id)
Nenhuma senha padrão (bootstrap sempre aleatório)
RBAC aplicado no backend (não só escondido na UI)
Agente não eleva a própria permissão
Segredos de tronco/ramal cifrados em repouso
Postgres/Redis não expostos à LAN
AMI/ARI/SIP não expostos à LAN (nftables)
Rate limiting de login
Auditoria de ações sensíveis, sem vazar segredos
Logs estruturados sem credenciais
.env/segredos nunca versionados no git
Validação de entrada em todos os DTOs
Sem SQL injection (Prisma parametrizado em toda parte)
Sem shell injection (allowlist de comandos Asterisk)
CSRF ⚠️ Mitigado via SameSite=Lax, sem token dedicado (ver acima)
HTTPS ⚠️ Não configurado nesta fase (rede privada) — ver docs/OPERATIONS.md

Como reportar/tratar um incidente

  1. Revogar sessões: UPDATE sessions SET revoked_at = now() WHERE revoked_at IS NULL; (ou endpoint de logout em massa, se necessário adicionar).
  2. Rotacionar segredos: gerar novos valores com scripts/generate-secrets.sh, atualizar .env, reiniciar api. Rotacionar SECRETS_MASTER_KEY exige redescriptografar e recriptografar todos os Trunk.secretEncrypted/Extension.sipPasswordEncrypted (não automatizado nesta fase — script futuro se necessário).
  3. Consultar AuditLog filtrando por userId/entityType/janela de tempo para reconstituir o que aconteceu.