Lonjakan trafik dari automated AI scrapers dan crawlers model bahasa besar (LLM) sering membebani endpoint publik tanpa memberikan nilai balik bagi penyedia infrastruktur. Strategi defensif konvensional seperti pemblokiran berbasis User-Agent mudah dilewati melalui manipulasi header HTTP, sedangkan integrasi CAPTCHA konvensional (misalnya Turnstile atau reCAPTCHA) merusak integrasi API terprogram (mesin-ke-mesin) dan membebani latensi jaringan.

Solusi yang efektif untuk API publik adalah kombinasi sliding window rate limiter dan tantangan komputasi Proof-of-Work (PoW) deterministik berbasis SHA-256. Saat sebuah klien melampaui batas frekuensi normal, API Gateway tidak langsung memutus koneksi atau meminta interaksi manusia, melainkan mewajibkan klien mengorbankan siklus CPU untuk menyelesaikan teka-teki kriptografi sebelum memproses request berikutnya.

Kelemahan Pemblokiran Statis dan CAPTCHA Konvensional

Pemblokiran berbasis User-Agent bergantung pada kepatuhan pihak klien. Library HTTP modern seperti httpx, curl-impersonate, atau Puppeteer dapat memalsukan string identitas penjelajah dalam hitungan detik. Di sisi lain, memblokir subnet ASN datacenter sering kali menghasilkan false positive tinggi terhadap pengguna VPN legal atau layanan cloud downstream.

CAPTCHA visual atau interaktif menimbulkan masalah berikut pada arsitektur API:

  • Inkompatibilitas Mesin: API dirancang untuk klien otomatis, script SDK, dan microservices. Menyajikan halaman HTML CAPTCHA merusak parser JSON klien.
  • Vendor Lock-in dan Overhead Latensi: Verifikasi token CAPTCHA memerlukan round-trip network tambahan ke server pihak ketiga (50-200 ms).
  • Ekonomi Bypass Murah: Layanan pemecah CAPTCHA pihak ketiga (CAPTCHA farms) mengenakan biaya serendah $1 per 1.000 token yang dipecahkan, membuat biaya eksploitasi bagi bot tetap rendah.

Arsitektur Pertahanan: Sliding Window + PoW

Untuk menaikkan biaya asimetris bagi penyerang tanpa mengganggu pengguna sah, API Gateway menerapkan pipeline pertahanan berlapis:

  1. Zona Hijau (Trafik Normal): Klien berada di bawah batas rate limit (misal: 60 req/menit). Request langsung diteruskan ke backend upstream.
  2. Zona Kuning (Trafik Mencurigakan): Klien melampaui batas sliding window. Gateway merespons dengan status 429 Too Many Requests yang menyertakan header tantangan PoW dan nilai Retry-After.
  3. Zona Merah (Hard Limit): Jika klien mengabaikan tantangan atau mengirim solusi salah secara masif, gateway memutus koneksi pada layer network (TCP reset atau drop via iptables/eBPF).

Sliding Window Rate Limiter Menggunakan Redis

Pendekatan fixed window rentan terhadap bursting di perbatasan pergantian jendela waktu. Algoritma sliding window log memanfaatkan Redis Sorted Sets (ZSET) untuk akurasi presisi tinggi:

-- Lua Script untuk Sliding Window Atomic Check
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local clearBefore = now - window

-- Hapus hit di luar jendela waktu aktif
redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore)

-- Hitung total request dalam jendela waktu
local currentRequests = redis.call('ZCARD', key)

if currentRequests < limit then
    redis.call('ZADD', key, now, now)
    redis.call('EXPIRE', key, math.ceil(window / 1000))
    return 1
else
    return 0
end

Mekanisme Proof-of-Work Deterministik Berbasis SHA-256

PoW API Gateway bekerja dengan skema Hashcash. Gateway menerbitkan token tantangan yang ditandatangani HMAC agar stateless, meminimalkan penyimpanan memori pada server untuk menyimpan tantangan yang belum terpecahkan.

Struktur challenge token berupa string berformat: base64(payload).base64(hmac_signature). Payload memuat:

  • ip: Alamat IP klien pengirim.
  • timestamp: Waktu penerbitan (epoch milliseconds).
  • difficulty: Jumlah nol wajib di awal digest heksadesimal (misal: 4 untuk 0000...).
  • salt: String acak untuk mencegah kalkulasi pramenghitung (rainbow tables).

Klien wajib mencari nilai nonce integer sedemikian rupa sehingga:

SHA256(salt + nonce) dimulai dengan '0' sebanyak difficulty

Implementasi Middleware Verifikasi PoW (Node.js/TypeScript)

Contoh middleware backend berikut menggunakan pustaka standar node:crypto tanpa dependensi berat. Kode ini memvalidasi integritas HMAC, rentang kedaluwarsa waktu, proteksi replay attack, dan kebenaran komputasi hash.

import crypto from 'node:crypto';
import type { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
const HMAC_SECRET = process.env.POW_SECRET || 'ganti-dengan-secret-panjang-min-32-char';
const CHALLENGE_TTL_MS = 60000; // Challenge valid 60 detik

interface ChallengePayload {
  ip: string;
  ts: number;
  difficulty: number;
  salt: string;
}

export async function powVerificationMiddleware(
  req: Request, 
  res: Response, 
  next: NextFunction
): Promise<void> {
  const challengeHeader = req.header('X-PoW-Challenge');
  const nonceHeader = req.header('X-PoW-Nonce');

  if (!challengeHeader || !nonceHeader) {
    res.status(429).json({
      error: 'PoW challenge required',
      instructions: 'Solve PoW challenge to proceed'
    });
    return;
  }

  const parts = challengeHeader.split('.');
  if (parts.length !== 2) {
    res.status(400).json({ error: 'Malformed challenge token' });
    return;
  }

  const [rawPayload, signature] = parts;

  // 1. Verifikasi integritas HMAC token
  const expectedSignature = crypto
    .createHmac('sha256', HMAC_SECRET)
    .update(rawPayload)
    .digest('base64url');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
    res.status(403).json({ error: 'Invalid challenge signature' });
    return;
  }

  // 2. Dekode payload dan validasi atribut
  let payload: ChallengePayload;
  try {
    payload = JSON.parse(Buffer.from(rawPayload, 'base64url').toString('utf-8'));
  } catch {
    res.status(400).json({ error: 'Invalid payload encoding' });
    return;
  }

  const clientIp = req.ip || req.socket.remoteAddress || '';
  if (payload.ip !== clientIp) {
    res.status(403).json({ error: 'Challenge was not issued for this IP' });
    return;
  }

  const now = Date.now();
  if (now - payload.ts > CHALLENGE_TTL_MS || payload.ts > now + 5000) {
    res.status(410).json({ error: 'Challenge expired' });
    return;
  }

  // 3. Proteksi Replay Attack menggunakan Redis SET NX
  const replayKey = `pow:replay:${crypto.createHash('sha256').update(challengeHeader).digest('hex')}`;
  const ttlSeconds = Math.ceil(CHALLENGE_TTL_MS / 1000);
  const setNxResult = await redis.set(replayKey, '1', 'EX', ttlSeconds, 'NX');

  if (!setNxResult) {
    res.status(409).json({ error: 'Challenge already spent' });
    return;
  }

  // 4. Verifikasi komputasi SHA-256
  const hash = crypto
    .createHash('sha256')
    .update(payload.salt + nonceHeader)
    .digest('hex');

  const requiredPrefix = '0'.repeat(payload.difficulty);
  if (!hash.startsWith(requiredPrefix)) {
    res.status(422).json({ error: 'Incorrect PoW solution' });
    return;
  }

  // Solusi valid: loloskan ke layer handler berikutnya
  next();
}

Penanganan HTTP 429 dan Standarisasi Header

Saat batas laju request tercapai, Gateway harus merespons dengan status HTTP/1.1 429 Too Many Requests dan header yang terstandardisasi:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711929600
X-PoW-Challenge: eyJpcCI6IjIwMy4wLjExMy4xOSIsInRzIjoxNzExOTI5NTcwLCJkaWZmaWN1bHR5Ijo1LCJzYWx0IjoiYTFmOTJj...
X-PoW-Difficulty: 5

Header Retry-After memberikan instruksi eksplisit kepada klien yang taat protokol (seperti crawler etis) untuk menunda request selama durasi detik yang ditentukan, sekaligus menyediakan payload PoW bagi klien otomatis yang membutuhkan akses mendesak secara programatik.

Manajemen Memori dan Mitigasi Replay Attack

Implementasi PoW naif rentan terhadap serangan solution replay, di mana klien menghitung nonce sekali lalu menggunakannya untuk menembus puluhan request berikutnya. Penyimpanan challenge yang belum diverifikasi di basis data akan membuka celah serangan Memory Exhaustion jika bot membombardir permintaan challenge baru secara masif.

Pola mitigasi memori yang optimal:

  • Stateless Issuance: Challenge dienkripsi atau ditandatangani HMAC secara kriptografis tanpa disimpan di RAM/Redis saat diterbitkan. Klien menyimpan state tersebut sampai menyerahkan solusi.
  • Atomic Replay Tracking: Hanya challenge yang berhasil diselesaikan yang disimpan sementara di Redis menggunakan perintah SET key 1 EX [ttl] NX. Panjang kunci direduksi menggunakan hash SHA-256 untuk menghemat konsumsi memori Redis.
  • Strict TTL: Masa berlaku tantangan dibatasi ketat (misal 30–60 detik). Waktu simpan kunci anti-replay di Redis disamakan dengan sisa durasi TTL tantangan tersebut.

Strategi Fail-Closed vs Fail-Open Saat Lonjakan Throughput

Saat Redis mengalami latensi tinggi (degraded state) atau cluster kehabisan kapasitas throughput di tengah badai request, arsitektur harus memilih strategi failover:

StrategiMekanisme DegradasiKonsekuensi / Trade-off
Fail-OpenJika Redis timeout, request diloloskan langsung ke origin service tanpa verifikasi sliding window atau PoW.Melindungi pengguna sah dari downtime total, namun origin database rentan terhadap cascading failure akibat serangan DoS scrapers. Cocok untuk layanan transaksi bernilai tinggi.
Fail-ClosedJika Redis tidak merespons dalam window timeout (misal 20 ms), Gateway langsung memutus request dengan status HTTP 503 atau 429.Origin backend terlindungi secara penuh dari beban berlebih. Sebagian pengguna sah akan terblokir selama downtime Redis. Wajib diterapkan pada endpoint publik berat (misal search atau LLM inference).

Rekomendasi arsitektur produksi: Gunakan in-memory local circuit breaker (seperti leaky bucket di memori Nginx/Envoy lokal) sebagai fallback instan jika Redis gagal merespons, dengan strategi Fail-Closed terbatas hanya pada IP klien yang tidak memiliki API Key terverifikasi.