fix(agents): POST /agents nunca verificava tenant de userId/extensionId

Achado numa revisão de segurança sobre o trabalho da PHASE 29: um FK do
Postgres só checa que a linha referenciada existe, não que ela é visível
sob a RLS da sessão atual — então um userId/extensionId de outro tenant
seria aceito silenciosamente no create do Agent (violação do princípio já
seguido em todo o resto do código: nunca confiar em id vindo do client
sem checar contra o tenant do JWT). O frontend já só oferece opções do
próprio tenant, mas isso é conveniência de UI, não autorização.

Corrigido com uma checagem explícita de TenantMembership/Extension antes
do create (400 se não pertencer). Reverificado ponta a ponta: criação
legítima continua funcionando igual.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-29 16:37:32 -03:00
parent 75212cafe6
commit 7f56c96454
6 changed files with 38 additions and 0 deletions

17
TODO.md
View File

@@ -1006,6 +1006,23 @@ Troncos, Discador > Lista de Bloqueio (agente.md secao 50-51, 41-42, 71,
`bridge(${destination_number})` → gerar v1 (DRAFT) → ativar → badge
"Ativa" confirmado. Smoke test de regressão nas 13 telas anteriores
do tenant + platform, todas 200 sem quebrar nada.
- [x] **Bug real de segurança, achado numa revisão dedicada depois desta
fase (PHASE 31)**: `POST /agents` nunca verificava que
`dto.userId`/`dto.extensionId` pertencem ao tenant de quem está
chamando — um FK do Postgres só checa que a linha existe, não que
ela é visível sob a RLS da sessão atual, então um `userId`/
`extensionId` de **outro tenant** seria aceito silenciosamente
(secao 31/146: nunca confiar em id vindo do client sem checar
contra o tenant do JWT — o frontend já só oferece opções do próprio
tenant, mas isso é conveniência de UI, o backend é a autoridade).
Corrigido com uma checagem explícita de `TenantMembership`/
`Extension` antes do create, 400 se não pertencer. Impacto prático
limitado (precisaria adivinhar um UUID de outro tenant, e o agente
criado ficaria órfão/inerte — o usuário referenciado nunca teria
`tenantId` ativo igual ao do agente sem ter membership de verdade),
mas corrigido de qualquer forma por princípio arquitetural.
Reverificado ponta a ponta depois da correção: criação legítima
continua funcionando igual.
- [ ] Sem paginação/uniqueness real em `Agent.userId`/`extensionId` — o
"só quem não é agente ainda" é conveniência de UI, não constraint de
banco (mesma classe de lacuna sistêmica da PHASE 12, `@@unique` +