Rule Engine
Rule engine terdiri dari dua fungsi di src/scanner.js:
compileRules(vectors)— mengubah artefak JSON menjadi regex sungguhan, sekali saat cold start.evaluateThreat(context, engine)— mengevaluasi satu request dan mengembalikan skor tertinggi.
Kompilasi
Section titled “Kompilasi”export function compileRules(vectors) { const engine = []; for (const vector of vectors || []) { if (!vector.enabled) continue; const compiledSignals = vector.signals.map((sig) => ({ ...sig, regex: new RegExp(sig.pattern, sig.flags || 'i'), })); engine.push({ ...vector, signals: compiledSignals }); } return engine;}Poin penting:
- Vector dengan
enabled: falsedilewati sepenuhnya. - Flag default adalah
i(case-insensitive). - Kompilasi terjadi di module scope, bukan per request.
- Regex yang tidak valid akan menggagalkan build pipeline sebelum sampai ke edge.
Evaluasi skor
Section titled “Evaluasi skor”export function evaluateThreat(context, engine) { let highestScore = 0; let detectedVector = 'recon'; let vectorDef = null;
for (const vector of engine) { let vectorScore = 0; const triggeredSignals = new Set();
vector.signals.forEach((sig, index) => { let matched = false; for (const target of sig.where) { if (context[target] && sig.regex.test(context[target])) { matched = true; break; } } if (matched) { vectorScore += sig.score; triggeredSignals.add(`${vector.id}:${index}`); } });
if (vector.combo_bonus) { for (const combo of vector.combo_bonus) { const allMatch = combo.if_all.every((ref) => triggeredSignals.has(ref)); if (allMatch) vectorScore += combo.add; } }
if (vectorScore > highestScore) { highestScore = vectorScore; detectedVector = vector.id; vectorDef = vector; } }
return { isAttack: highestScore > 0, score: highestScore, attackType: detectedVector, vectorDef };}Aturan yang perlu diingat
Section titled “Aturan yang perlu diingat”- Setiap sinyal dihitung sekali per vector, meskipun polanya cocok di beberapa field.
- Skor adalah jumlah, bukan rata-rata. Satu sinyal kuat bisa langsung melewati threshold.
- Vector dengan skor tertinggi menang. Jika seri, vector yang lebih dulu dievaluasi
(urutan di
rules.compiled.json) yang dilaporkan, karena perbandingan memakai>bukan>=. - Combo bonus hanya menambah skor vector yang sama. Referensi
if_allberbentuk<vector-id>:<index-sinyal>, misalnyalfi:0. isAttackbernilaitruejika skor > 0, bahkan jika masih di bawah threshold. Keputusan akhir tetap dilakukan olehindex.js.
Field where
Section titled “Field where”where menentukan field context mana yang diperiksa untuk sebuah sinyal:
| Nilai | Isi sebenarnya |
|---|---|
path |
Pathname yang sudah di-decode dan di-lowercase |
query |
Query string lengkap, termasuk ?, sudah di-decode dan di-lowercase |
body |
Body teks yang sudah di-decode dan di-lowercase, maksimal 16 KB |
headers |
Hanya nilai header User-Agent, sudah di-lowercase |
Skor maksimum vector saat ini
Section titled “Skor maksimum vector saat ini”Nilai dihitung dari config/rules.json (tanpa memperhitungkan field mana yang cocok):
| Vector | Jumlah sinyal | Skor dasar | Combo | Maksimum teoretis |
|---|---|---|---|---|
sqli |
5 | 250 | — | 250 |
rce |
5 | 250 | — | 250 |
ssrf |
4 | 205 | — | 205 |
lfi |
4 | 185 | +15 | 200 |
xss |
4 | 120 | +15 | 135 |
recon |
5 | 115 | +20 | 135 |
Threshold default 50 berarti:
sqli,rce,ssrf, dan sebagian besarlfidapat trigger dengan satu sinyal.xssdanreconmembutuhkan beberapa sinyal atau combo.lfipola(\.\./){2,}(35) tidak cukup sendirian; perlu sinyal lain.
Case sensitivity
Section titled “Case sensitivity”Karena context selalu di-lowercase dan regex default memakai flag i, aturan saat ini
efektif case-insensitive. Menulis UNION SELECT tetap akan cocok.
Decoding dan implikasi sinyal
Section titled “Decoding dan implikasi sinyal”path, query, dan body di-decode sebelum dicocokkan. Artinya pola yang menargetkan bentuk
URL-encoded tidak akan pernah cocok. Contohnya sinyal SQLi:
{ "pattern": "(%27)|(\\-\\-\\s)", "where": ["query", "body"], "score": 25 }%27 adalah tanda kutip ter-encode. Setelah safeDecode(), nilainya sudah menjadi ',
sehingga bagian (%27) tidak pernah match. Bagian \-\-\s tetap berfungsi untuk komentar SQL
yang diikuti spasi.
Menambah vector baru
Section titled “Menambah vector baru”- Tambahkan objek vector di
config/rules.json. - Pastikan
idunik dan hanya berisi huruf kecil, angka, atau_. - Isi
signalsminimal satu; setiap sinyal wajib punyapattern,where, danscore. - Jika memakai
combo_bonus, pastikan referensiif_allmenunjuk sinyal yang ada. - Jalankan
npm run build:rules. - Uji dengan
npm run dev.
Contoh vector minimal:
{ "id": "scanner_probe", "enabled": true, "default_fake_status": 200, "signals": [ { "pattern": "\\bnikto\\b", "where": ["headers"], "score": 40 }, { "pattern": "\\bsqlmap\\b", "where": ["headers"], "score": 40 } ], "combo_bonus": [ { "if_all": ["scanner_probe:0", "scanner_probe:1"], "add": 20 } ], "roast": ["Scanner-nya ketinggalan zaman, bang."], "fake_payload": { "delay_ms": [150, 400], "body": { "error": "not_found" } }}Skema lengkap ada di Referensi rules.json.