Skip to content

[Segurança] Nenhuma operação de cobrança verifica o papel de admin no servidor #1119

Description

@elio-neto

Problema

A área de cobrança inteira é tratada como área de admin no frontend, em três portões distintos. No
servidor, nenhuma das operações correspondentes chama Actor.assertAdmin().

O grep de assertAdmin em packages/console retorna 8 chamadas — todas em user.ts, model.ts,
provider.ts e workspace.ts (update). Nenhuma em billing.ts.

Por que é explorável

As operações são server functions do SolidStart com id estável (billing.setMonthlyLimit,
billing.redeemCoupon, billing.reload, checkoutUrl, sessionUrl, enroll, setUseBalance,
cancelWaitlist). Várias estão ligadas a <form method="post"> para funcionar sem JavaScript, portanto
aceitam POST comum. Um membro com papel member as invoca diretamente, sem precisar que a UI apareca.

Evidência

Portões do frontend:

// packages/console/app/src/routes/workspace/[id]/billing/index.tsx:20-31
<Show when={sessionInfo()?.isAdmin}>
  <Show when={isBlack()}><BlackSection /></Show>
  <BillingSection />
  <RedeemSection />
  <Show when={billingInfo()?.customerID}>
    <ReloadSection />
    <MonthlyLimitSection />
    <PaymentSection />
  </Show>
</Show>

// packages/console/app/src/routes/workspace/[id].tsx:34-41 (nav desktop) e :62-69 (nav mobile)
<Show when={userInfo()?.isAdmin}>
  <A href={`/workspace/${params.id}/billing`} ...>{i18n.t("workspace.nav.billing")}</A>
  <A href={`/workspace/${params.id}/settings`} ...>{i18n.t("workspace.nav.settings")}</A>
</Show>

Servidor, sem verificação de papel:

// packages/console/core/src/billing.ts:188-197
export const setMonthlyLimit = fn(z.number(), async (input) => {
  return await Database.use((tx) =>
    tx.update(BillingTable).set({ monthlyLimit: input })
      .where(eq(BillingTable.workspaceID, Actor.workspace())),
  )
})

Funções sem assertAdmin em packages/console/core/src/billing.ts:

Linha Função Efeito ao ser chamada por membro comum
75 reload Cobra imediatamente o cartão salvo do workspace
158 redeemCoupon Resgata cupom em nome do workspace (BUILDATHON credita US$ 500, linha 174)
188 setMonthlyLimit Altera o teto de gasto de todo o workspace
199 generateCheckoutUrl Gera sessão de checkout do Stripe
412 generateSessionUrl Gera URL do portal de cobrança (ver faturas, trocar meio de pagamento)
450 subscribeBlack Cria assinatura

Ações de rota na mesma condição:

  • routes/workspace/[id]/billing/black-section.tsx:117-139setUseBalance
  • routes/workspace/[id]/billing/black-section.tsx:69-87cancelWaitlist
  • routes/workspace/[id]/go/lite-section.tsx:121-141setLiteUseBalance

Impacto

Escalada de privilégio dentro do workspace. Um membro comum:

  • altera o limite mensal de gasto e assim libera ou bloqueia o consumo de todo o workspace
  • dispara uma cobrança imediata no cartão salvo
  • resgata cupons em nome do workspace
  • obtém URL do portal de cobrança do Stripe, pela qual vê faturas e troca o meio de pagamento
  • entra ou cancela assinatura

Sugestão de correção

  1. Adicionar Actor.assertAdmin() no início de reload, redeemCoupon, setMonthlyLimit,
    generateCheckoutUrl, generateSessionUrl, generateLiteCheckoutUrl e subscribeBlack.
  2. Adicionar a mesma verificação nas ações de rota setUseBalance, setLiteUseBalance e
    cancelWaitlist.
  3. Onde a operação deve ficar disponível para membro (por exemplo assinatura Lite individual),
    documentar a decisão no código e restringir o alcance ao próprio usuário em vez de ao workspace.
  4. Como controle estrutural, exigir que toda função exportada de billing.ts declare explicitamente o
    papel mínimo.

Critérios de aceite

  • Cada função exportada de billing.ts declara e verifica o papel mínimo no servidor
  • As ações de rota setUseBalance, setLiteUseBalance e cancelWaitlist verificam o papel
  • Teste por operação: ator com papel member recebe erro de autorização
  • Teste: nenhuma alteração persiste em BillingTable após tentativa de member
  • Cada portão <Show when={isAdmin}> do frontend tem um teste de servidor correspondente

Labels sugeridas: security, alta

Achado da auditoria estatica de seguranca do commit 0465a5d. Relatorio completo em docs/security-audit/relatorio-auditoria-seguranca.pdf.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions