As Server Actions do Next.js expõem automaticamente cada função exportada em um arquivo use-server como um endpoint HTTP público, atribuindo-lhe um identificador único que o cliente usa para invocar a função. O endpoint existe independentemente de um botão, link ou qualquer outro elemento de UI ser renderizado, o que significa que qualquer pessoa que descubra o identificador pode chamar a função diretamente.

O design do framework trata as Server Actions como funções auxiliares comuns, mas, em tempo de execução, elas se tornam URLs acessíveis. Para desenvolvedores que presumiam que a UI era o único guardião, isso cria uma superfície de ataque invisível que pode ser explorada com uma única requisição manipulada.

Por que as Server Actions pareciam seguras

As Server Actions foram introduzidas para permitir que os desenvolvedores escrevam código do lado do servidor diretamente ao lado de seus componentes, evitando o boilerplate de rotas de API separadas. Um uso típico se parece com:

<form action={deleteInvoice}>
  <button type="submit">Delete</button>
</form>

Como o formulário é a única maneira visível de acionar deleteInvoice, muitos desenvolvedores escondem o botão para usuários não autorizados, pensam que as assinaturas do TypeScript impedirão dados malformados e confiam no fato de que a função reside em um módulo exclusivo do servidor. Nenhuma dessas suposições oferece proteção real.

A exposição oculta

Quando um arquivo contém “use server”, o Next.js compila cada função exportada em um endpoint como:

POST /_next/data/<build-id>/<page>.json?__rsc=<action-id>

O <action-id> é um hash estável que o bundle do cliente incorpora. Um invasor pode obtê-lo ao:

  • Inspecionar o tráfego de rede da página.
  • Ler o JavaScript empacotado (o ID é uma string simples).
  • Adivinhar com base em convenções de nomenclatura, caso o projeto siga padrões previsíveis.

Uma vez que o ID é conhecido, uma requisição pode ser enviada de qualquer ferramenta — cURL, Postman ou um script malicioso — ignorando quaisquer verificações de nível de UI.

Três riscos concretos

Risco Por que importa
Verificações de UI são ineficazes Esconder um botão ou link não deleta o endpoint subjacente. O endpoint permanece acessível, assim como uma página de admin oculta que ainda existe no servidor.
TypeScript não oferece segurança em tempo de execução Os tipos são removidos quando o código é executado. Uma função declarada como deleteInvoice(id: number) pode receber uma string massiva, um array ou até mesmo um JSON malicioso, levando a erros de lógica ou ataques de injeção.
Sem autenticação ou autorização padrão As Server Actions parecem helpers locais, então os desenvolvedores frequentemente esquecem de adicionar verificações de sessão, proteção CSRF ou verificações de permissão a nível de linha, que são padrão em rotas de API tradicionais.

Como proteger uma Server Action

  1. Autentique o chamador – Verifique se uma sessão ou token válido existe antes de qualquer lógica de negócio ser executada.
  2. Valide o payload – Use uma biblioteca de esquema (ex: Zod, Yup) para impor tipos de dados e restrições de valor em tempo de execução.
  3. Autorize a operação – Além de "o usuário está logado?", confirme se o usuário é o proprietário do registro específico que ele está tentando modificar ou excluir.

Um exemplo mínimo:

'use server';
import { getSession } from '@/auth';
import { z } from 'zod';
import { db } from '@/db';

const DeleteInvoiceSchema = z.object({
  id: z.number().int().positive(),
});

export async function deleteInvoice(formData: FormData) {
  const session = await getSession();
  if (!session) throw new Error('Unauthenticated');

  const parsed = DeleteInvoiceSchema.safeParse({
    id: Number(formData.get('id')),
  });
  if (!parsed.success) throw new Error('Invalid input');

  const invoice = await db.invoice.findUnique({ where: { id: parsed.data.id } });
  if (!invoice || invoice.ownerId !== session.userId) {
    throw new Error('Unauthorized');
  }

  await db.invoice.delete({ where: { id: invoice.id } });
}

O código verifica explicitamente a autenticação, valida o id recebido e garante que o usuário logado realmente possui a fatura antes de realizar a exclusão.

Outras armadilhas sutis do Next.js

Problema Sintoma Correção
Variáveis de ambiente NEXT_PUBLIC_ Qualquer coisa com o prefixo NEXT_PUBLIC_ é incluída no bundle do cliente, expondo segredos. Mantenha segredos em variáveis de ambiente comuns, nunca use o prefixo NEXT_PUBLIC_.
Redirecionamentos abertos Aceitar um parâmetro de consulta redirect e concatená-lo ingenuamente pode enviar usuários para //evil.com. Valide o destino contra uma whitelist ou force verificações de mesma origem (same-origin).
Server-Side Request Forgery (SSRF) Buscar uma URL fornecida por um usuário pode permitir que invasores alcancem serviços internos ou endpoints de metadados da nuvem. Use uma lista de permissões (allow-list) para hostnames, bloqueie intervalos de IPs privados e defina timeouts.
dangerouslySetInnerHTML Renderizar HTML fornecido pelo usuário sem sanitização abre brechas para XSS. Use uma biblioteca como DOMPurify ou evite o uso de HTML bruto completamente.

Escaneando esses padrões automaticamente

Ferramentas de análise estática podem sinalizar as construções de risco listadas acima. Uma opção leve é o Semgrep, que executa rapidamente e pode ser integrado a pipelines de CI:

npx --yes semgrep --config https://raw.githubusercontent.com/catidegla/stacksec/main/rules .

O conjunto de regras inclui verificações para funções de servidor exportadas, uso indevido de NEXT_PUBLIC_, redirecionamentos abertos, padrões de SSRF e inserção insegura de HTML.

O que observar a seguir

  • Atualizações de framework – Fique de olho nos lançamentos do Next.js; a equipe pode introduzir hooks de autenticação integrados ou isolar (sandbox) os endpoints gerados.
  • Ferramentas da comunidade – Novos plugins do ESLint e regras do Semgrep específicas para Next.js estão surgindo para automatizar as salvaguardas descritas aqui.
  • Incidentes no mundo real – À medida que mais projetos adotam Server Actions, fique atento a exploits divulgados que ilustrem o risco na prática. A detecção precoce pode informar revisões de segurança internas antes que ocorra uma violação.

Conclusão: Uma Server Action do Next.js não é um helper privado; é um endpoint HTTP público no momento em que você a exporta. Trate-a como qualquer outra rota de API — autentique, valide e autorize — caso contrário, a conveniência de escrever código de servidor ao lado da UI pode rapidamente se tornar um risco de segurança.