Next.js Server Actions secara otomatis mengekspos setiap fungsi yang diekspor dalam file use-server sebagai endpoint HTTP publik, dengan menetapkan pengenal unik yang digunakan klien untuk memanggil fungsi tersebut. Endpoint tersebut tetap ada terlepas dari apakah tombol, tautan, atau elemen UI lainnya dirender, yang berarti siapa pun yang menemukan pengenal tersebut dapat memanggil fungsi secara langsung.

Desain framework ini memperlakukan Server Actions seperti fungsi pembantu (helper functions) biasa, tetapi pada saat runtime, mereka menjadi URL yang dapat dijangkau. Bagi pengembang yang mengasumsikan bahwa UI adalah satu-satunya penjaga gerbang, hal ini menciptakan permukaan serangan (attack surface) yang tidak terlihat yang dapat dieksploitasi dengan satu permintaan yang dirancang khusus.

Mengapa Server Actions tampak aman

Server Actions diperkenalkan agar pengembang dapat menulis kode sisi server tepat di samping komponen mereka, menghindari boilerplate dari rute API yang terpisah. Penggunaan tipikal terlihat seperti:

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

Karena formulir adalah satu-satunya cara yang terlihat untuk memicu deleteInvoice, banyak pengembang menyembunyikan tombol untuk pengguna yang tidak berwenang, berpikir bahwa tanda tangan (signature) TypeScript akan menghentikan data yang salah format, dan mengandalkan fakta bahwa fungsi tersebut berada di modul khusus server. Tidak satu pun dari asumsi tersebut memberikan perlindungan nyata.

Eksposur yang tersembunyi

Ketika sebuah file berisi “use server”, Next.js mengompilasi setiap fungsi yang diekspor menjadi sebuah endpoint seperti:

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

<action-id> adalah hash stabil yang disematkan dalam bundle klien. Seorang penyerang dapat memperolehnya dengan cara:

  • Memeriksa lalu lintas jaringan halaman.
  • Membaca JavaScript yang dibundel (ID tersebut adalah string biasa).
  • Menebak berdasarkan konvensi penamaan jika proyek mengikuti pola yang dapat diprediksi.

Setelah ID diketahui, permintaan dapat dikirim dari alat apa pun—cURL, Postman, atau skrip berbahaya—dengan melewati semua pemeriksaan tingkat UI.

Tiga risiko nyata

Risiko Mengapa ini penting
Pemeriksaan UI tidak efektif Menyembunyikan tombol atau tautan tidak menghapus endpoint yang mendasarinya. Endpoint tersebut tetap dapat dijangkau, sama seperti halaman admin tersembunyi yang masih ada di server.
TypeScript tidak menawarkan keamanan runtime Tipe data dihapus saat kode dijalankan. Fungsi yang dideklarasikan sebagai deleteInvoice(id: number) dapat menerima string yang sangat besar, array, atau bahkan JSON berbahaya, yang menyebabkan kesalahan logika atau serangan injeksi.
Tidak ada autentikasi atau otorisasi default Server Actions terlihat seperti helper lokal, sehingga pengembang sering lupa menambahkan pemeriksaan sesi, perlindungan CSRF, atau pemeriksaan izin tingkat baris (row-level permission) yang merupakan standar dalam rute API tradisional.

Cara mengamankan Server Action

  1. Autentikasi pemanggil – Verifikasi bahwa sesi atau token yang valid ada sebelum logika bisnis apa pun dijalankan.
  2. Validasi payload – Gunakan pustaka skema (misalnya, Zod, Yup) untuk menegakkan tipe data dan batasan nilai pada saat runtime.
  3. Otorisasi operasi – Selain “apakah pengguna sudah login?”, konfirmasikan bahwa pengguna tersebut memiliki catatan spesifik yang ingin mereka ubah atau hapus.

Contoh minimal:

'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 } });
}

Kode tersebut secara eksplisit memeriksa autentikasi, memvalidasi id yang masuk, dan memastikan pengguna yang masuk benar-benar memiliki invoice tersebut sebelum melakukan penghapusan.

Jebakan Next.js lainnya yang halus

Masalah Gejala Perbaikan
Variabel lingkungan NEXT_PUBLIC_ Apa pun yang diawali dengan NEXT_PUBLIC_ akan dibundel ke dalam klien, sehingga mengekspos rahasia. Simpan rahasia dalam variabel lingkungan biasa, jangan pernah mengawalinya dengan NEXT_PUBLIC_.
Open redirects Menerima parameter kueri redirect dan menggabungkannya secara naif dapat mengirim pengguna ke //evil.com. Validasi target terhadap daftar putih (whitelist) atau terapkan pemeriksaan same-origin.
Server-Side Request Forgery (SSRF) Mengambil URL yang diberikan oleh pengguna dapat membiarkan penyerang menjangkau layanan internal atau endpoint metadata cloud. Gunakan daftar putih (allow-list) untuk hostname, blokir rentang IP pribadi, dan atur timeout.
dangerouslySetInnerHTML Merender HTML yang disediakan pengguna tanpa sanitasi membuka celah XSS. Gunakan pustaka seperti DOMPurify atau hindari penggunaan HTML mentah sama sekali.

Memindai pola-pola ini secara otomatis

Alat analisis statis dapat menandai konstruksi berisiko yang tercantum di atas. Salah satu opsi ringan adalah Semgrep, yang berjalan dengan cepat dan dapat diintegrasikan ke dalam pipeline CI:

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

Kumpulan aturan tersebut mencakup pemeriksaan untuk fungsi server yang diekspor, penyalahgunaan NEXT_PUBLIC_, redirect terbuka, pola SSRF, dan penyisipan HTML yang tidak aman.

Apa yang perlu diperhatikan selanjutnya

  • Pembaruan framework – Pantau rilis Next.js; tim mungkin akan memperkenalkan hook autentikasi bawaan atau melakukan sandboxing pada endpoint yang dihasilkan.
  • Alat bantu komunitas – Plugin ESLint baru dan aturan Semgrep khusus Next.js mulai bermunculan untuk mengotomatiskan perlindungan yang dijelaskan di sini.
  • Insiden dunia nyata – Seiring semakin banyaknya proyek yang mengadopsi Server Actions, perhatikan eksploitasi yang terungkap yang mengilustrasikan risiko dalam praktiknya. Deteksi dini dapat menjadi masukan bagi tinjauan keamanan internal sebelum terjadi pelanggaran.

Poin Penting: Sebuah Next.js Server Action bukanlah helper privat; itu adalah endpoint HTTP publik saat Anda mengekspornya. Perlakukan seperti rute API lainnya—autentikasi, validasi, dan otorisasi—jika tidak, kemudahan menulis kode server di samping UI dapat dengan cepat menjadi liabilitas keamanan.