Skip to main content

Grill Decisions — #08 Cross-Tenant Data Leak

Data: 2026-07-08


Decisão 1: Escopo — apenas /api/payment/* ou todos os endpoints?

Pergunta: O pentest lista #08 como profile-fraud e #26 como crm/bonus-query. Outros endpoints têm o mesmo padrão (Integration, Cart, Product). Tratar todos ou só /api/payment/*?

Opções:

  • Todos os endpoints multi-tenant sem JWT
  • Apenas /api/payment/* usados por zzapp + zzportal
  • Apenas profile-fraud

Decisão: /api/payment/* usados por zzapp + zzportal.

Trade-off: Foco nos endpoints com PII (profile-fraud) e dados de pagamento (history, payment info, NPS). Endpoints de ecommerce e webhook têm callers diferentes (público, gateways). Endpoints de CRM, Integration, Cart são #26 e cards separados — cada um tem suas particularidades de caller e fluxo.


Decisão 2: Mecanismo de auth — JWT vs API key vs ambos

Pergunta: Qual mecanismo de autenticação adicionar aos endpoints? [Authorize(JwtBearer)] ou um novo [UseApiKey]?

Opções:

  • [Authorize(JwtBearer)] — JWT padrão do auth-service
  • [UseApiKey("profileFraud")] — nova chave de API específica
  • Ambos (JWT + API key) — defense-in-depth, mas mistura mecanismos

Decisão: [Authorize(JwtBearer)] — mesmo padrão de approve, reverse, cancel.

Trade-off: Ambos clientes (zzapp, zzportal) já enviam Bearer em todas as requests. API key adicional seria redundante e confusa (qual prevalece?). Consistência com endpoints irmãos no mesmo controller.


Decisão 3: Granularidade — controller-level vs action-level

Pergunta: PaymentController tem mix de auth (JWT, PaymentsScheme, API key, público). Colocar JWT no controller inteiro quebraria endpoints legítimos. Onde aplicar?

Opções:

  • Controller-level + exclude nos que não devem ter JWT
  • Action-level em cada endpoint

Decisão: Controller-level no ProfileFraudController (100% dos actions precisam de JWT). Action-level nos 6 actions do PaymentController (mix de auth).

Trade-off: Controller-level é mais seguro (não esquece action nova). Mas PaymentController tem 15 actions com 4 mecanismos de auth diferentes — forçar JWT no controller exigiria [AllowAnonymous] nas exceções, o que é frágil. Action-level é explícito e seguro.


Decisão 4: [AddSchema] — manter ou remover?

Pergunta: Os endpoints que ganham JWT já têm [AddSchema] (valida presença do header api-company-target). Manter ou remover?

Opções:

  • Manter [AddSchema] — defense-in-depth
  • Remover [AddSchema] — JWT claim schema é suficiente

Decisão: Manter [AddSchema].

Trade-off: Com JWT, o schema vem do claim, não do header. Mas o [AddSchema] garante que o header está presente e válido (pós-fix #07). Se o JWT não tiver claim schema (caso de borda), o fallback para o header ainda é validado. Custo zero, ganho marginal de segurança.


Decisão 5: Cross-tenant binding — fix separado ou incluso?

Pergunta: A raiz do cross-tenant é o UserProvider.GetSchemaName() aceitar o header api-company-target. Isso é parcialmente resolvido pelo #07 (schema whitelist). O #08 precisa tratar também?

Opções:

  • Apenas adicionar JWT (claim schema tem prioridade 2, vence header prioridade 3)
  • JWT + forçar schema do claim (ignorar header completamente nos endpoints afetados)

Decisão: Apenas JWT. O claim schema do JWT naturalmente vence o header pela ordem de prioridade do GetSchemaName().

Trade-off: Sem JWT → fallback para header → cross-tenant. Com JWT → claim vence → tenant correto. Simples e sem código adicional. Se no futuro precisarmos de fallback para header (ex: usuário sem schema no JWT), o #07 já valida contra whitelist.


Decisão 6: Breaking change — risco para zzapp/zzportal?

Pergunta: Adicionar [Authorize(JwtBearer)] pode quebrar zzapp ou zzportal se eles não enviarem Bearer consistentemente?

Verificação:

  • zzapp: AuthInterceptor adiciona Authorization: Bearer {token} em todas as requests (auth_interceptor.dart:20)
  • zzportal: api.defaults.headers.common.Authorization = Bearer {token} setado no sign-in, refresh token, e rehydration do Redux store — cobre todas as requests

Decisão: Sem breaking change. Ambos clientes já enviam Bearer em 100% das requests.