diff --git a/TODO.md b/TODO.md index 98a3b6e..48df89d 100644 --- a/TODO.md +++ b/TODO.md @@ -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` + diff --git a/apps/api/src/agents/agents.controller.ts b/apps/api/src/agents/agents.controller.ts index a13b432..67c4f8f 100644 --- a/apps/api/src/agents/agents.controller.ts +++ b/apps/api/src/agents/agents.controller.ts @@ -1,4 +1,5 @@ import { + BadRequestException, Body, Controller, Delete, @@ -36,6 +37,26 @@ export class AgentsController { ); await assertQuota(tenantId, "maxAgents", activeCount); + // Bug real, achado construindo a tela de Agentes (PHASE 29): nada + // aqui verificava que `dto.userId`/`dto.extensionId` pertencem a este + // tenant antes do create — um FK constraint no Postgres não passa + // pela RLS de visibilidade (só checa existência da linha), então um + // id de outro tenant seria aceito silenciosamente. O frontend já só + // oferece opções do próprio tenant, mas o backend é a autoridade + // (secao 31/146), nunca confia só no que o client filtrou. + const [membership, extension] = await withTenantContext(prisma, tenantId, (tx) => + Promise.all([ + tx.tenantMembership.findUnique({ where: { tenantId_userId: { tenantId, userId: dto.userId } } }), + dto.extensionId ? tx.extension.findFirst({ where: { id: dto.extensionId, tenantId } }) : Promise.resolve(null), + ]), + ); + if (!membership) { + throw new BadRequestException("userId nao pertence a este tenant"); + } + if (dto.extensionId && !extension) { + throw new BadRequestException("extensionId nao pertence a este tenant"); + } + const agent = await withTenantContext(prisma, tenantId, (tx) => tx.agent.create({ data: { diff --git a/apps/frontend/.impeccable/review/agentes-after-delete-desktop.png b/apps/frontend/.impeccable/review/agentes-after-delete-desktop.png index 6e865a6..9022a21 100644 Binary files a/apps/frontend/.impeccable/review/agentes-after-delete-desktop.png and b/apps/frontend/.impeccable/review/agentes-after-delete-desktop.png differ diff --git a/apps/frontend/.impeccable/review/agentes-empty-desktop.png b/apps/frontend/.impeccable/review/agentes-empty-desktop.png index e5697e1..aa4cc9a 100644 Binary files a/apps/frontend/.impeccable/review/agentes-empty-desktop.png and b/apps/frontend/.impeccable/review/agentes-empty-desktop.png differ diff --git a/apps/frontend/.impeccable/review/agentes-form-desktop.png b/apps/frontend/.impeccable/review/agentes-form-desktop.png index d27d065..2218aad 100644 Binary files a/apps/frontend/.impeccable/review/agentes-form-desktop.png and b/apps/frontend/.impeccable/review/agentes-form-desktop.png differ diff --git a/apps/frontend/.impeccable/review/agentes-with-data-desktop.png b/apps/frontend/.impeccable/review/agentes-with-data-desktop.png index b267be8..4a64485 100644 Binary files a/apps/frontend/.impeccable/review/agentes-with-data-desktop.png and b/apps/frontend/.impeccable/review/agentes-with-data-desktop.png differ