Implementasi Content Security Policy (CSP) Level 3 pada Next.js App Router: Mitigasi XSS Modern

Mengapa allowlist domain CSP Level 2 sudah usang: panduan menerapkan CSP Level 3 berbasis nonce kriptografis dan hash SHA-256 pada Next.js App Router, integrasi skrip analitik, dan pelaporan report-only.

PuluhLabs Security Team, Lead Application Security Engineer · 9 November 2026 · 6 min baca

Implementasi Content Security Policy (CSP) Level 3 pada Next.js App Router: Mitigasi XSS Modern
Keamanan Siber
DECOUPLED

Isi artikel ini

  • Mengapa Pendekatan Whitelist Domain CSP Level 2 Telah Usang?
  • Implementasi Nonce Generator pada Next.js Middleware
  • Mengonsumsi Nilai Nonce pada Root Layout Next.js
  • Memantau Pelanggaran dengan Mode Report-Only
  • Strategi Hash-Based CSP untuk Halaman Statis (SSG) dan Edge CDN

Serangan Cross-Site Scripting (XSS) secara konsisten menempati daftar ancaman keamanan aplikasi web paling berbahaya menurut OWASP Top 10. Meskipun pengembang modern telah memanfaatkan sanitasi input dan escaping otomatis pada library antarmuka seperti React, celah keamanan tetap dapat timbul dari injeksi dependensi npm pihak ketiga (supply chain attack), kerentanan pada parsing atribut HTML tak terduga, atau manipulasi skrip analitik eksternal.

Solusi pertahanan lapis kedua yang paling efektif untuk membatasi dampak eksekusi kode berbahaya di browser pengguna adalah penerapan header HTTP Content Security Policy (CSP) Level 3. Dengan CSP yang terkonfigurasi ketat, browser akan menolak mengeksekusi skrip berbahaya meskipun payload XSS berhasil menembus lapisan HTML aplikasi.

text
Alur Kerja Nonce-Based Content Security Policy Level 3:
1. Permintaan Masuk ────> [Next.js Middleware]
                              │ Menghasilkan kriptografis unik: nonce = base64(randomBytes)
                              ▼
                          [HTTP Response Headers]
                          Content-Security-Policy:
                            script-src 'nonce-d83a1b...' 'strict-dynamic';
                              │
2. Browser Memvalidasi Skrip  ▼
   ┌───────────────────────────────────────────────┐
   │ <script nonce="d83a1b..."> // Diizinkan       │
   │ <script>alert(document.cookie)</script>       │
   │ └──> [DITOLAK BROWSER: Missing Valid Nonce]   │
   └───────────────────────────────────────────────┘

Mengapa Pendekatan Whitelist Domain CSP Level 2 Telah Usang? #

Pada standar CSP Level 1 dan Level 2 generasi lama, pengembang umumnya menulis aturan berbasis daftar domain yang diizinkan (allowlist), contohnya:

http
Content-Security-Policy: script-src 'self' https://apis.google.com https://cdnjs.cloudflare.com;

Pendekatan ini memiliki kelemahan mendasar yang fatal. Jika aplikasi mengizinkan CDN publik seperti cdnjs.cloudflare.com atau domain Google APIs, peretas dapat menyisipkan payload XSS yang memuat endpoint JSONP rentan atau library lama yang memiliki bug eksekusi kode dari CDN yang sama. Akibatnya, browser tetap mengeksekusi payload jahat karena domain asalnya cocok dengan whitelist.

CSP Level 3 mengatasi masalah ini dengan beralih dari daftar domain ke sistem kriptografi berbasis nonce (number used once) yang dipadukan dengan direktif 'strict-dynamic'.

Dengan 'strict-dynamic':

  • Browser modern hanya akan mengeksekusi tag skrip yang memiliki atribut nonce persis sama dengan nilai acak yang dikirimkan server pada header HTTP.
  • Skrip tepercaya yang memuat nonce tersebut berhak membuat tag skrip turunan secara dinamis tanpa perlu mendaftarkan setiap sub-domain skrip baru ke dalam whitelist header.
  • Browser akan mengabaikan keyword 'unsafe-inline', http:, atau daftar domain lama jika 'strict-dynamic' aktif, memastikan backward compatibility bagi browser generasi lama tanpa mengorbankan keamanan browser modern.

Implementasi Nonce Generator pada Next.js Middleware #

Dalam Next.js App Router, implementasi nonce harus dilakukan di dalam middleware agar setiap request HTTP yang masuk menerima nilai token kriptografis yang benar-benar acak dan segar.

Berikut adalah implementasi berkas middleware.ts pada root project Next.js:

typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  // 1. Buat nonce kriptografis berbasis base64 (16 byte acak)
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64');

  // 2. Susun direktif CSP Level 3 yang komprehensif
  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'report-sample';
    style-src 'self' 'unsafe-inline';
    img-src 'self' blob: data: https://images.unsplash.com https://puluhlabs.com;
    font-src 'self' data:;
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    connect-src 'self' https://vitals.vercel-insights.com https://analytics.google.com;
    upgrade-insecure-requests;
  `.replace(/\s{2,}/g, ' ').trim();

  // 3. Masukkan nonce ke header request internal agar dapat dibaca Server Components
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-nonce', nonce);
  requestHeaders.set('content-security-policy', cspHeader);

  // 4. Teruskan request dengan menyertakan CSP pada response headers
  const response = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });

  response.headers.set('Content-Security-Policy', cspHeader);
  return response;
}

export const config = {
  matcher: [
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico|robots.txt).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
  ],
};

Mengonsumsi Nilai Nonce pada Root Layout Next.js #

Setelah middleware menyisipkan x-nonce ke dalam header request, komponen app/layout.tsx dapat membaca header tersebut untuk disematkan ke elemen skrip aplikasi dan pelacak eksternal seperti Google Tag Manager.

tsx
// app/layout.tsx
import { headers } from 'next/headers';
import Script from 'next/script';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const nonce = headers().get('x-nonce') || undefined;

  return (
    <html lang="id">
      <head>
        {/* Skrip Analitik Eksternal yang diizinkan melalui nonce */}
        <Script
          id="google-tag-manager"
          strategy="afterInteractive"
          nonce={nonce}
          dangerouslySetInnerHTML={{
            __html: `
              (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
              new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
              j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
              'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
              })(window,document,'script','dataLayer','GTM-XXXXXXX');
            `,
          }}
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Memantau Pelanggaran dengan Mode Report-Only #

Menerapkan CSP secara langsung pada aplikasi skala besar berisiko memecah fungsionalitas antarmuka apabila terdapat skrip pihak ketiga (seperti widget customer service atau payment gateway iframe) yang belum didaftarkan dengan benar.

Langkah mitigasi paling bijak adalah mengaktifkan mode pelaporan selama masa uji coba 14 hari:

http
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-...' 'strict-dynamic'; report-uri /api/security/csp-reports;

Pada endpoint backend /api/security/csp-reports, tim engineer dapat mencatat setiap log insiden dalam format JSON:

json
{
  "csp-report": {
    "document-uri": "https://puluhlabs.com/checkout",
    "referrer": "",
    "violated-directive": "script-src",
    "effective-directive": "script-src",
    "original-policy": "default-src 'self'...",
    "disposition": "report",
    "blocked-uri": "eval",
    "line-number": 42,
    "source-file": "https://thirdparty.example.com/widget.js"
  }
}

Audit berkala terhadap data laporan ini memungkinkan tim mengidentifikasi ekstensi browser nakal pengguna versus potensi payload XSS nyata sebelum akhirnya beralih ke enforcement Content-Security-Policy aktif secara permanen.

Strategi Hash-Based CSP untuk Halaman Statis (SSG) dan Edge CDN #

Ketika aplikasi web Next.js dikompilasi menggunakan mode static export (output: 'export') atau di-host pada jaringan CDN edge statis seperti Cloudflare Pages, pembuatan token nonce secara dinamis per request HTTP tidak dapat dilakukan tanpa komputasi edge worker. Jika seluruh halaman di-cache secara permanen di edge, menyematkan nonce statis akan membatalkan prinsip dasar keamanan nonce, karena penyerang dapat membaca nilai nonce statis tersebut dan menyisipkannya pada tag skrip injeksi mereka.

Sebagai alternatif bagi arsitektur Server-Side Generation (SSG), pengembang dapat memanfaatkan verifikasi integritas berbasis Hash SHA-256.

Dalam model hash-based CSP:

  1. Setiap potongan skrip inline dihitung nilai ringkasan kriptografisnya (cryptographic digest) menggunakan algoritma SHA-256, SHA-384, atau SHA-512.
  2. Nilai hash yang dihasilkan kemudian didaftarkan secara eksplisit ke dalam header script-src:
    http
    Content-Security-Policy: script-src 'self' 'sha256-qznLcsROx4GACP2dm0UCK3NzsLUKKA8ISFix9KCJ38=' 'sha256-RFWzHiAcPvMWbUIJSio00mR8urke4IFS8ckMvdDw04o=';
  3. Saat browser mengurai berkas HTML, browser akan menghitung nilai hash dari setiap blok skrip inline di dalam dokumen. Jika hash tersebut cocok dengan salah satu entri yang terdaftar pada header HTTP, skrip diizinkan berjalan. Jika konten skrip dimanipulasi walau hanya satu karakter spasi, browser langsung menolak eksekusinya.

Pendekatan hash-based ini sangat ideal bagi situs portal korporat, dokumentasi teknis, dan landing page statis karena memungkinkan cache HTTP berdurasi panjang (long-term immutable caching) pada jaringan CDN global tanpa memerlukan waktu eksekusi komputasi server tambahan pada setiap kunjungan pengguna.

Bagikan Artikel Ini:
Topik Terkait:Keamanan SiberNext.jsXSS MitigationContent Security PolicyApp Router
Istilah Teknis Terkait (Knowledge Graph Glossary)
Jejaring Entitas & Solusi Teknis Terkait
3 Solusi Terintegrasi
Compliance Tool

Diagnostik Kepatuhan UU PDP No. 27/2022

Audit kesiapan arsitektur web Anda terhadap regulasi perlindungan data pribadi nasional.

Buka Solusi
Legal Template

Generator Matriks ROPA Data Pribadi

Susun Record of Processing Activities (ROPA) resmi untuk kepatuhan audit korporat.

Buka Solusi
Compliance Hub

Panduan Regulasi Siber & Privasi

Rangkuman pasal krusial, sanksi denda 2% omset, dan protokol insiden 72 jam.

Buka Solusi
TO
PuluhLabs Security TeamPenulis Terverifikasi

Lead Application Security Engineer

Praktisi dan konsultan pengembangan platform digital terpercaya dengan pengalaman memimpin perancangan lebih dari 50+ proyek untuk skala korporat, bisnis B2B, dan instansi profesional. Berfokus pada pembangunan solusi web yang tangguh, cepat, aman, serta dirancang untuk mendorong pertumbuhan bisnis jangka panjang.

Ditinjau & divalidasi oleh: Tim Profesional PuluhLabs
Terakhir ditinjau: 9 November 2026

Ingin Menerapkan Standar Performa Ini pada Proyek Agensi Anda?

Tim PuluhLabs siap menjadi mitra rekayasa digital rahasia (white-label) untuk agensi Anda. Garansi performa skor Lighthouse 90+, Core Web Vitals hijau, dan 100% hak milik kode.

Konsultasi Proyek Bersama Tim PuluhLabs

Artikel Rekomendasi Selanjutnya

Lihat Semua
  1. Pengembangan Web

    Arsitektur Micro-Frontends vs Modular Monolith: Panduan Memilih untuk Aplikasi Web Skala Enterprise

  2. Pengembangan Web

    Implementasi Zero-Trust Architecture pada REST API dan Webhook Korporat

  3. Pengembangan Web

    Optimasi Query PostgreSQL Skala Jutaan Baris: Indexing, Table Partitioning, dan Connection Pooling