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>
This commit is contained in:
Gustavo
2026-09-08 18:32:03 -03:00
co-authored by Claude Sonnet 5
parent 7a4979e0e1
commit 5b926d637b
3 changed files with 286 additions and 2 deletions
+222
View File
@@ -0,0 +1,222 @@
# 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.