Penggunaan Large Language Models (LLM) untuk mempercepat pembuatan kode sering menghasilkan ekspresi reguler (regex) yang tampak meyakinkan namun mengandung cacat logika mendasar. Pada layer sanitasi backend, regex yang salah tidak selalu melempar exception secara eksplisit. Kesalahan ini sering berujung pada silent failure: input berbahaya lolos tanpa peringatan, server mengalami lonjakan utilisasi CPU (ReDoS), atau data non-ASCII terpotong secara tidak semestinya.
Gejala Kegagalan pada Produksi
Kegagalan validasi berbasis regex hasil LLM biasanya terdeteksi melalui tiga gejala utama di sistem backend:
- Bypassed Input Validation: Karakter kontrol, script tag, atau delimiter SQL tetap lolos ke layer persistensi data karena batasan pattern yang longgar atau hilangnya anchor terminal.
- Server Hang Akibat ReDoS: Utilisasi CPU worker backend mencapai 100% saat memproses payload string berulang dengan struktur tertentu, menyebabkan catastrophic backtracking.
- Edge Case Unhandled UTF-8: Penggunaan pattern kelas karakter seperti
[a-zA-Z0-9]memotong nama legal beraksen (misal: résumé) atau memicu kegagalan encoding multi-byte pada parser downstream.
Studi Kasus: Kerentanan Regex Hasil Generate AI
Berikut implementasi backend umum di Node.js/TypeScript untuk sanitasi input username dan string komentar yang dihasilkan atas prompt ke LLM: "Buat fungsi sanitasi komentar dan username yang aman dari XSS dan karakter aneh".
// BEFORE: Implementasi rentan hasil LLM
export function sanitizeUserInput(input: string): { isValid: boolean; sanitized: string } {
// LLM menghasilkan regex ini untuk 'membersihkan tag dan membatasi karakter'
// Cacat 1: Nested quantifier (([a-z]+)+) rentan Catastrophic Backtracking (ReDoS)
// Cacat 2: Kurang anchor (^ dan $), sanitasi hanya mencocokkan substring pertama
// Cacat 3: Flag regex tidak memperhitungkan karakter unicode UTF-8 multi-byte
const dangerousPattern = /<([a-z]+)+>.*</([a-z]+)>/i;
if (dangerousPattern.test(input)) {
return { isValid: false, sanitized: "" };
}
// Percobaan sanitasi username hanya mengizinkan alfanumerik biasa
const sanitized = input.replace(/[^a-zA-Z0-9_ -]/g, "");
return { isValid: true, sanitized };
}
Root Cause Analysis
Terdapat dua akar masalah teknis dari kode di atas:
- Hallucinated Boundary & Quantifier Nesting: Pola
<([a-z]+)+>memiliki non-deterministic finite automaton (NFA) state yang ambigu. Ketika input berisi<aaaaaaaaaaaaaaaaaaaaaaaaaaaa!, engine regex mencoba seluruh permutasi pembagian karakter sebelum gagal, memicu komputasi eksponensial $O(2^n)$. - Asumsi ASCII vs Unicode: Regex yang dihasilkan tidak menyertakan flag unicode
uatauv. Karakter surrogate pair UTF-16 di JavaScript dapat terpotong di tengah byte saat menggunakan replace berbasis rentang karakter ASCII.
Langkah Perbaikan dan Refactoring
Strategi penanganan masalah ini bertumpu pada arsitektur validasi bertahap: memisahkan tugas validasi integritas dari tugas sanitasi, serta mengganti regex monolitik dengan parser atau library terverifikasi.
// AFTER: Sanitasi bertahap dengan parser eksplisit dan assertion test
import DOMPurify from "dompurify";
import { JSDOM } from "jsdom";
const window = new JSDOM("").window;
const purify = DOMPurify(window);
// Alternatif lebih ringan tanpa library DOM: gunakan library validator khusus schema
// ponytail: batasan input 255 karakter untuk cegah memori bloat sebelum diproses
const MAX_INPUT_LENGTH = 255;
const SAFE_USERNAME_REGEX = /^[\p{L}\p{N}_\-\s]{1,50}$/u;
export function validateAndSanitize(rawInput: string): { isValid: boolean; output: string } {
if (typeof rawInput !== "string" || rawInput.length === 0 || rawInput.length > MAX_INPUT_LENGTH) {
return { isValid: false, output: "" };
}
// 1. Sanitasi payload HTML menggunakan Context-Aware Parser, bukan Regex
const cleanHtml = purify.sanitize(rawInput);
// 2. Validasi struktural menggunakan Unicode Property Escapes aman (O(N))
const isMatch = SAFE_USERNAME_REGEX.test(cleanHtml);
return {
isValid: isMatch,
output: cleanHtml.trim(),
};
}
Verifikasi dengan Runnable Assertion Tests
Uji coba berikut membuktikan kegagalan implementasi awal dan keberhasilan implementasi baru tanpa menggunakan framework testing eksternal.
// verification.ts
import { sanitizeUserInput } from "./before";
import { validateAndSanitize } from "./after";
function runAssertions() {
console.log("[INFO] Menjalankan pengujian...");
// Kasus 1: ReDoS Attack String
const redosPayload = "<" + "a".repeat(25) + "!";
const startTimerBefore = performance.now();
sanitizeUserInput(redosPayload);
const timeBefore = performance.now() - startTimerBefore;
console.log(`Payload ReDoS (Regex Lama): ${timeBefore.toFixed(2)}ms`);
const startTimerAfter = performance.now();
validateAndSanitize(redosPayload);
const timeAfter = performance.now() - startTimerAfter;
console.log(`Payload ReDoS (Metode Baru): ${timeAfter.toFixed(2)}ms`);
if (timeBefore > 500) {
console.warn("[WARNING] Regex lama terbukti rentan terhadap ReDoS (Catastrophic Backtracking).");
}
// Kasus 2: Unicode Support (Nama beraksen)
const unicodeName = "Müller";
const legacyResult = sanitizeUserInput(unicodeName);
const fixedResult = validateAndSanitize(unicodeName);
console.assert(legacyResult.sanitized !== unicodeName, "GAGAL: Kode lama harusnya merusak nama UTF-8.");
console.assert(fixedResult.isValid === true, "GAGAL: Kode baru harusnya menerima karakter UTF-8 valid.");
console.assert(fixedResult.output === unicodeName, "GAGAL: Output baru harus mempertahankan karakter Müller.");
// Kasus 3: XSS Bypass lewat anchor hilang
const xssPayload = "normalText<script>alert(1)</script>";
const xssFixedResult = validateAndSanitize(xssPayload);
console.assert(xssFixedResult.isValid === false, "GAGAL: Script injection harus dieliminasi dan dinyatakan invalid.");
console.log("[OK] Semua assertion selesai dijalankan dengan benar.");
}
runAssertions();
Rekomendasi Praktis Implementasi
- Tolak Regex Kompleks untuk Parsing: Jangan gunakan regex tunggal untuk memvalidasi markup terstruktur seperti HTML, URL, atau SQL. Gunakan dedicated parser.
- Gunakan Flag Unicode: Selalu gunakan flag
/uatau/vjika regex memproses input berbasis teks dari user guna menghindari silent corruption pada data non-ASCII. - Linting dan Timeout: Pasang aturan linter seperti
eslint-plugin-securityuntuk mendeteksi catastrophic backtracking sejak fase CI/CD, atau terapkan timeout execution jika runtime mendukung (misalnya regex engine dengan batas step).
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!