Files
b2bcall/docs/SECURITY.md
B2BCall Bootstrap 675806314d docs: registrar liberação de SIP/RTP no firewall e confusão de senhas
Atualiza TODO.md/SECURITY.md para refletir a regra de nftables real (SIP/
RTP liberados para RFC1918, AMI/ARI seguem bloqueados incondicionalmente)
e documenta em TROUBLESHOOTING.md o caso completo: timeout de registro de
softphone causado pelo firewall + confusão entre senha de login web e
senha SIP do ramal (duas credenciais diferentes).
2026-08-27 21:33:32 -03:00

6.3 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 sem restrição de origem: 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) e ARI (8088/8089) bloqueados incondicionalmente da interface LAN por nftables (infrastructure/nftables/b2bcall.nft), mesmo o Asterisk rodando em network_mode: host — nenhum softphone ou host externo deve alcançá-los, só a própria aplicação via rede interna.
  • SIP (5060/udp) e RTP (10000-20000/udp) são liberados na interface LAN, mas só para origem dentro do RFC1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) — necessário para softphones/ramais reais de agentes registrarem, já que o OpenSIPS que ficaria na frente (docs/OPENSIPS.md) não foi implantado nesta fase. Origem fora do RFC1918 (internet pública) continua bloqueada — o Asterisk nunca deve receber SIP de fora da rede privada (agente.md seção 5). 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 não expostos à LAN (nftables, incondicional)
SIP/RTP restritos a origem RFC1918 na LAN (nftables — liberado deliberadamente para softphones reais)
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.