2.7
Template YAML dan Plugin Kustom
templates/ dan plugins/ adalah dua jalur perluasan Deep Eye yang tidak berbagi kode. Template YAML bersifat deklaratif: pemeriksaan ditulis sebagai data, divalidasi parser.py, lalu dijalankan TemplateExecutor. Plugin bersifat imperatif: satu atau beberapa kelas Python mewarisi PluginBase, lalu PluginManager memuat dan menjalankannya. Keduanya nonaktif secara default dan baru hidup ketika templates.enabled atau plugin_manager.enabled disetel true. core/scanner_engine.py:174-1881 config/config.example.yaml:627-6312 docs/CONFIGURATION.md:131-1353
Pipeline template dari berkas ke vuln dict
Satu template melewati lima tahap: walk direktori, parse YAML, validasi schema, filter tag/severity, lalu eksekusi. Tahap walk dan filter ada di load_templates; tahap eksekusi ada di TemplateExecutor.run. modules/template_engine/loader.py:11-454 modules/template_engine/executor.py:51-965
graph TD
dirs["template_directories"] --> walk["load_templates()"]
walk --> rglob["rglob *.yaml + *.yml"]
rglob --> parse["parse_template_file()"]
parse --> check{"validate_template"}
check -->|"TemplateError"| skip["Skipping template"]
check -->|"valid"| filt{"tag / severity filter"}
filt -->|"gugur"| drop["dibuang"]
filt -->|"lolos"| store["_loaded_templates"]
store --> run["TemplateExecutor.run()"]
run --> subst["substitute_vars()"]
subst --> send["_send_request()"]
send --> mat{"evaluate_matchers()"}
mat -->|"overall False"| nomatch["tidak ada finding"]
mat -->|"overall True"| extract["run_extractors()"]
extract --> build["_build_vuln()"]
build --> out["List[vuln dict]"]
style store fill:#336791,color:#fff
style skip fill:#dc2626,color:#fff
style drop fill:#dc2626,color:#fff
style nomatch fill:#dc2626,color:#fff
style out fill:#8b5cf6,color:#fff
style build fill:#8b5cf6,color:#fff
style check fill:#f59e0b,color:#000
style filt fill:#f59e0b,color:#000
style mat fill:#f59e0b,color:#000
%% fig: Pipeline template dari direktori ke vuln dict
%% desc: Loader memvalidasi dan memfilter tiap berkas YAML, lalu executor mengirim request dan membentuk finding hanya ketika matcher lulus.Direktori yang tidak ada tidak melempar error; load_templates mencatatnya di logger.debug lalu pindah ke direktori berikutnya. Berkas yang gagal divalidasi juga tidak menghentikan proses: TemplateError ditangkap dan template dilewati dengan pesan Skipping template {file_path}: {e}. modules/template_engine/loader.py:21-326
ScannerEngine memanggil load_templates dengan tiga argumen dari blok konfigurasi templates: template_directories dengan default ['templates'], tag_filters, dan severity_filter. Filter yang kosong dikirim sebagai None. core/scanner_engine.py:177-1887
Filter tag memakai irisan himpunan, yaitu tag_set & tpl_tags, sehingga template lolos bila setidaknya satu tag cocok. Filter severity membandingkan info.severity yang sudah di-lower(). modules/template_engine/loader.py:34-418
Perilaku itu dikunci oleh test loader: subdirektori tetap ter-walk, YAML rusak dilewati tanpa menggagalkan berkas lain, dan direktori yang tidak ada menghasilkan list kosong. tests/test_template_engine.py:257-3009
Schema wajib
validate_template menolak template sebelum sempat dieksekusi. Aturannya terkumpul di lima konstanta tingkat modul: REQUIRED_TOP_LEVEL, REQUIRED_INFO, VALID_SEVERITIES, VALID_MATCHER_TYPES, dan VALID_PARTS. modules/template_engine/parser.py:9-1310
| Pemeriksaan | Aturan | Pesan error |
|---|---|---|
| top level | id, info, http wajib ada | missing required field |
| info | name dan severity wajib ada | info.<key> required |
| severity | salah satu critical, high, medium, low, info | invalid severity |
| http | list tidak boleh kosong | http must be a non-empty list |
| blok http | tiap blok wajib punya path atau raw | requires 'path' or 'raw' |
| matcher | type harus anggota VALID_MATCHER_TYPES | matchers[j].type invalid |
Kolom pesan error mengutip teks yang benar-benar dilempar validate_template. modules/template_engine/parser.py:49-8111
parse_template memakai yaml.safe_load, bukan yaml.load, lalu menyimpan path sumber ke data["_source"]. Field itu nanti muncul sebagai template_path di dict vulnerability. modules/template_engine/parser.py:20-3712 modules/template_engine/executor.py:131-14413
Template bawaan mengikuti schema ini. cors-wildcard.yaml memakai matchers-condition: and dengan dua matcher word yang membaca part: header. templates/misconfig/cors-wildcard.yaml:16-3514 env-file-exposure.yaml memasangkan matcher status dengan matcher regex pada body. templates/exposures/env-file-exposure.yaml:14-2515
Matcher, extractor, dan substitusi variabel
evaluate_matchers memetakan type matcher ke fungsi di _MATCHERS, membungkus tiap pemanggilan dengan try/except, lalu menggabungkan hasil lewat matchers-condition yang default-nya or. Matcher dengan tipe tak dikenal menghasilkan entri dengan matched: False dan error bernilai unknown matcher. modules/template_engine/matcher.py:101-13416
| Tipe | Fungsi | Perilaku |
|---|---|---|
status | match_status | status_code harus ada di list status |
word | match_word | substring match, mendukung case-insensitive dan condition and/or |
regex | match_regex | re.search, pola rusak dihitung False |
size | match_size | panjang response.text harus ada di list size |
dsl | match_dsl | eval ekspresi dengan safe_globals, condition default and |
Detail tiap fungsi ada di modul matcher. modules/template_engine/matcher.py:26-9817
run_extractors baru berjalan setelah matcher lulus. Saat ini hanya tipe regex yang menghasilkan nilai, dan nama default-nya extracted_{i}. modules/template_engine/executor.py:78-8918 modules/template_engine/matcher.py:137-15919
Sebelum request dikirim, substitute_vars mengganti {{BaseURL}}, {{Hostname}}, dan {{RandomString}}. Placeholder terakhir diisi string acak 8 karakter dan diganti per kemunculan lewat loop while. modules/template_engine/executor.py:22-3720
⚠️
match_dslmenjalankaneval(str(expr), safe_globals, {}).safe_globalsmengosongkan__builtins__dan hanya menyediakanlen,status_code,body,True, danFalse, tetapi template tetap kode yang dieksekusi. Perlakukan direktori template seperti kode: jangan memuat template dari sumber tak terpercaya. modules/template_engine/matcher.py:76-9821
Bentuk finding
_build_vuln menormalkan hasil ke dict yang sama dengan modul lain: type dari info.name, severity, url, payload yang dipotong 200 karakter, evidence yang dipotong 500 karakter, cve_references dari classification.cve-id, template_id, dan template_path. modules/template_engine/executor.py:114-14422
cvss_score tidak dihitung dari vektor serangan; nilainya diambil dari tabel SEVERITY_TO_CVSS dengan critical 9.5, high 7.5, medium 5.5, low 3.0, dan info 0.0. modules/template_engine/executor.py:13-1923 modules/template_engine/executor.py:14124
Key type, url, dan severity adalah kontrak yang dipakai lapisan Pelaporan dan Ekspor serta Scan Diff dan Retest, jadi jangan mengubahnya tanpa menyesuaikan keduanya.
Siklus plugin kustom
Plugin adalah kelas Python, bukan data. PluginBase menyediakan metadata name, version, author, dan description, serta menerima http_client dan seluruh config di konstruktor. core/plugin_manager.py:16-3525
Identitas plugin dihitung, bukan ditulis: get_plugin_id() mengembalikan cls.__name__.lower().replace('plugin', ''). Konfigurasi per-plugin diambil dari config['plugins'][id], dan is_enabled() membaca enabled dari situ dengan default False. core/plugin_manager.py:35-4426
graph TD
cfg["plugin_manager.enabled"] --> gate{"enabled?"}
gate -->|"false"| off["load_plugins tidak dipanggil"]
gate -->|"true"| load["PluginManager.load_plugins()"]
load --> missing{"plugin_dir ada?"}
missing -->|"tidak"| mkdir["mkdir parents=True, return 0"]
missing -->|"ya"| glob["glob *.py, lewati nama berawalan _"]
glob --> imp["_load_plugin_classes()"]
imp --> insp["inspect.getmembers, issubclass PluginBase"]
insp -->|"kosong"| warn["No PluginBase subclass found"]
insp -->|"ada"| inst["plugin_class(http_client, config)"]
inst --> reg["self.plugins[plugin_id]"]
reg --> enable["get_enabled_plugins()"]
enable --> scan["scan_with_plugins(url, context)"]
scan --> merge["vulnerabilities.extend(...)"]
style reg fill:#336791,color:#fff
style off fill:#dc2626,color:#fff
style warn fill:#dc2626,color:#fff
style mkdir fill:#dc2626,color:#fff
style merge fill:#2d7d46,color:#fff
style cfg fill:#f59e0b,color:#000
style enable fill:#f59e0b,color:#000
style inst fill:#8b5cf6,color:#fff
%% fig: Siklus pemuatan dan eksekusi plugin
%% desc: PluginManager memuat berkas .py hanya ketika enabled, mendaftarkan tiap subclass PluginBase per plugin_id, lalu menjalankan yang enabled saat scan.load_plugins melewati berkas yang namanya berawalan _, sehingga __init__.py tidak pernah dieksekusi sebagai plugin. Direktori yang belum ada dibuat dengan mkdir(parents=True, exist_ok=True) dan fungsi mengembalikan 0, bukan error. core/plugin_manager.py:87-10327
_load_plugin_classes memakai importlib.util.spec_from_file_location, menjalankan modul, lalu menyaring inspect.getmembers ke kelas yang issubclass(obj, PluginBase) dan bukan PluginBase itu sendiri. Satu berkas boleh memuat lebih dari satu plugin; test membuktikan AlphaPlugin dan BetaPlugin di berkas yang sama sama-sama terdaftar. core/plugin_manager.py:118-13828 tests/test_bugfix_gaps.py:43-6629
Kelas yang tidak mengimplementasi scan akan gagal saat dipanggil, karena PluginBase.scan melempar NotImplementedError dengan pesan Plugin must implement scan() method. scan_with_plugins menangkap exception per plugin, mencatatnya lewat logger.error, lalu melanjutkan plugin berikutnya. core/plugin_manager.py:46-5730 core/plugin_manager.py:152-17531
plugins/example_plugin.py adalah rujukan bentuk plugin minimal: metadata di tingkat kelas, scan(url, context) yang membaca context.get('response'), lalu dict dengan type, severity, url, evidence, description, remediation, dan plugin. plugins/example_plugin.py:11-6332 Bentuk dict itu sama dengan yang didokumentasikan di plugins/README.md. plugins/README.md:103-11433
Konfigurasi dan urutan pemanggilan
Enam langkah yang ditulis plugins/README.md cocok dengan kode: berkas diletakkan di plugins/, PluginManager memindai *.py, memuat kelas turunan PluginBase, memeriksa enabled di config, menjalankan plugin saat fase scan, lalu menggabungkan hasil ke laporan akhir. plugins/README.md:124-13134
| Key konfigurasi | Default | Efek |
|---|---|---|
plugin_manager.enabled | false | ScannerEngine memanggil load_plugins() saat konstruksi |
plugin_manager.plugin_directory | plugins | direktori yang di-glob *.py |
plugin_manager.auto_load | true | tercatat di config contoh |
templates.enabled | false | inisialisasi TemplateExecutor dan load_templates |
templates.template_directories | ["templates"] | akar walk rglob |
templates.tag_filters | [] | filter tag, dikirim sebagai None bila kosong |
templates.severity_filter | [] | filter severity |
Nilai default itu dibaca PluginManager dari config['plugin_manager']['plugin_directory'] dan oleh ScannerEngine dari blok templates. config/config.example.yaml:627-6312 core/plugin_manager.py:82-8535 docs/CONFIGURATION.md:131-1353
Di loop scan, plugin dijalankan lebih dulu lewat scan_with_plugins, lalu template dijalankan dengan template_executor.run(tpl, url) untuk setiap entri _loaded_templates. Keduanya menyumbang ke list vulnerabilities yang sama sebelum pemrosesan pasca-scan. core/scanner_engine.py:532-54436
⚠️ Sistem plugin nonaktif secara default dan plugin berjalan dengan akses penuh ke host OS. Jangan mengarahkan
plugin_directoryke path world-writable dan jangan memuat plugin dari sumber tak terpercaya. SECURITY.md:48-5337
Bagaimana finding dari kedua jalur ini masuk ke laporan dibahas di Pelaporan dan Ekspor; posisi keduanya di dalam satu putaran scan dibahas di Alur Scan dan Pemindai Kerentanan dan Katalog Modul.