Lompat ke konten
dokumentasi.id
Commit 51864a8
v1
Star1,9 rb

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

6

Engine Workflow DSL v2

Engine workflow Guardian memisahkan kompilasi dari eksekusi. core/workflow_schema.py adalah compiler sekaligus DSL, sementara eksekusi hidup di core/workflow.py (core/workflow_schema.py:14). Dokumen YAML divalidasi dan diubah menjadi CompiledWorkflow sebelum satu tool pun dijalankan, lalu engine menelusuri levels hasil kompilasi generation demi generation (core/workflow_schema.py:1-25).

Bentuk dokumen: WorkflowStep

Setiap step v2 wajib punya id. Field itu menjadi kunci namespace untuk depends_on dan untuk template {{ <id>.parsed.<key> }} (core/workflow_schema.py:1-25). Validator hanya ketat pada dua field: id harus cocok dengan ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$, dan type harus salah satu dari tool, analysis, report setelah di-lowercase (core/workflow_schema.py:39-54, core/workflow_schema.py:56-74).

FieldTipeBatas / nilaiPeran
idstr≤64 karakter, regex ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$kunci step_results, target depends_on
typestrtool / analysis / reportdefault tool
toolstr opsionalnama tooldipakai step bertipe tool
agentstr opsionalbebas, tanpa validator enumditeruskan ke _execute_step
objectivestr≤500 karakterlabel tujuan step
parametersdictnilai str boleh templatedirender sebelum eksekusi
depends_onlist[str]id yang harus adamenentukan generation
whenstr opsional≤400 karakterekspresi Jinja2, falsy berarti skip

agent tidak punya validator, jadi nilai seperti debate atau visual baru bermakna saat _execute_step memilih cabang analysis (core/workflow_schema.py:42-54). Pembagian peran antar agen dijelaskan di Sistem Multi-Agen.

compile_workflow: validasi, migrasi, dan level

graph LR
  A["YAML dict"] --> B{"mode autonomous atau tanpa steps?"}
  B -->|ya| C["CompiledWorkflow kosong"]
  B -->|tidak| D{"ada step yang punya id?"}
  D -->|tidak| E["_migrate_v1_steps"]
  D -->|ya| F["WorkflowDoc.model_validate"]
  E --> F
  F --> G{"id duplikat atau depends_on asing?"}
  G -->|ya| H["WorkflowCompileError"]
  G -->|tidak| I["_topological_levels"]
  I --> J{"semua node terurut?"}
  J -->|tidak| K["Cycle detected"]
  J -->|ya| L["CompiledWorkflow.levels"]
  style A fill:#f59e0b,color:#000
  style C fill:#dc2626,color:#fff
  style H fill:#dc2626,color:#fff
  style K fill:#dc2626,color:#fff
  style L fill:#2d7d46,color:#fff
%% fig: Tahap compile_workflow
%% desc: Dokumen YAML melewati short-circuit autonomous, migrasi v1, validasi pydantic, lalu Kahn sampai menghasilkan levels atau WorkflowCompileError.
Fig. 1: Tahap compile_workflowDokumen YAML melewati short-circuit autonomous, migrasi v1, validasi pydantic, lalu Kahn sampai menghasilkan levels atau WorkflowCompileError.

compile_workflow menyalin dict pemanggil lebih dulu, lalu memeriksa bentuknya: argumen yang bukan mapping langsung ditolak dengan WorkflowCompileError (core/workflow_schema.py:128-131). Dokumen mode: autonomous — atau dokumen tanpa steps — tidak dijadwalkan sama sekali; compiler mengembalikan CompiledWorkflow kosong dengan steps={} dan levels=[] (core/workflow_schema.py:132-142).

Deteksi versi tidak bergantung pada field version. Compiler menganggap dokumen sebagai v2 hanya jika ada minimal satu step yang memuat id berupa string; selain itu langkah-langkahnya dimigrasi dulu, lalu version dipaksa menjadi 2 (core/workflow_schema.py:144-157). Karena itu dokumen bertanda version: 2.0.0 yang masih memakai skema v1 tetap compile (core/workflow_schema.py:144-148).

Migrasi v1 mengambil name tiap step, menyanitasinya menjadi id lewat _sanitize_id, dan merangkai depends_on ke id sebelumnya sehingga urutan linear lama terjaga (core/workflow_schema.py:220-239, core/workflow_schema.py:242-246). Setelah validasi pydantic, compiler menolak id duplikat, depends_on ke id yang tidak dikenal, dan dependensi ke diri sendiri (core/workflow_schema.py:164-179).

DAG hasil kompilasi dan level paralel

_topological_levels menjalankan algoritma Kahn. Setiap elemen levels adalah satu generation: seluruh node di dalamnya sudah terpenuhi dependensinya dan saling independen, sedangkan urutan antar generation ketat (core/workflow_schema.py:249-254). Node yang siap diurutkan secara alfabetis sebelum masuk level, jadi hasilnya deterministik (core/workflow_schema.py:262-274). Jika jumlah node yang berhasil diurutkan tidak sama dengan jumlah step, sisa node dilaporkan sebagai siklus (core/workflow_schema.py:276-281).

graph TD
  subgraph G1["Generation 1"]
    probe["probe — httpx"]
  end
  subgraph G2["Generation 2"]
    js_crawl["js_crawl — cariddi"]
    spec_fuzz["spec_fuzz — schemathesis"]
    auth_check["auth_check — nuclei"]
  end
  subgraph G3["Generation 3"]
    stateful_fuzz["stateful_fuzz — restler, bersyarat when"]
  end
  subgraph G4["Generation 4"]
    correlate["correlate — analyst"]
  end
  subgraph G5["Generation 5"]
    report["report"]
  end
  probe --> js_crawl
  probe --> spec_fuzz
  probe --> auth_check
  spec_fuzz --> stateful_fuzz
  js_crawl --> correlate
  spec_fuzz --> correlate
  stateful_fuzz --> correlate
  auth_check --> correlate
  correlate --> report
  style probe fill:#2d7d46,color:#fff
  style js_crawl fill:#2d7d46,color:#fff
  style spec_fuzz fill:#2d7d46,color:#fff
  style auth_check fill:#2d7d46,color:#fff
  style stateful_fuzz fill:#f59e0b,color:#000
  style correlate fill:#8b5cf6,color:#fff
  style report fill:#336791,color:#fff
%% fig: DAG dan generation hasil kompilasi api_pentest_v2
%% desc: Tiga step berbagi satu pendahulu di generation 2, stateful_fuzz tertahan oleh klausa when, lalu correlate dan report menutup DAG.
Fig. 2: DAG dan generation hasil kompilasi api_pentest_v2Tiga step berbagi satu pendahulu di generation 2, stateful_fuzz tertahan oleh klausa when, lalu correlate dan report menutup DAG.

probe menjadi satu-satunya node berderajat nol. js_crawl, spec_fuzz, dan auth_check sama-sama hanya bergantung pada probe, jadi ketiganya berada di generation 2 (workflows/api_pentest_v2.yaml:6-47). stateful_fuzz menambah satu tingkat karena bergantung pada spec_fuzz (workflows/api_pentest_v2.yaml:30-38), dan correlate menunggu empat pendahulu sekaligus sebelum report menutup DAG (workflows/api_pentest_v2.yaml:49-53).

Eksekusi per generation

sequenceDiagram
  autonumber
  participant CLI as cli/commands/workflow.py
  participant Engine as WorkflowEngine
  participant Schema as workflow_schema
  participant Mem as PentestMemory
  participant Disk as session JSON
  CLI->>Engine: resume_session lalu run_workflow
  Engine->>Schema: compile_workflow(doc)
  Schema-->>Engine: CompiledWorkflow.levels
  loop tiap generation
    Engine->>Mem: cek completed_actions
    Engine->>Schema: evaluate_when(step.when, prior)
    alt when falsy
      Engine->>Mem: mark_action_complete(step.id)
      Engine->>Disk: _save_session
    else when truthy
      Engine->>Schema: render_parameters(step.parameters, prior)
      Engine->>Engine: _execute_step(v1_step)
      Engine->>Disk: _save_session
    end
  end
%% fig: Siklus satu step di dalam satu generation
%% desc: Engine memeriksa completed_actions, mengevaluasi when, merender parameter, menjalankan step, lalu menulis checkpoint atomik.
Fig. 3: Siklus satu step di dalam satu generationEngine memeriksa completed_actions, mengevaluasi when, merender parameter, menjalankan step, lalu menulis checkpoint atomik.

run_workflow memuat dokumen, mengompilasinya, lalu membuat satu asyncio.Semaphore dari config["pentest"]["max_parallel_tools"] yang di-floor minimal 1 (core/workflow.py:97-102). Loop berjalan sebagai for gen_idx, level in enumerate(compiled.levels); di dalam satu generation semua step dijalankan lewat asyncio.gather, sehingga paralelisme dibatasi semaphore, bukan urutan penulisan YAML (core/workflow.py:112-132).

⚠️ settings.max_parallel_tools di YAML tidak dipakai untuk concurrency. Engine membaca pentest.max_parallel_tools dari config (core/workflow.py:97-102), sementara workflows/web_pentest.yaml menulis max_parallel_tools: 3 di blok settings (workflows/web_pentest.yaml:40-43). Untuk mengubah batas paralel, ubah config.

_execute_compiled_step menjembatani DSL v2 ke eksekutor v1: step dirakit ulang menjadi dict {name, type, objective, parameters} lalu diteruskan ke _execute_step (core/workflow.py:711-727). Hasil yang dikembalikan berbentuk id, success, command, exit_code, parsed, dan raw_output, dengan raw_output dipotong 8000 karakter (core/workflow.py:736-743). Bentuk inilah yang menjadi konteks template untuk step berikutnya (core/workflow.py:108-110).

success dihitung dari exit_code == 0 eksekusi tool terakhir, bukan dari isi output; bila tidak ada eksekusi baru, exit_code bernilai -1 (core/workflow.py:726-741). Pemilihan tool dan streaming output-nya dibahas di Integrasi Tool dan Pemilihan Tool.

Template Jinja2 tersandbox

_JINJA_ENV memakai SandboxedEnvironment dengan undefined=StrictUndefined dan autoescape=False (core/workflow_schema.py:105-110). Sandbox mematikan akses filesystem dan network; StrictUndefined membuat variabel yang salah ketik melempar error alih-alih menghasilkan string kosong (core/workflow_schema.py:105-109). render_parameters menelusuri dict dan list secara rekursif, melewati nilai non-string, dan hanya merender string yang memuat {{ atau {% (core/workflow_schema.py:192-202, core/workflow_schema.py:284-296).

Ekspresi di YAMLKonteks yang dibutuhkanNilai
{{ discovery.parsed.host }}step_results["discovery"]["parsed"]["host"]string hasil render
spec_fuzz.parsed.failed | int > 0hasil step spec_fuzzbool untuk when
hosts | length > 0variabel hosts di contextbool untuk when

Bentuk key parsed berasal dari dict hasil step (core/workflow.py:736-743). Rujukan ke variabel yang tidak ada menggagalkan render, dan _execute_compiled_step menangkapnya sebagai step yang di-skip, bukan crash (core/workflow.py:704-709).

Klausa when

evaluate_when mengembalikan True bila when bernilai None, lalu merender ekspresi di dalam {{ }} pada sandbox yang sama (core/workflow_schema.py:205-212). Hasil render dianggap falsy bila string-nya, setelah di-strip dan di-lowercase, termasuk "", "false", "none", atau "0" (core/workflow_schema.py:213-214). Kegagalan template dipetakan ke WorkflowCompileError (core/workflow_schema.py:210-212).

Di engine, step dengan when falsy ditandai selesai lewat mark_action_complete dan menghasilkan {"success": False, "skipped": True, "reason": "when-false"} (core/workflow.py:699-702, core/memory.py:210-213). Berbeda dari itu, kegagalan evaluasi mengembalikan {"success": False, "skipped": True, "error": ...} tanpa menandai selesai (core/workflow.py:692-697).

⚠️ Hasil step yang di-skip tetap masuk step_results, tetapi tidak punya kunci parsed. Step berikutnya yang menulis {{ <id>.parsed.<key> }} ke arah step terlewat akan gagal render karena StrictUndefined, lalu ikut ter-skip (core/workflow.py:736-743, core/workflow_schema.py:105-110).

Checkpoint atomik dan --resume

_save_session menulis state ke NamedTemporaryFile di direktori output yang sama, lalu menukarnya dengan os.replace agar session.json tidak pernah setengah tertulis (core/workflow.py:846-862). Checkpoint dipanggil setelah setiap step selesai di dalam _run_one, dan sekali lagi di blok except saat workflow gagal (core/workflow.py:126-130, core/workflow.py:146-149).

ArtefakLokasiIsi utama
session_<id>.json<output.save_path>/session_<session_id>.json, default ./reportscompleted_actions, findings, tool_executions, token_ledger, threat_model
berkas temp.session_<id>_*.tmp di direktori yang samastate sebelum os.replace

Isi JSON berasal dari PentestMemory.save_state (core/memory.py:329-352). resume_session memuat berkas itu, mengembalikan False bila berkas tidak ada atau tidak terbaca, dan menyetel current_step = len(completed_actions) (core/workflow.py:872-892). Saat resume, _run_one melewati step yang id-nya sudah ada di completed_actions (core/workflow.py:119-125).

⚠️ Kunci skip adalah step.id saja, tanpa hash parameter atau versi dokumen. Mengubah parameters sebuah step lalu menjalankannya ulang dengan --resume tetap melewati step tersebut (core/workflow.py:119-125, core/memory.py:210-213).

Di CLI, --resume/-r menerima session ID, dan --target boleh kosong hanya ketika --resume dipakai (cli/commands/workflow.py:46-51, cli/commands/workflow.py:71-78). Alurnya: engine.resume_session(resume) lebih dulu, baru run_autonomous untuk nama autonomous atau run_workflow untuk nama lain (cli/commands/workflow.py:154-163). Detail seluruh opsi ada di Referensi Perintah CLI.

Katalog YAML di workflows/

Berkas di workflows/ memuat campuran skema. Empat contoh yang mewakili:

BerkasSkemaLangkahCiri
workflows/web_pentest.yamlv1, tanpa id5dirangkai berurutan oleh migrasi, name menjadi id
workflows/web_pentest_with_debate.yamlv26rantai linier ber-id, diakhiri agent: debate
workflows/api_pentest_v2.yamlv27punya when, lima generation
workflows/autonomous.yamlmode: autonomous0tanpa steps, max_steps: 25

workflows/web_pentest.yaml menulis name pada setiap step tanpa id, jadi seluruhnya melewati _migrate_v1_steps (workflows/web_pentest.yaml:7-38). workflows/web_pentest_with_debate.yaml sudah memakai id dan depends_on eksplisit, dengan max_parallel_tools: 2 di settings (workflows/web_pentest_with_debate.yaml:5-40). workflows/autonomous.yaml tidak punya steps sama sekali; ia hanya mendeklarasikan mode: autonomous, max_steps: 25, dan daftar agen, sehingga compile menghasilkan rencana kosong dan CLI mengalihkannya ke run_autonomous (workflows/autonomous.yaml:1-39, core/workflow_schema.py:132-142, cli/commands/workflow.py:160-163).

_load_workflow_doc mencari <nama>.yaml lebih dulu, lalu jatuh ke pencocokan substring pada stem berkas, dan bila tetap tidak ketemu memakai fallback tiga step: subdomain_discovery lalu port_scanning lalu analysis (core/workflow.py:643-668).

⚠️ docs/WORKFLOW_GUIDE.md masih menyatakan conditional step "not natively supported" dan menyarankan memecah workflow menjadi beberapa berkas (docs/WORKFLOW_GUIDE.md:166-196). Sejak DSL v2, when sudah menjadi field resmi di WorkflowStep (core/workflow_schema.py:54). Panduan itu tertinggal dari kode.

Kelayakan setiap YAML di katalog dijaga oleh test parametrize yang mengompilasi seluruh berkas dan memastikan setiap step muncul di levels (tests/test_shipped_workflows.py:16-39). Cakupan pengujiannya dibahas di Pengujian dan Evaluasi.