Skip to content

fix(seguranca): fechar os 19 gaps de tenant_id - #31

Merged
janeiaraujo merged 1 commit into
mainfrom
fix/tenant-isolation-gaps
Aug 3, 2026
Merged

fix(seguranca): fechar os 19 gaps de tenant_id#31
janeiaraujo merged 1 commit into
mainfrom
fix/tenant-isolation-gaps

Conversation

@janeiaraujo

Copy link
Copy Markdown
Owner

O que resolve

A varredura criada no PR #28 mapeou 19 consultas a coleções de tenant sem filtro de organização. Elas não vazavam leitura no uso normal — o id costuma vir de um documento já filtrado — mas aceitavam um id de outra organização chegando pela URL. Faltava defesa em profundidade.

Corrigidos por arquivo, não em lote, conforme o plano definido quando a lista foi congelada.

Escritas (as mais graves — permitiam alterar dado de outro tenant)

Arquivo O que era
activity Contador de views de KB
review (2x) Agendamento e conclusão de revisão
postmortem Atualização do documento
smart-search Atualização de solicitação de KB
webhooks Estatísticas de entrega

Exemplo — antes um recordId de outra organização passado no corpo alterava a data de revisão do KB alheio:

- await db.collection('records').updateOne({ _id: new ObjectId(recordId) }, {
+ await db.collection('records').updateOne({ _id: new ObjectId(recordId), tenant_id: request.tenantId }, {

Leituras

GPS (3x): fluxo lido a partir da sessão, agora filtrado.

Gamificação (10x): aqui havia dois problemas distintos, e vale destacar o segundo:

  • calculateUserStats nem recebia tenantId. Agora recebe — e sem valor padrão de propósito: um tenantId = null silencioso reintroduziria exatamente o bug na próxima vez que alguém chamasse a função sem o argumento.
  • checkAndAwardBadges já recebia tenantId na assinatura, mas não usava em nenhuma das queries. O parâmetro estava lá dando falsa impressão de que o escopo era respeitado.

O baseline ficou vazio

KNOWN_GAPS agora é new Set([]). A catraca passa a falhar em qualquer consulta nova sem tenant_id, sem exceções herdadas — que era o objetivo desde o começo. Casos legítimos futuros devem ir para ALLOWED_EXCEPTIONS com o motivo escrito, não de volta para a lista de gaps.

Test plan

  • npm test → 25 passam, com o baseline vazio
  • node --check em todos os arquivos alterados
  • Com o app rodando: abrir um KB (contador de views), agendar uma revisão, rodar um fluxo GPS e abrir a tela de Gamificação — as 4 áreas tocadas
  • Confirmar que os números da Gamificação continuam corretos (as contagens agora são por tenant)

A varredura do PR #28 mapeou 19 consultas a colecoes de tenant sem
filtro de organizacao. Nao vazavam leitura no uso normal (o id costuma
vir de documento ja filtrado), mas aceitavam um id de outra organizacao
chegando pela URL - faltava defesa em profundidade.

Corrigidos por arquivo, nao em lote, conforme o plano:

ESCRITAS (as mais graves - permitiam alterar dado de outro tenant)
- activity: contador de views de KB
- review: agendamento e conclusao de revisao (2x)
- postmortem: atualizacao do documento
- smart-search: atualizacao de solicitacao de KB
- webhooks: estatisticas de entrega

LEITURAS
- gps: fluxo lido a partir da sessao (3x)
- gamification: 10 consultas. Aqui havia dois problemas distintos -
  calculateUserStats nem recebia tenantId (agora recebe, e sem valor
  padrao de proposito: um default silencioso reintroduziria o bug), e
  checkAndAwardBadges ja recebia mas nao usava nas queries.

O baseline KNOWN_GAPS ficou vazio: a catraca agora falha em qualquer
consulta nova sem tenant_id, sem excecoes herdadas. Novos casos
legitimos devem ir para ALLOWED_EXCEPTIONS com o motivo escrito.
Copilot AI review requested due to automatic review settings August 3, 2026 19:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR fecha os “gaps” de isolamento multi-tenant identificados pelo detector (PR #28), adicionando tenant_id aos filtros de consultas/escritas em coleções tenant-scoped e deixando o baseline (KNOWN_GAPS) vazio para que o teste passe a falhar para qualquer regressão futura.

Changes:

  • Adiciona tenant_id: request.tenantId em updateOne/findOne/countDocuments que antes aceitavam _id/created_by sem escopo de tenant.
  • Ajusta Gamificação para receber tenantId em calculateUserStats e usar tenant_id nas queries.
  • Zera KNOWN_GAPS no teste tenant-isolation para ativar a “catraca” sem exceções herdadas.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
backend/tests/tenant-isolation.test.js Baseline KNOWN_GAPS agora vazio; mantém a “catraca” para novos gaps.
backend/src/modules/activity/activity.routes.js Escopa update do contador de views de KB por tenant_id.
backend/src/modules/review/review.routes.js Escopa updates de agendamento/conclusão de review por tenant_id.
backend/src/modules/postmortem/postmortem.routes.js Escopa update do post-mortem por tenant_id.
backend/src/modules/smart-search/smart-search.routes.js Escopa update de kb_requests por tenant_id.
backend/src/modules/gps/gps.routes.js Escopa leituras de gps_flows por tenant_id.
backend/src/modules/gamification/gamification.routes.js Escopa contagens/queries de stats e badges por tenant_id.
backend/src/modules/webhooks/webhooks.routes.js Escopa update de estatísticas de webhook por tenant_id (mas há um bug de escopo a corrigir).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 466 to 469
await db.collection('webhooks').updateOne(
{ _id: webhook._id },
{ _id: webhook._id, tenant_id: request.tenantId },
{ $inc: statsUpdate }
);
@janeiaraujo
janeiaraujo merged commit bd97f75 into main Aug 3, 2026
4 checks passed
@janeiaraujo
janeiaraujo deleted the fix/tenant-isolation-gaps branch August 3, 2026 23:37
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.

2 participants