Files
MIKROTIK-RMM/docs/AUDITORIA_BASE_2GT.md
GustavoandClaude Sonnet 5 5b926d637b docs: record upstream security audit (Mission 01)
- 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>
2026-09-08 18:32:03 -03:00

23 KiB
Raw Permalink Blame History

Auditoria Técnica e de Segurança — Base Upstream 2GT (MikroTik Manager)

  • Commit auditado: 1a4b6350528b8c656ab7a84ee4806a8a86cf9932 (v0.24.5-beta)
  • Branch: mikrormm-dev, sobre bootstrap 7a4979e
  • 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 com router.use(requireAuth) no topo do arquivo exceto os endpoints públicos explícitos de oidc.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 em middleware/auth.ts (requireAdmin, requireWrite). Sem escopo por site/tenant.
  • Banco: PostgreSQL, schema em db/migrate.ts (migrations idempotentes, CREATE TABLE IF NOT EXISTS + ALTER incrementais), 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 em routes/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.ts namespace /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 no docker-compose.yml.
  • Backups: services/BackupService.ts + routes/backups.ts, arquivos .rsc (/export compact) em backups_data volume.
  • 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 a admin/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) + tabela audit_log, consultável via routes/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

  1. SEC-002 — Sites não são fronteira de autorização (ALTO para multi-tenant).
  2. SEC-005 — Change Guard cobre só um subconjunto pequeno de operações de escrita.
  3. SEC-001 — Sessões JWT sem revogação server-side.
  4. SEC-003 — RouterOS API-SSL sem verificação de certificado (rejectUnauthorized: false).
  5. SEC-004 — Chaves de criptografia default como fallback de decrypt permanente.
  6. SEC-006 — Injeção potencial de comando RouterOS via ssh_username.
  7. SEC-009 — DoS moderado transitivo em qs/express (não corrigido por instrução da missão).
  8. SEC-007/SEC-008 — Supply chain: imagens sem pinning, npm install em vez de npm ci.
  9. SEC-010 — Rate limit de login só por IP.
  10. 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

  1. SEC-002 — implementar RBAC por site antes de conectar RBs de mais de um cliente final na mesma instância.
  2. 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 withSafeApply a firewall/rotas antes de liberar escrita irrestrita em produção.
  3. 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 -1 após o commit de auditoria (arquivos: este relatório, teste middleware/__tests__/auditFindings.test.ts, atualização de docs/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.