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 claimschemaé 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
schematem 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:
AuthInterceptoradicionaAuthorization: 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.