Skip to content

fix(security): refuse the well-known default password on /api/cli/connect from off-loopback (#14486) - #14491

Merged
diegosouzapw merged 2 commits into
release/v3.8.51from
fix/cli-connect-insecure-default-gate
Sep 24, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.51from
fix/cli-connect-insecure-default-gate

Conversation

@diegosouzapw

@diegosouzapw diegosouzapw commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

Fecha o item 1 (high) da #14486, achado pela insecure-defaults:audit (Trail of Bits) na bateria /omni-code-sec de 09-21.

O buraco

A #13679 tratou a senha-padrão pública como o que ela é — uma credencial conhecida por todo mundo — e passou a recusar o login com ela quando a requisição não vem de loopback (src/app/api/auth/login/route.ts:171). O gate ficou só ali.

POST /api/cli/connect verifica a mesma senha (verifyManagementPassword), e em caso de sucesso emite um access token oma_ com escopo admin (:120). A rota não está em LOCAL_ONLY_API_PREFIXES. E scripts/dev/sync-env.mjs:306-346 copia INITIAL_PASSWORD=CHANGEME do .env.example para o .env em todo npm install.

Resultado numa instalação recém-feita: quem alcançasse a porta trocava a senha pública por um token admin. O aviso de boot existe, mas aviso não é controle — foi exatamente o raciocínio da #13679, que esta rota não herdou.

A correção

O mesmo gate, no mesmo formato, com a mesma trilha de auditoria (cli.connect.insecure_default_blocked): se a senha é a well-known e a origem não é loopback → 403, nenhum token emitido.

O que não muda: parear a CLI do próprio host (loopback) continua funcionando — é o caminho que o operador precisa para rotacionar a senha; e qualquer senha já rotacionada continua funcionando de qualquer origem.

TDD

tests/unit/14486-cli-connect-insecure-default-gate.test.ts, três casos, vermelhos no tip antes da correção:

Caso Antes Depois
IP público + CHANGEME 200 + token admin 403, sem token, entrada de auditoria
loopback + CHANGEME 200 200 (preservado)
IP público + senha rotacionada 200 200 (preservado)

Os dois casos positivos entraram verdes de propósito: são a prova de que o gate morde só onde deve.

Validação

57 asserções nas 12 suítes vizinhas (login #13679, managementPassword, authz, access-token scopes) — a única falha foi authz-bypass AC-7, um teste com orçamento explícito de hot-reload <50ms: 9/9 no tip ocioso da .113, 8/9 neste devbox sob load 40, e minha mudança não toca essa área. Os 6 testes acrescentados ao stryker.conf.json são do tip, não desta PR.

Refs #14486.

⚠️ base-red inherited: #14496

…nect from off-loopback

#13679 blocks the shipped INITIAL_PASSWORD placeholder at /api/auth/login when
the request is not loopback. /api/cli/connect verifies the same management
password, mints an admin-scoped oma_ access token on success, and is not in
LOCAL_ONLY_API_PREFIXES — so on a fresh install, where sync-env.mjs copies
INITIAL_PASSWORD=CHANGEME out of .env.example, the public default could be
exchanged for admin from anywhere the port is reachable.

Same gate, same audit shape (cli.connect.insecure_default_blocked). Loopback
pairing and any rotated password are untouched.

Refs #14486.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant