- docs/AUDITORIA_BASE_2GT.md: full audit of upstream 1a4b635 (v0.24.5-beta)
- 0 critical, 1 high (SEC-002: no per-site RBAC), 4 medium, 5 low, 5 informational
- Verdict: APROVADO COM CORREÇÕES OBRIGATÓRIAS
- First read-only RB: authorized (SIM)
- test: add regression coverage proving SEC-001 (no JWT revocation) and
SEC-002 (site scoping has no per-user authorization component)
- docs/SISTEMA_ATUAL.md: record confirmed facts (RBAC scope, Change Guard
coverage, RouterOS TLS verification) surfaced by the audit
No production code changed — audit-only per mission scope.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
23 KiB
Auditoria Técnica e de Segurança — Base Upstream 2GT (MikroTik Manager)
- Commit auditado:
1a4b6350528b8c656ab7a84ee4806a8a86cf9932(v0.24.5-beta) - Branch:
mikrormm-dev, sobre bootstrap7a4979e - Escopo: código-fonte completo (backend, frontend, Docker), sem RouterBOARD real conectada
- Método: leitura de código, grep dirigido, testes automatizados (jest/vitest),
npm audit, análise de Dockerfiles/compose. Nenhuma credencial real foi usada ou impressa.
Mapa factual da arquitetura
- Frontend: React 19 + Vite, SPA servida por nginx, autentica via Bearer JWT em
localStorage. - Backend: Node/Express (
backend/src/index.ts), monta ~28 routers sob/api/*, todos comrouter.use(requireAuth)no topo do arquivo exceto os endpoints públicos explícitos deoidc.ts(/status,/login,/callback). - Autenticação:
routes/auth.ts+middleware/auth.ts— JWT HS256, bcryptjs, TOTP opcional (otpauth), OIDC opcional (openid-client). - Autorização: 3 papéis globais (
admin/operator/viewer) checados emmiddleware/auth.ts(requireAdmin,requireWrite). Sem escopo por site/tenant. - Banco: PostgreSQL, schema em
db/migrate.ts(migrations idempotentes,CREATE TABLE IF NOT EXISTS+ALTERincrementais), executado automaticamente no boot do backend. - Redis/BullMQ: filas de polling/automação; rate limiting (
middleware/rateLimitRedis.ts) também usa Redis com fallback em memória. - InfluxDB: métricas de série temporal (
config/influxdb.ts, consultado emroutes/metrics.ts). - Jobs/Scheduler/Polling:
services/PollerService.ts,services/DeviceBulkAddWorker.ts. - Alertas:
services/AlertService.ts+routes/alerts.ts. - RouterOS integration:
services/mikrotik/RouterOSClient.ts(API binário RouterOS, TCP/TLS),services/mikrotik/DeviceCollector.ts. - SSH:
services/sshExec.ts,services/SshKeyService.ts,services/BackupService.ts(ssh2), terminal interativo via Socket.IO (index.tsnamespace/terminal). - API: ~28 arquivos de rota, ~250 endpoints.
- SNMP: não encontrada implementação SNMP no backend (ausente — não fazia parte do escopo upstream atual).
- NetFlow:
services/netflow/NetflowCollector.ts, coletor UDP na porta 2055, exposto nodocker-compose.yml. - Backups:
services/BackupService.ts+routes/backups.ts, arquivos.rsc(/export compact) embackups_datavolume. - Firmware upgrade:
services/FirmwareOrchestrator.ts+routes/firmware.ts, waves com concorrência limitada (utils/concurrency.ts,MAX_WAVE_CONCURRENCY=20). - Terminal: Socket.IO namespace
/terminal, restrito aadmin/operator, com auditoria e rate limit. - Change Guard:
services/changeGuard/ChangeGuard.ts— dead-man-switch no próprio dispositivo (scheduler RouterOS que desfaz a mudança se a conexão não for reconfirmada). - Auditoria:
middleware/auditMiddleware.ts(global) + tabelaaudit_log, consultável viaroutes/auditLog.ts(admin-only).
Achados
SEC-001 — Sessões JWT sem revogação server-side
Severidade: MÉDIO
Componente: backend/src/middleware/auth.ts, routes/auth.ts
Evidência: signToken/verifyToken não consultam o banco; POST /api/auth/logout só responde OK sem invalidar nada ("JWT is stateless; client just discards token"); PUT /api/auth/password e POST /api/auth/totp/disable não alteram nenhum estado que verifyToken observe. Comprovado por teste automatizado (middleware/__tests__/auditFindings.test.ts, SEC-001).
Causa-raiz: design stateless sem jti/versão de sessão nem lista de revogação.
Impacto: um token vazado (XSS, log, dispositivo comprometido) continua válido por até 24h mesmo depois de o usuário trocar a senha, desabilitar TOTP ou ser removido/rebaixado por um admin — só uma rotação de JWT_SECRET (que derruba TODAS as sessões) resolve.
Exploração/condição: requer vazamento prévio do token; não é explorável remotamente sem esse pré-requisito.
Recomendação: adicionar jti + tabela de revogação (ou coluna token_version em users, checada em requireAuth), e reduzir expiresIn para algo como 1–4h com refresh token.
Status: OPEN
SEC-002 — "Sites" são um filtro de exibição, não um limite de autorização
Severidade: ALTO (crítico para uso multi-cliente GTI; baixo para uso interno de um único tenant)
Componente: backend/src/utils/siteScope.ts, middleware/site.ts, schema users
Evidência: siteContext lê X-Site-Id do cliente sem checar identidade; resolveSiteId não recebe req.user; tabela users (db/migrate.ts:6-12) não tem nenhuma coluna/relação com sites. Comprovado por teste (auditFindings.test.ts, SEC-002) e por inspeção do schema.
Causa-raiz: o recurso "sites" (#130) foi implementado como agrupamento operacional (dashboards por site), não como fronteira de tenant.
Impacto: qualquer usuário autenticado — mesmo viewer — enxerga e (se operator/admin) opera sobre TODOS os dispositivos de TODOS os sites simplesmente omitindo o header X-Site-Id. Se a GTI pretende dar a um técnico/cliente acesso restrito a "seus" equipamentos, essa garantia não existe hoje.
Recomendação: antes de onboarding com múltiplos clientes reais no mesmo backend, implementar RBAC por site (tabela user_sites, checagem no middleware, nunca confiar em header sem cruzar com o usuário).
Status: OPEN
SEC-003 — RouterOS API-SSL sem verificação de certificado
Severidade: MÉDIO
Componente: backend/src/services/mikrotik/RouterOSClient.ts:97
Evidência: tls.connect({ host, port, rejectUnauthorized: false }, ...).
Causa-raiz: RouterOS normalmente usa certificado autoassinado; verificar CA exigiria gerenciamento de certificados por dispositivo, não implementado.
Impacto: conexão à RouterBOARD é suscetível a MITM na rede entre backend e device — um atacante na mesma rede/segmento poderia interceptar credenciais RouterOS e comandos.
Recomendação: oferecer trust-on-first-use com fixação de fingerprint por dispositivo (guardar o hash do cert na primeira conexão e alertar/rejeitar se mudar), em vez de desabilitar globalmente.
Status: OPEN
SEC-004 — Chaves de criptografia default nunca expiram como fallback de decrypt
Severidade: MÉDIO
Componente: backend/src/utils/secrets.ts:154-161, utils/crypto.ts
Evidência: encDecryptors sempre inclui KNOWN_DEFAULT_ENC (as duas chaves públicas do repositório) na lista de chaves tentadas para decrypt, permanentemente, mesmo depois de anos de rotação.
Causa-raiz: decisão deliberada de nunca quebrar dados antigos cifrados com a chave default (documentada no próprio código).
Impacto: se uma instalação alguma vez rodou sem ENCRYPTION_KEY customizada (mesmo que temporariamente) e um pg_dump daquele período vazar, os segredos daquele período são decifráveis com uma chave pública e conhecida deste repositório — para sempre.
Recomendação: aceitável como tradeoff de migração, mas documentar explicitamente no runbook de produção; considerar expurgar as chaves default do array de decrypt após confirmar (via needsReencryption) que nenhum registro as usa mais.
Status: ACCEPTED (mitigação: ENCRYPTION_KEY DEV já é forte e aleatória desde a Missão 00; nunca esteve no default)
SEC-005 — Change Guard cobre apenas um subconjunto pequeno das operações de escrita
Severidade: MÉDIO (expectativa vs. realidade)
Componente: backend/src/routes/devices.ts (único chamador de withSafeApply), routes/commands.ts, terminal Socket.IO
Evidência: grep -rl withSafeApply routes/ retorna apenas devices.ts, usado no fluxo de "preflight"/apply genérico de mudanças analisadas (analyzeChange). As dezenas de endpoints diretos de firewall, NAT, rotas, VLAN, bonds, endereços IP, interfaces (mesmo arquivo devices.ts) chamam a API RouterOS diretamente, sem snapshot nem dead-man-switch. commands.ts (execução em massa) e o terminal SSH interativo também não passam por ChangeGuard.
Causa-raiz: Change Guard foi construído para o fluxo guiado de "mudança arriscada analisada", não como interceptador universal de toda escrita.
Impacto: um operador pode remover uma rota, uma regra de firewall ou uma interface via endpoint direto (ou via terminal/comando em massa) e perder conectividade com o dispositivo sem qualquer rede de segurança automática.
O que o Change Guard REALMENTE garante: para o fluxo que passa por withSafeApply, garante rollback automático se a nova conexão de verificação falhar, com um restore point salvo no próprio dispositivo antes da mudança.
O que ele NÃO garante: proteção em firewall/NAT/rotas/VLAN/interfaces editados diretamente, em commands.ts (bulk), no terminal interativo, em configHistory.ts rollback, ou contra falha de hardware/queda de energia do device.
Recomendação: documentar essa cobertura parcial para os operadores GTI e avaliar estender withSafeApply aos endpoints de firewall/rotas/VLAN mais usados antes de operar em RBs de produção.
Status: OPEN (documentação atualizada nesta auditoria)
SEC-006 — Injeção de comando RouterOS via ssh_username não sanitizado
Severidade: BAIXO
Componente: backend/src/services/SshKeyService.ts:196-204
Evidência: `/user/ssh-keys/import public-key-file=${fileName} user=${username}` e `/file/remove ${fileName}` interpolam username/fileName sem escaping. fileName é gerado internamente (seguro); username vem de target.ssh_username, campo configurável por qualquer usuário operator/admin ao cadastrar o dispositivo.
Causa-raiz: ausência de validação/allowlist de caracteres para ssh_username.
Impacto: um operador que já tem permissão de configurar credenciais de um dispositivo (logo, já tem acesso legítimo a ele) poderia usar um ssh_username malformado para injetar comandos RouterOS adicionais na mesma sessão SSH. Não é escalação de privilégio (o ator já controla o dispositivo), mas quebra o princípio de defesa em profundidade.
Recomendação: validar ssh_username contra o alfabeto aceito pelo RouterOS para nomes de usuário antes de interpolar em qualquer comando.
Status: OPEN
SEC-007 — nginx:alpine sem tag de versão e demais imagens sem pinning por digest
Severidade: BAIXO (reprodutibilidade/supply chain)
Componente: nginx/Dockerfile:12, docker-compose.yml
Evidência: FROM nginx:alpine (sem versão nenhuma); FROM node:26-alpine; postgres:15-alpine, redis:7-alpine, influxdb:2.7 — nenhuma imagem fixada por digest sha256.
Impacto: dois builds em datas diferentes podem produzir imagens de base diferentes (patches de SO, ou até uma nova major do nginx), reduzindo a reprodutibilidade entre DEV e PROD e dificultando auditoria de supply chain.
Recomendação: fixar ao menos a versão do nginx (nginx:1.27-alpine) e considerar pinning por digest em PROD.
Status: OPEN
SEC-008 — Dockerfiles usam npm install em vez de npm ci
Severidade: BAIXO
Componente: backend/Dockerfile, nginx/Dockerfile (estágio frontend-builder)
Evidência: ambos os estágios de build rodam npm install em vez de npm ci, apesar de existir package-lock.json no repositório.
Impacto: npm install pode resolver versões ligeiramente diferentes das travadas no lockfile em rebuilds futuros (dentro dos ranges semver), reduzindo reprodutibilidade bit-a-bit do build.
Recomendação: trocar para npm ci nos dois Dockerfiles.
Status: OPEN
SEC-009 — qs/body-parser/express — DoS moderado (transitivo)
Severidade: BAIXO
Componente: dependência transitiva qs (via express@4.22.2 → body-parser)
Evidência: npm audit --omit=dev no backend reporta 3 vulnerabilidades moderadas (GHSA-x5fp-wj9c-mxmx, GHSA-4mjr-xmp4-gh2g) — bypass de limite de array / DoS via isBuffer manipulado.
Contexto de exploração: a API já tem rate limit global (120 req/min/IP, rateLimitRedis), reduzindo o valor prático do DoS. Correção disponível via npm audit fix dentro do range semver do Express 4.x.
Recomendação: aplicar o fix na próxima janela de manutenção de dependências (fora do escopo desta missão — "não atualizar dependências" foi respeitado aqui).
Status: OPEN (não corrigido nesta missão, por instrução explícita da missão)
SEC-010 — Rate limit de login é só por IP, sem lockout por conta
Severidade: BAIXO/INFORMATIVO
Componente: backend/src/middleware/rateLimitRedis.ts (loginRateLimit)
Evidência: chave rl:login:ip:${ip}; nenhuma chave por username.
Impacto: um atacante distribuído (múltiplos IPs/proxies) não é throttled por conta — mas a mensagem de erro é genérica ("Invalid credentials") em ambos os casos (usuário inexistente ou senha errada), então não há enumeração de usuários.
Recomendação: considerar um limite adicional por username além do IP.
Status: ACCEPTED (mitigação parcial já existe; risco baixo)
SEC-011 — Política de senha mínima aceitável, sem verificação de senha vazada
Severidade: INFORMATIVO
Componente: backend/src/utils/passwordPolicy.ts
Evidência: mínimo 10 caracteres, 1 letra, 1 número; sem checagem contra listas de senhas vazadas (ex. HaveIBeenPwned) nem bloqueio de senhas triviais como Password123.
Recomendação: aceitável para baseline; considerar reforço futuro.
Status: ACCEPTED
SEC-012 — Criptografia de credenciais RouterOS (AES-256-GCM) — bem desenhada
Severidade: INFORMATIVO (achado positivo)
Componente: backend/src/utils/crypto.ts, utils/secrets.ts
Evidência: AES-256-GCM com IV aleatório de 16 bytes por operação, tag de autenticação verificada (setAuthTag/getAuthTag), chave derivada via SHA-256 quando não tem exatamente 32 bytes, chave nunca persistida no banco (arquivo secrets.json fora do Postgres, modo 0o600), suporte a rotação com múltiplas chaves de decrypt. Design correto e documentado no próprio código.
Status: FALSE_POSITIVE (não é um achado negativo — registrado como ponto forte confirmado)
SEC-013 — Backups: nomes de arquivo sanitizados corretamente, sem path traversal encontrado
Severidade: INFORMATIVO
Componente: backend/src/services/BackupService.ts:73-74
Evidência: filename = device.name.replace(/[^a-z0-9]/gi, '_'); file_path sempre construído com path.join(BACKUPS_DIR, String(device.id), filename), device.id é SERIAL numérico. Endpoints de download/preview/restore buscam file_path do banco por id numérico, nunca aceitam path do cliente.
Status: FALSE_POSITIVE
SEC-014 — RBAC de backend (rotas) consistente com o esperado
Severidade: INFORMATIVO
Evidência: todas as 28 rotas verificadas têm router.use(requireAuth) (ou, no caso de oidc.ts, requireAuth/requireAdmin por rota, corretamente deixando só /status, /login, /callback públicos). Endpoints administrativos sensíveis (users, oidc/config, audit-log, cert/regenerate, credential-presets, maintenance-windows) usam requireAdmin. Autoproteção contra auto-rebaixamento/auto-exclusão de admin implementada em routes/settings.ts.
Status: FALSE_POSITIVE (sem achado — RBAC de rota está correto)
Achados por severidade
- Crítico: 0
- Alto: 1 (SEC-002)
- Médio: 4 (SEC-001, SEC-003, SEC-004, SEC-005)
- Baixo: 5 (SEC-006, SEC-007, SEC-008, SEC-009, SEC-010)
- Informativo: 5 (SEC-011 a SEC-014, incl. 2 achados positivos)
Principais 10 achados
- SEC-002 — Sites não são fronteira de autorização (ALTO para multi-tenant).
- SEC-005 — Change Guard cobre só um subconjunto pequeno de operações de escrita.
- SEC-001 — Sessões JWT sem revogação server-side.
- SEC-003 — RouterOS API-SSL sem verificação de certificado (
rejectUnauthorized: false). - SEC-004 — Chaves de criptografia default como fallback de decrypt permanente.
- SEC-006 — Injeção potencial de comando RouterOS via
ssh_username. - SEC-009 — DoS moderado transitivo em
qs/express(não corrigido por instrução da missão). - SEC-007/SEC-008 — Supply chain: imagens sem pinning,
npm installem vez denpm ci. - SEC-010 — Rate limit de login só por IP.
- SEC-012/SEC-013/SEC-014 — Pontos fortes confirmados: criptografia de credenciais, sanitização de path de backup, RBAC de rota consistente.
Credenciais RouterOS
Avaliação: aceitável com ressalva. Armazenamento em repouso é sólido (AES-256-GCM, IV único, tag autenticada, chave fora do banco, rotação suportada). A ressalva é em trânsito: TLS sem verificação de certificado (SEC-003) expõe a credencial a MITM na rede local. Não há exposição das credenciais em API, logs (logSafe/safeClientError fazem redação deliberada) ou frontend (maskedConfig para OIDC, sem exposição de *_encrypted observada nas rotas revisadas).
RBAC
Avaliação: seguro dentro de um único tenant, inseguro para multi-cliente. A separação admin/operator/viewer é aplicada corretamente no backend (não só na UI) e resiste a manipulação direta de endpoint. A falha estrutural é SEC-002: não existe isolamento por site/cliente — inaceitável se a GTI planeja hospedar múltiplos clientes finais na mesma instância antes de implementar user_sites.
Operações remotas
Avaliação: seguro com uma ressalva de TLS. Terminal SSH interativo tem controle de papel (admin/operator), auditoria e rate limit por usuário. RouterOS API usa protocolo binário estruturado (não concatenação de string), reduzindo risco de injeção clássica. A ressalva é SEC-003 (TLS sem verificação).
Change Guard
O que realmente protege: mudanças que passam pelo fluxo de "preflight + apply analisado" em devices.ts, com snapshot no dispositivo e rollback automático via scheduler RouterOS se a verificação de conectividade pós-mudança falhar.
O que NÃO protege: a maioria das edições diretas de firewall/NAT/rotas/VLAN/interfaces, execução de comandos em massa (commands.ts) e o terminal interativo — nenhum desses passa por withSafeApply.
Backups
Estado: seguro para o escopo atual. Nomes de arquivo sanitizados, paths nunca vêm do cliente, autorização de escrita (requireWrite) presente em criar/restaurar/excluir. Sem criptografia em repouso do arquivo .rsc em si (fica em texto plano no volume backups_data) — aceitável para DEV, a avaliar antes de integrar com SFTP GTI (fora do escopo desta missão).
Docker
Estado: funcional, com dívida de reprodutibilidade. Non-root user no backend, capability mínima necessária (NET_RAW só para arp-scan/ping), healthchecks presentes. Pontos a melhorar: pinning de imagens (SEC-007) e npm ci (SEC-008).
Dependências
Estado: aceitável. Backend: 3 vulnerabilidades moderadas transitivas (SEC-009), sem crítico/alto. Frontend: 0 vulnerabilidades em dependências de produção. Nenhuma atualização automática foi aplicada, conforme instrução da missão.
Testes
BACKEND TESTS: 545/545 (543 originais + 2 de auditoria, SEC-001/SEC-002)
FRONTEND TESTS: 56/56
TYPECHECK: PASS
LINT: PASS (0 erros; 270 warnings backend / 108 frontend, security/detect-object-injection e afins — nenhum classificado como vulnerabilidade real nesta auditoria; ver observação abaixo)
BUILD: PASS
Nota sobre os warnings do eslint-plugin-security: os 378 warnings (270+108) foram amostrados por arquivo (rfSpectrum.ts, serverArp.ts, secrets.ts, wifiChannels.ts, topologyLayout.ts entre outros). Nenhum ponto analisado usa índice vindo diretamente de entrada HTTP não validada para acessar um objeto sensível (a maioria é iteração sobre arrays/enums internos, falso positivo típico da regra detect-object-injection). Classificação: falso positivo majoritário — não foi encontrada uma cadeia de exploração real amostrando os arquivos mais citados pela ferramenta. Não corrigidos mecanicamente, conforme instrução da missão.
Primeira RB GTI read-only
AUTORIZADA PARA PRIMEIRA RB GTI READ-ONLY: SIM
Para uma única RouterBOARD, em modo read-only (sem escrita, sem Change Guard necessário, sem terminal), com um único usuário/tenant, a base é segura o suficiente: credenciais cifradas corretamente, RBAC de rota correto, auditoria presente. As ressalvas (TLS sem verificação — SEC-003, sessões sem revogação — SEC-001) são aceitáveis para um piloto controlado e monitorado.
Correções obrigatórias antes da primeira RB de escrita ou multi-cliente
- SEC-002 — implementar RBAC por site antes de conectar RBs de mais de um cliente final na mesma instância.
- SEC-005 — decidir explicitamente (e documentar para os operadores GTI) quais operações de escrita ficam sem rede de segurança do Change Guard, ou estender
withSafeApplya firewall/rotas antes de liberar escrita irrestrita em produção. - SEC-003 — avaliar fixação de fingerprint TLS antes de operar RBs fora de uma rede de gestão confiável.
Documentação operacional
docs/SISTEMA_ATUAL.md foi atualizado (seções 11 e 22) para refletir SEC-001, SEC-002, SEC-003 e SEC-005 como fatos comprovados sobre o sistema atual.
Git
- Commit desta missão: ver
git log -1após o commit de auditoria (arquivos: este relatório, testemiddleware/__tests__/auditFindings.test.ts, atualização dedocs/SISTEMA_ATUAL.md). - Nenhuma refatoração ampla foi feita; nenhuma correção de vulnerabilidade foi aplicada ao código de produção (todos os achados ficam como
OPEN/ACCEPTED, conforme regra da missão — só se corrige o que é necessário para provar/criar o teste). - Worktree limpo ao final.
VEREDITO FINAL
APROVADO COM CORREÇÕES OBRIGATÓRIAS
A arquitetura é sólida e o nível de engenharia é notavelmente maduro para uma base open source (comentários de causa-raiz, testes extensivos, defesa em profundidade em vários pontos, Change Guard bem desenhado onde é usado). Não há achado CRÍTICO. Mas existe um achado ALTO (SEC-002) que é estrutural para o modelo de negócio pretendido pela GTI (múltiplos clientes) e um MÉDIO (SEC-005) que muda a expectativa operacional sobre o que o Change Guard cobre. Nenhum dos dois inviabiliza a base — ambos são corrigíveis sem trocar de projeto — mas devem ser resolvidos antes de qualquer RB de escrita ou multi-cliente real.
Próxima missão recomendada
MISSÃO 02 — RBAC por site (user_sites) + extensão do Change Guard aos endpoints de firewall/rotas mais usados, antes de iniciar qualquer integração GTI (SFTP, GLPI, Slack) ou conectar a primeira RouterBOARD real em modo de escrita.