Table of Contents
▼React Server Components (RSC) makin jadi standar di 2026. Hampir setiap proyek Next.js baru sudah pakai RSC secara default, dan banyak developer Indonesia mulai mengadopsinya tanpa benar-benar memahami mekanisme di baliknya.
Di sinilah masalah mulai muncul.
Ada satu komponen inti RSC yang jarang dibahas secara mendalam: React Flight Protocol. Protokol inilah yang menggerakkan seluruh mekanisme streaming RSC dari server ke client — dan di dalamnya terdapat titik-titik rawan yang bisa menjadi celah keamanan serius jika tidak ditangani dengan benar.
Artikel ini akan membedah cara kerja React Flight Protocol, menjelaskan apa itu deserialization sink, dan memberikan panduan mitigasi yang bisa langsung kamu terapkan.
Apa Itu React Flight Protocol dan Cara Kerjanya
React Flight Protocol adalah format serialisasi khusus yang digunakan React untuk mentransfer data dari Server Components ke Client Components.
Bayangkan RSC seperti ini: server merender komponen, lalu hasilnya dikirim ke browser bukan sebagai HTML biasa, melainkan sebagai stream berformat khusus yang bisa dipahami oleh React di sisi client.
Format stream itu disebut RSC Payload — dan protokol yang mengatur encoding serta decoding-nya itulah React Flight Protocol.
Struktur RSC Payload
RSC Payload bukan JSON biasa. Ia punya format custom yang terdiri dari baris-baris dengan prefix karakter tertentu:
0:["$","div",null,{"children":"Hello World"}]
1:I["./ClientComponent",["chunk.js"],"default"]
2:{"id":42,"name":"User"}
Setiap baris punya makna berbeda:
0:— React element tree (yang akan dirender)1:I— Client Component reference (lazy import)2:— Data module atau props
Di sisi client, React mem-parse setiap baris ini dan merekonstruksi component tree secara bertahap.
Alur Kerja Lengkapnya
- Request masuk ke server
- Server merender RSC dan menghasilkan stream
- Stream dikirim ke client via HTTP (atau diteruskan ke SSR renderer)
- Client mem-parse payload menggunakan
react-server-dom-webpack/client - Component tree direkonstruksi dan dirender ke DOM
Proses ini terlihat sederhana. Tapi langkah keempat — parsing payload — adalah tempat di mana celah keamanan bisa muncul.
Apa Itu Deserialization Sink dan Kenapa Berbahaya
Deserialization sink adalah titik di dalam kode di mana data eksternal (yang belum tervalidasi) diproses dan diubah menjadi objek atau instruksi yang bisa dieksekusi.
Dalam konteks RSC, sink ini terjadi ketika React client mem-parse RSC Payload yang diterima dari server.
Mengapa Ini Berbahaya
Anggap saja seperti JSON.parse() yang lebih powerful. Jika seseorang bisa memanipulasi payload yang diterima client, mereka berpotensi menyisipkan instruksi berbahaya yang akan dieksekusi langsung oleh React.
Yang membuat ini lebih mengkhawatirkan: React Flight deserializer mendukung beberapa tipe data khusus selain string dan angka biasa. Ia bisa merepresentasikan referensi ke fungsi, lazy module reference, hingga objek khusus React seperti Suspense dan ErrorBoundary.
Contoh format payload yang merepresentasikan Client Component reference:
1:I["path/to/ClientComponent",["client-chunk.js"],"ComponentName"]
Jika attacker bisa menyisipkan referensi modul yang berbeda ke dalam payload ini, mereka berpotensi membuat client me-load dan mengeksekusi modul yang tidak seharusnya.
Vektor Serangan Nyata di RSC yang Harus Diwaspadai
Mari kita bahas skenario serangan konkret yang relevan untuk developer Indonesia.
1. Manipulasi RSC Payload via Proxy atau Man-in-the-Middle
Jika aplikasi kamu tidak menggunakan HTTPS secara konsisten, atau ada layer proxy di antaranya, attacker bisa memodifikasi RSC stream sebelum sampai ke client.
Payload yang terlihat normal:
0:["$","div",null,{"children":"Selamat datang"}]
Bisa dimodifikasi menjadi referensi ke modul berbahaya:
1:I["../../node_modules/some-malicious-module",["chunk.js"],"exec"]
Mitigasi utama di sini adalah selalu enforce HTTPS dan Subresource Integrity (SRI) untuk semua chunk yang di-load.
2. Server-Side Data Injection Melalui Prop yang Tidak Disanitasi
Ini adalah vektor yang paling sering diabaikan developer Indonesia.
Perhatikan contoh Server Component berikut:
// app/user/page.tsx (Server Component)
async function UserPage({ searchParams }) {
const query = searchParams.q; // BAHAYA: langsung dari URL
const data = await db.query(`SELECT * FROM users WHERE name = '${query}'`);
return <UserList users={data} />;
}
Data dari database langsung dimasukkan ke props tanpa sanitasi. Jika data mengandung string yang menyerupai RSC directive (misalnya nilai yang dimulai dengan $), React deserializer di client bisa salah menginterpretasikan data tersebut.
React memang punya proteksi parsial untuk ini, tapi tidak ada jaminan penuh jika data benar-benar tidak disanitasi sama sekali.
Cara yang benar:
// app/user/page.tsx (Server Component) — AMAN
async function UserPage({ searchParams }) {
// Validasi input terlebih dahulu
const query = typeof searchParams.q === 'string'
? searchParams.q.slice(0, 100)
: '';
// Gunakan parameterized query
const data = await db.query(
'SELECT id, name, email FROM users WHERE name = $1',
[query]
);
// Hanya kirim field yang dibutuhkan
const safeUsers = data.map(({ id, name, email }) => ({ id, name, email }));
return <UserList users={safeUsers} />;
}
3. Prototype Pollution via Deserialized Object
RSC Payload bisa merepresentasikan objek JavaScript kompleks. Jika server mengembalikan objek yang berasal dari input user tanpa deep sanitasi, ada risiko prototype pollution di sisi client.
Contoh berbahaya:
// Server Component yang vulnerable
async function ConfigPage({ params }) {
const userConfig = await getUserConfig(params.userId);
// userConfig bisa berisi: { "__proto__": { "isAdmin": true } }
return <Dashboard config={userConfig} />;
}
Selalu lakukan deep clone dan validasi struktur objek sebelum meneruskannya ke client:
import { z } from 'zod';
const ConfigSchema = z.object({
theme: z.enum(['light', 'dark']),
language: z.enum(['id', 'en']),
notifications: z.boolean(),
});
async function ConfigPage({ params }) {
const rawConfig = await getUserConfig(params.userId);
// Validasi dan strip field yang tidak dikenal
const userConfig = ConfigSchema.parse(rawConfig);
return <Dashboard config={userConfig} />;
}
4. Sensitive Data Leakage via RSC Payload
Ini bukan serangan injection, tapi tetap kritis.
RSC Payload dikirim sebagai plaintext yang bisa dibaca siapa saja di DevTools browser. Jika Server Component kamu mengembalikan data sensitif (password hash, token, data pribadi lengkap), semua itu terekspos ke client.
// SANGAT BERBAHAYA
async function UserProfile({ userId }) {
const user = await db.users.findById(userId);
// user berisi: { id, name, email, passwordHash, apiSecret, ... }
return <ProfileCard user={user} />; // Semua field terkirim ke client!
}
Selalu pilih hanya field yang dibutuhkan:
// AMAN
async function UserProfile({ userId }) {
const user = await db.users.findById(userId, {
select: { id: true, name: true, avatar: true }
});
return <ProfileCard user={user} />;
}
Cara Mitigasi dan Defensive Coding di RSC
Setelah memahami vektor serangannya, berikut pendekatan mitigasi yang bisa langsung diterapkan.
Validasi Input di Setiap Entry Point
Gunakan library validasi seperti Zod atau Valibot di setiap Server Component yang menerima input dari luar:
import { z } from 'zod';
const SearchParamsSchema = z.object({
q: z.string().max(200).optional(),
page: z.coerce.number().int().positive().max(100).default(1),
category: z.enum(['tech', 'business', 'design']).optional(),
});
export default async function SearchPage({ searchParams }) {
const params = SearchParamsSchema.safeParse(searchParams);
if (!params.success) {
return <ErrorPage message="Parameter tidak valid" />;
}
// Gunakan params.data yang sudah tervalidasi
const results = await search(params.data);
return <SearchResults results={results} />;
}
Terapkan Content Security Policy yang Ketat
CSP membantu mencegah eksekusi script yang tidak diotorisasi, termasuk jika attacker berhasil menyisipkan referensi modul berbahaya:
// next.config.js
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'nonce-{NONCE}'",
"connect-src 'self'",
"img-src 'self' data: https:",
].join('; '),
},
],
},
];
},
};
Batasi Surface Area Server Actions
Server Actions adalah fitur powerful RSC yang juga bisa menjadi attack vector. Selalu validasi input dan lakukan otorisasi:
'use server';
import { auth } from '@/lib/auth';
import { z } from 'zod';
const UpdateProfileSchema = z.object({
name: z.string().min(1).max(100),
bio: z.string().max(500).optional(),
});
export async function updateProfile(formData: FormData) {
// 1. Pastikan user sudah login
const session = await auth();
if (!session?.user) {
throw new Error('Unauthorized');
}
// 2. Validasi input
const input = UpdateProfileSchema.safeParse({
name: formData.get('name'),
bio: formData.get('bio'),
});
if (!input.success) {
throw new Error('Input tidak valid');
}
// 3. Gunakan data tervalidasi
await db.users.update({
where: { id: session.user.id },
data: input.data,
});
}
Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.
Checklist Keamanan Sebelum Deploy Aplikasi RSC ke Production
Gunakan checklist ini sebelum setiap deployment:
Validasi Input
- Semua
searchParamsdanparamsdari URL divalidasi dengan schema - Semua form input melalui Server Actions sudah divalidasi dengan Zod atau Valibot
- Input user tidak pernah langsung masuk ke query database (pakai parameterized query)
Data Exposure
- Server Components hanya mengirim field yang benar-benar dibutuhkan client
- Tidak ada password hash, API key, atau token yang terkirim ke client
- Lakukan audit RSC Payload di Network tab DevTools sebelum go live
Otorisasi
- Setiap Server Action memverifikasi sesi dan role user
- Data yang ditampilkan sudah difilter berdasarkan hak akses user
- Tidak ada endpoint yang mengembalikan data user lain tanpa pengecekan
Infrastruktur
- HTTPS diterapkan secara konsisten (termasuk redirect dari HTTP)
- Content Security Policy sudah dikonfigurasi
- Header keamanan lain sudah aktif (X-Frame-Options, X-Content-Type-Options)
Dependencies
- Versi React dan Next.js sudah up-to-date (patch keamanan terbaru)
- Tidak ada library pihak ketiga yang suspicious atau tidak aktif di-maintain
-
npm auditdijalankan dan tidak ada vulnerability level high/critical
Penutup
React Flight Protocol adalah mekanisme yang sangat cerdas. Ia memungkinkan streaming komponen dari server ke client dengan performa tinggi — dan ini adalah salah satu alasan RSC menjadi standar modern.
Tapi seperti teknologi powerful lainnya, ia membawa tanggung jawab keamanan yang tidak boleh diabaikan.
Deserialization sink di RSC bukan hanya teori. Ini adalah risiko nyata yang relevan untuk setiap developer Indonesia yang membangun aplikasi dengan Next.js App Router hari ini.
Langkah paling penting yang bisa kamu ambil sekarang adalah mulai dari validasi input — karena sebagian besar vektor serangan yang dibahas di artikel ini bisa dicegah hanya dengan validasi yang konsisten di setiap entry point Server Component.
Secure by default. Bukan secure by luck.