Evolusi sistem agen otonom (LLM tools, smolagents, browser automation) mendorong pola rekayasa balik (reverse-engineering) endpoint API internal aplikasi web. Skenario umum: pengguna atau penyerang menyalin cookie sesi atau token bearer dari DevTools browser, lalu memasukkannya ke dalam konfigurasi script atau runtime agen (seperti Python requests, httpx, atau Node.js undici) untuk mengeksekusi aksi otomatis secara programmatic.

Masalah mendasarnya adalah session portability: bearer credential tidak terikat secara kriptografis pada lingkungan eksekusi asalnya. Cookie beratribut HttpOnly, Secure, dan SameSite=Lax hanya mencegah pencurian via XSS dan basic CSRF, tetapi tidak mencegah kredensial tersebut diekstraksi dan dimainkan ulang (replay) pada runtime HTTP headless.

1. Cryptographic Session Binding Menggunakan DPoP (RFC 9449)

Solusi paling kokoh terhadap replay token adalah menghilangkan sifat bearer menggunakan Demonstrating Proof-of-Possession (DPoP, RFC 9449). Dengan DPoP, setiap permintaan HTTP wajib membuktikan kepemilikan kunci privat yang pasangannya telah didaftarkan saat sesi diinisiasi.

Mekanisme pada Browser Client

Browser web modern mendukung WebCrypto API untuk membuat keypair asimetris non-extractable (misalnya ECDSA P-256):

// Inisialisasi keypair sekali saat login/bootstrapping SPA
const keyPair = await window.crypto.subtle.generateKey(
  { name: "ECDSA", namedCurve: "P-256" },
  false, // Private key tidak dapat diekspor via JS
  ["sign", "verify"]
);

// Menyimpan CryptoKey ke IndexedDB terisolasi origin
await saveKeyToIndexedDB(keyPair.privateKey);

Karena extractable: false diterapkan, skrip atau ekstensi biasa tidak dapat mengekstrak raw private key ke clipboard. Setiap request API menandatangani payload kecil (berisi HTTP method, target URL, dan timestamp htm, htu, iat) dan mengirimkannya melalui header DPoP. Ketika penyerang menyalin cookie sesi atau access token ke Python agent, agen tersebut tidak memiliki private key WebCrypto untuk membuat header DPoP yang valid.

2. Validasi Browser Fetch Metadata Middleware

Browser engine berbasis Chromium, Gecko, dan WebKit menyertakan header Sec-Fetch-* pada setiap outgoing request. Header ini masuk kategori forbidden header name, artinya JavaScript di browser tidak dapat memodifikasi nilainya secara artifisial.

  • Sec-Fetch-Site: Menjelaskan relasi origin pemanggil (same-origin, same-site, cross-site).
  • Sec-Fetch-Mode: Menjelaskan jenis request (cors, navigate, no-cors).
  • Sec-Fetch-Dest: Menjelaskan tujuan resource (empty untuk XHR/fetch, document, image).
  • Origin: Menunjukkan origin pemanggil request mutasi.

Autonomous agent berbasis library HTTP generik (cURL, Python requests, Go net/http) umumnya tidak mengirim header-header ini secara otomatis, atau mengirim kombinasi nilai yang tidak konsisten dengan perilaku SPA resmi.

3. Inspeksi TLS Fingerprint (JA4/JA3) di API Gateway

Penyerang dapat merekayasa header HTTP standar, tetapi peniruan TLS handshake pada level runtime jauh lebih sulit. Tumpukan TLS pada Python (OpenSSL standar) menghasilkan parameter ClientHello (ciphers, extensions, elliptic curves) yang berbeda signifikan dibanding BoringSSL milik Chromium atau NSS milik Firefox.

Gunakan API Gateway (seperti Envoy, Cloudflare, atau HAProxy) untuk mengekstrak JA4/JA3 hash dari request TLS. Bandingkan header User-Agent dengan JA4 fingerprint:

  • Request mengaku Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ... Chrome/...
  • Namun JA4 fingerprint mengindikasikan signature Python urllib3 / requests.
  • Aksi: Gateway segera melakukan drop koneksi atau menandai session cookie untuk di-revoke seketika.

4. Implementasi Middleware Backend (Go)

Contoh middleware terpadu dalam Go standard library untuk memverifikasi Sec-Fetch Metadata, origin, dan stub validasi DPoP:

package middleware

import (
	"crypto/subtle"
	"net/http"
	"strings"
)

type SecurityConfig struct {
	AllowedOrigin string
	EnforceDPoP   bool
}

func EnforceSessionIntegrity(cfg SecurityConfig) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// 1. Bypass pengecekan browser untuk path non-API jika diperlukan
			if !strings.HasPrefix(r.URL.Path, "/api/") {
				next.ServeHTTP(w, r)
				return
			}

			// 2. Fetch Metadata Verification
			secFetchSite := r.Header.Get("Sec-Fetch-Site")
			secFetchMode := r.Header.Get("Sec-Fetch-Mode")
			origin := r.Header.Get("Origin")

			// Request valid dari SPA harus bertipe same-origin/same-site dengan mode cors
			if secFetchSite != "" && secFetchSite != "same-origin" && secFetchSite != "same-site" {
				http.Error(w, `{"error":"unauthorized_fetch_site"}`, http.StatusForbidden)
				return
			}
			if secFetchMode != "" && secFetchMode != "cors" {
				http.Error(w, `{"error":"invalid_fetch_mode"}`, http.StatusForbidden)
				return
			}
			if origin != "" && subtle.ConstantTimeCompare([]byte(origin), []byte(cfg.AllowedOrigin)) != 1 {
				http.Error(w, `{"error":"origin_mismatch"}`, http.StatusForbidden)
				return
			}

			// 3. TLS Fingerprint Mismatch Detection (Diinjeksi reverse proxy/gateway)
			if isHeadlessRuntime(r.Header.Get("User-Agent"), r.Header.Get("X-JA4-Fingerprint")) {
				http.Error(w, `{"error":"client_anomaly_detected"}`, http.StatusForbidden)
				return
			}

			// 4. DPoP Proof Presence
			if cfg.EnforceDPoP {
				dpopHeader := r.Header.Get("DPoP")
				if dpopHeader == "" {
					http.Error(w, `{"error":"missing_dpop_proof"}`, http.StatusUnauthorized)
					return
				}
				// ponytail: Validasi token JWT DPoP, cek method, htu, dan jti replay di cache.
			}

			next.ServeHTTP(w, r)
		})
	}
}

func isHeadlessRuntime(ua, ja4 string) bool {
	// Cek anomali: Mengaku browser tapi ja4 cocok dengan library scripting umum
	if strings.Contains(ua, "Mozilla/") && strings.HasPrefix(ja4, "t13d") /* contoh pola non-browser */ {
		return true
	}
	return false
}
Catatan simplifikasi: Verifikasi DPoP di atas hanya mengecek keberadaan header. Tambahkan verifikasi kriptografi JWT DPoP (signature public key) dan cache jti di Redis untuk mencegah replay header DPoP itu sendiri saat memproteksi transaksi finansial atau mutasi data sensitif.

5. Trade-offs dan Keterbatasan

Menerapkan pengamanan ini memiliki konsekuensi arsitektural yang perlu diperhitungkan:

  • Puppeteer & Playwright: Penyerang tingkat lanjut masih dapat mengotomatisasi browser Chromium utuh (bukan script HTTP ringan). Hal ini tetap memaksa bot agen menghabiskan resource komputasi dan memori yang jauh lebih besar (100-500 MB RAM per instance vs beberapa KB pada script HTTP), menaikkan cost serangan secara drastis.
  • State Persistence: Penyimpanan private key WebCrypto di IndexedDB rentan terhapus jika pengguna membersihkan browser storage, mengharuskan re-autentikasi sesi.
  • CFO / Corporate Proxies: Beberapa corporate intercepting proxy memodifikasi TLS handshake, yang dapat menghasilkan false-positive pada pengecekan JA4 fingerprint. Pertimbangkan mode warning/log-only sebelum blocking langsung pada enterprise user.