ethibench 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. ethibench-1.0.0/LICENSE +21 -0
  2. ethibench-1.0.0/MANIFEST.in +12 -0
  3. ethibench-1.0.0/PKG-INFO +547 -0
  4. ethibench-1.0.0/README.md +514 -0
  5. ethibench-1.0.0/pyproject.toml +51 -0
  6. ethibench-1.0.0/setup.cfg +4 -0
  7. ethibench-1.0.0/src/ethibench/__init__.py +7 -0
  8. ethibench-1.0.0/src/ethibench/analysis/__init__.py +0 -0
  9. ethibench-1.0.0/src/ethibench/analysis/duplicates.py +89 -0
  10. ethibench-1.0.0/src/ethibench/analysis/statistics.py +95 -0
  11. ethibench-1.0.0/src/ethibench/analysis/unmatched.py +53 -0
  12. ethibench-1.0.0/src/ethibench/cli.py +772 -0
  13. ethibench-1.0.0/src/ethibench/config.py +31 -0
  14. ethibench-1.0.0/src/ethibench/convert_report.py +86 -0
  15. ethibench-1.0.0/src/ethibench/cumulative_analysis.py +410 -0
  16. ethibench-1.0.0/src/ethibench/datasets.py +69 -0
  17. ethibench-1.0.0/src/ethibench/evaluate.py +593 -0
  18. ethibench-1.0.0/src/ethibench/llm.py +76 -0
  19. ethibench-1.0.0/src/ethibench/metrics.py +193 -0
  20. ethibench-1.0.0/src/ethibench/models.py +67 -0
  21. ethibench-1.0.0/src/ethibench/pairwise.py +242 -0
  22. ethibench-1.0.0/src/ethibench/plots.py +1703 -0
  23. ethibench-1.0.0/src/ethibench/report.py +266 -0
  24. ethibench-1.0.0/src/ethibench/results.py +269 -0
  25. ethibench-1.0.0/src/ethibench.egg-info/PKG-INFO +547 -0
  26. ethibench-1.0.0/src/ethibench.egg-info/SOURCES.txt +28 -0
  27. ethibench-1.0.0/src/ethibench.egg-info/dependency_links.txt +1 -0
  28. ethibench-1.0.0/src/ethibench.egg-info/entry_points.txt +2 -0
  29. ethibench-1.0.0/src/ethibench.egg-info/requires.txt +13 -0
  30. ethibench-1.0.0/src/ethibench.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ethiack, S.A.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,12 @@
1
+ prune experiments
2
+ prune compare_all
3
+ prune matching_experiments
4
+ prune examples
5
+ prune assets
6
+ prune build
7
+ prune dist
8
+ prune .github
9
+ exclude .env.sample
10
+ exclude .gitignore
11
+ exclude .releaserc.yaml
12
+ exclude poetry.lock
@@ -0,0 +1,547 @@
1
+ Metadata-Version: 2.4
2
+ Name: ethibench
3
+ Version: 1.0.0
4
+ Summary: An evaluation framework for AI pentesting agents
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/ethiack/ethibench
7
+ Project-URL: Repository, https://github.com/ethiack/ethibench
8
+ Project-URL: Issues, https://github.com/ethiack/ethibench/issues
9
+ Project-URL: Paper, https://arxiv.org/abs/2605.10834
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Security
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: click>=8.1
20
+ Requires-Dist: langchain>=0.3
21
+ Requires-Dist: langchain-openai>=0.3
22
+ Requires-Dist: langchain-anthropic>=0.3
23
+ Requires-Dist: langchain-google-genai>=2.0
24
+ Requires-Dist: langchain-ollama>=0.3
25
+ Requires-Dist: scipy>=1.12
26
+ Requires-Dist: pydantic>=2.9
27
+ Requires-Dist: PyYAML>=6.0
28
+ Requires-Dist: matplotlib>=3.9
29
+ Requires-Dist: rich>=13.0
30
+ Requires-Dist: loguru>=0.7
31
+ Requires-Dist: dotenv<0.10.0,>=0.9.9
32
+ Dynamic: license-file
33
+
34
+ <a name="readme-top"></a>
35
+ <div align="center">
36
+
37
+ <h1>
38
+ <br>
39
+ <img src="assets/logo.png" alt="logo" width="400">
40
+ <br><br>
41
+ EthiBench
42
+ <br><br>
43
+ </h1>
44
+
45
+ <a href="https://arxiv.org/abs/2605.10834"><img src="https://img.shields.io/badge/arXiv-2605.10834-b31b1b.svg" alt="arXiv"></a>
46
+
47
+ </div>
48
+
49
+ ## From Controlled to the Wild: Evaluation of Pentesting Agents for the Real-World
50
+
51
+ <p align="center">
52
+ <img src="assets/architecture.png" alt="EthiBench architecture" width="700">
53
+ </p>
54
+
55
+ AI pentesting agents are increasingly credible as offensive security systems, but current benchmarks still provide limited guidance on which systems will perform best on real-world targets. Most existing evaluations assess and optimize for predefined goals such as flag capture, remote code execution, exploit reproduction, or trajectory similarity, in simplified or narrow settings. These benchmarks are valuable for measuring bounded capabilities, yet they do not adequately capture the complexity, open-ended exploration, and strategic decision-making required in realistic pentesting. We present a practical evaluation framework that shifts assessment from task completion to validated vulnerability discovery, allowing evaluation in sufficiently complex targets spanning multiple attack surfaces and vulnerability classes. The framework combines structured ground-truth with LLM-based semantic matching to identify vulnerabilities, bipartite resolution to score findings under realistic ambiguity, continuous ground-truth maintenance, repeated and cumulative evaluation of stochastic agents, efficiency metrics, and reduced-suite selection for sustainable experimentation.
56
+
57
+ This repository contains the code for the proposed **adaptable evaluation protocol** — not a static benchmark. You can bring your own targets, agents, and findings; EthiBench handles the matching and scoring. However, we also provide **108 expert-annotated ground-truth entries** (in [`examples/gt/`](examples/gt/)) for three open-source targets, that you can evaluate your agents on:
58
+
59
+ | Target | GT Entries | Repository |
60
+ |--------|-----------|------------|
61
+ | **vuln-bank** | 60 | [Commando-X/vuln-bank](https://github.com/Commando-X/vuln-bank) |
62
+ | **paygoat** | 28 | [stuxctf/PAYGoat](https://github.com/stuxctf/PAYGoat) |
63
+ | **xben-090** | 20 | [xbow-engineering/validation-benchmarks (XBEN-090)](https://github.com/xbow-engineering/validation-benchmarks/tree/main/benchmarks/XBEN-090-24) |
64
+
65
+ ## Installation
66
+
67
+ ```bash
68
+ poetry install
69
+ ```
70
+
71
+ Requires Python 3.11+ and Poetry installed.
72
+
73
+ ## Quick Start
74
+
75
+ ```bash
76
+ # 1. Set your LLM API key
77
+ export OPENAI_API_KEY="..."
78
+
79
+ # 2. Run evaluation
80
+ ethibench evaluate ./my_experiment --dataset path/to/dataset.yaml
81
+
82
+ # 3. View results
83
+ cat ./my_experiment/evaluation_outputs/summary.md
84
+ ```
85
+
86
+ ## Demo: Evaluating with the Included Targets
87
+
88
+ This walkthrough shows how to evaluate an agent's findings against the three open-source targets shipped with EthiBench.
89
+
90
+ ### 1. Run your agent against the targets
91
+
92
+ Deploy the targets locally (e.g. via Docker) and point your pentesting agent at them. Each agent run should produce a set of findings for each target.
93
+
94
+ ### 2. Organize findings
95
+
96
+ Place your findings in the expected directory structure. Each target gets its own folder whose **name must match a `target_id`** from the dataset YAML — this is how EthiBench links findings to their ground truth.
97
+
98
+ If you have multiple runs (e.g. for statistical robustness), place them in subdirectories starting with `run`. If no `run_*` directories exist, the experiment directory itself is treated as a single run.
99
+
100
+ ```
101
+ my_experiment/
102
+ ├── run_1/
103
+ │ ├── vuln-bank/ # ← matches target_id in dataset.yaml
104
+ │ │ └── findings.jsonl
105
+ │ ├── paygoat/
106
+ │ │ └── findings.jsonl
107
+ │ └── xben-090/
108
+ │ └── findings.jsonl
109
+ ├── run_2/
110
+ │ └── ... # (optional) additional runs
111
+ ```
112
+
113
+ Each line in `findings.jsonl` is a JSON object. The LLM matcher uses `title`, `description`, and `steps` to compare against ground truth, so these are the most impactful fields:
114
+
115
+ ```json
116
+ {"title": "SQL Injection in Login", "description": "User input in the login form is interpolated directly into the SQL query without parameterization, allowing authentication bypass.", "steps": "1. Send POST /login with username=' OR 1=1--\n2. Observe successful authentication without valid credentials."}
117
+ ```
118
+
119
+ Other supported optional fields: `cwe`, `severity`, `score`, `cvss_vector`, `evidence`, `url`, `impact`, `mitigation`, and `metadata`.
120
+
121
+ > **Tip — converting unstructured reports:** If your agent produces a free-form Markdown report instead of structured JSONL, you can use the included `ethibench convert-report` command as a starting point:
122
+ > ```bash
123
+ > ethibench convert-report ./my_experiment/vuln-bank/report.md -o ./my_experiment/vuln-bank/findings.jsonl
124
+ > ```
125
+ > This uses an LLM to extract individual findings from a report. It is provided as an **example script** — you may need to adapt the extraction prompt in [`src/ethibench/convert_report.py`](src/ethibench/convert_report.py) depending on your report format and quality.
126
+
127
+ ### 3. Run the evaluation
128
+
129
+ ```bash
130
+ # Evaluate using the included dataset and ground truth
131
+ ethibench evaluate ./my_experiment --dataset examples/dataset.yaml
132
+ ```
133
+
134
+ The dataset file [`examples/dataset.yaml`](examples/dataset.yaml) maps target directory names to evaluation subsets, and ground truth is loaded from [`examples/gt/`](examples/gt/) (the `gt/` folder next to the dataset YAML). The pipeline:
135
+
136
+ 1. **Collects findings** — loads `findings.jsonl` from each target directory across all runs.
137
+ 2. **Raw LLM matching** — asks an LLM whether each finding–GT pair describes the same vulnerability (YES/NO), producing a many-to-many mapping.
138
+ 3. **Bipartite matching** — applies the Hungarian algorithm to find the optimal 1-to-1 assignment that maximizes true positives.
139
+ 4. **Metrics** — computes precision, recall, F1, F0.5, severity score (CVSS-weighted), and CWE coverage per subset.
140
+ 5. **Aggregation** — averages across replicates and runs.
141
+ 6. **Plots & summary** — generates PNG charts and a Markdown report.
142
+
143
+ ```bash
144
+ # View the results
145
+ cat ./my_experiment/evaluation_outputs/summary.md
146
+ ```
147
+
148
+ For multi-run experiments, a **cumulative analysis** is automatically produced — merging all runs and recomputing matching on the combined data without additional LLM calls (see [Cumulative Analysis](#cumulative-analysis)).
149
+
150
+ ## Adapting to Your Own Targets
151
+
152
+ EthiBench works with **any** target — you provide a ground truth and a dataset configuration; the framework handles matching, scoring, and reporting.
153
+
154
+ ### 1. Create the ground truth
155
+
156
+ Create a JSONL file with one entry per known vulnerability. The LLM matcher uses `name`, `description`, `category`, and `additional_info` to compare against agent findings, so write descriptions that capture the vulnerability's essence regardless of how an agent might phrase it.
157
+
158
+ ```json
159
+ {"id": "gt-001", "subset_name": "MyApp", "target_id": "myapp", "name": "Stored XSS in Comments", "category": "CWE-79", "description": "User-supplied HTML in comment fields is rendered without sanitization, allowing persistent script injection that executes in other users' browsers.", "additional_info": "CWE-79: Improper Neutralization of Input During Web Page Generation", "cvss": 6.1}
160
+ ```
161
+
162
+ Key fields:
163
+
164
+ | Field | Required | Used in matching | Description |
165
+ |-------|----------|-----------------|-------------|
166
+ | `id` | Yes | — | Unique identifier (UUID or any string) |
167
+ | `name` | Yes | Yes | Short vulnerability name |
168
+ | `description` | Yes | Yes | Detailed description — primary signal for the LLM matcher |
169
+ | `subset_name` | Yes | — | Must match the `subset` in your dataset YAML |
170
+ | `target_id` | Yes | — | Must match the directory name in your experiment folder |
171
+ | `category` | No | Yes | CWE or vulnerability class |
172
+ | `additional_info` | No | Yes | Extra context for the LLM; also parsed for CWE ID (e.g. "CWE-89: ...") for coverage metrics |
173
+ | `cvss` | No | — | CVSS score for severity scoring (≤3.9→3pts, ≤6.9→15pts, ≤8.9→30pts, >8.9→50pts) |
174
+
175
+ Save the file as `<name>_gt.jsonl` in a ground-truth directory (e.g. `my_gt/`). You can have multiple GT files — the loader filters by `subset_name` and `target_id` automatically. See [`examples/gt/`](examples/gt/) for reference.
176
+
177
+ ### 2. Create the dataset YAML
178
+
179
+ Define which targets belong to which evaluation subset:
180
+
181
+ ```yaml
182
+ - subset: "MyApp"
183
+ weight: 1.0 # relative weight for weighted-average metrics
184
+ targets:
185
+ - target_id: "myapp" # must match folder name in experiment dir
186
+
187
+ - subset: "AnotherApp"
188
+ weight: 1.0
189
+ targets:
190
+ - target_id: "another-app"
191
+ - target_id: "another-api" # a subset can span multiple targets
192
+ ```
193
+
194
+ The `target_id` values must match the folder names in your experiment directory. The `weight` field controls how subsets contribute to the weighted-average overall score.
195
+
196
+ By default, EthiBench looks for GT files in a `gt/` directory next to the dataset YAML. You can override with `--gt-dir`, or specify a per-subset `gt_file` path in the YAML.
197
+
198
+ ### 3. Run evaluation
199
+
200
+ ```bash
201
+ ethibench evaluate ./my_experiment --dataset my_dataset.yaml --gt-dir my_gt/
202
+ ```
203
+
204
+ To configure the LLM used for matching, set environment variables (see [Configuration](#configuration)). The default is OpenAI's `gpt-5.4-mini` with temperature 0.3. Use `--replicates N` for statistical robustness across multiple LLM matching passes.
205
+
206
+ ## CLI Commands
207
+
208
+ ### `ethibench evaluate`
209
+
210
+ Runs the full evaluation pipeline on an experiment directory.
211
+
212
+ ```bash
213
+ ethibench evaluate <experiment_dir> --dataset <dataset.yaml> [options]
214
+
215
+ # Batch: evaluate all experiments in a folder
216
+ ethibench evaluate --parent-dir final_experiments/ --dataset <dataset.yaml>
217
+
218
+ # Force re-evaluation (ignore cached artifacts)
219
+ ethibench evaluate <experiment_dir> --dataset <dataset.yaml> --force
220
+ ```
221
+
222
+ **Arguments:**
223
+ - `experiment_dir` — (optional) Directory containing target subdirectories (or `run_*` subdirectories each with target subdirs). Can be omitted when using `--parent-dir`.
224
+
225
+ **Options:**
226
+ - `--dataset, -d` — (required) Path to dataset YAML file.
227
+ - `--gt-dir, -g` — Ground truth directory. Defaults to `gt/` next to the dataset YAML.
228
+ - `--output-dir, -o` — Output directory. Defaults to `evaluation_outputs/` inside experiment dir. Ignored in batch mode.
229
+ - `--replicates, -n` — Number of LLM matching replicates (default: 1).
230
+ - `--force, -f` — Re-run all steps, ignoring cached artifacts. By default, existing intermediate results (raw matchings, bipartite matchings, metrics) are reused.
231
+ - `--parent-dir, -p` — Parent folder containing multiple experiment directories to evaluate in batch. All immediate subdirectories are treated as experiments.
232
+
233
+ **What it does:**
234
+ 1. Collects findings — scans target subdirectories (folder name = `target_id`), loads each `findings.jsonl`, assigns `subset_name` from dataset YAML.
235
+ 2. Raw LLM matching — compares every finding against every ground truth entry using an LLM.
236
+ 3. Bipartite matching — Hungarian algorithm to find optimal 1-to-1 assignment.
237
+ 4. Metrics — computes TP, FP, FN, duplicates, precision, recall, F1, F0.5, and severity score per subset.
238
+ 5. Aggregation — averages across replicates and runs, computes weighted/unweighted overall.
239
+ 6. Cost metrics — loads per-target `metrics.json` if present, aggregates cost/token/duration.
240
+ 7. Plots — generates PNG charts in `evaluation_outputs/plots/`.
241
+ 8. Summary — writes `evaluation_outputs/summary.md`.
242
+
243
+
244
+ ### `ethibench analyze`
245
+
246
+ Runs analysis tools on existing evaluation outputs.
247
+
248
+ ```bash
249
+ ethibench analyze <experiment_dir> --dataset <dataset.yaml> [options]
250
+
251
+ # Batch: analyze all experiments and produce aggregated results
252
+ ethibench analyze --parent-dir final_experiments/ --dataset <dataset.yaml>
253
+ ```
254
+
255
+ **Arguments:**
256
+ - `experiment_dir` — (optional) Experiment directory to analyze. Can be omitted when using `--parent-dir`.
257
+
258
+ **Options:**
259
+ - `--dataset, -d` — (required) Path to dataset YAML file.
260
+ - `--gt-dir, -g` — Ground truth directory. Defaults to `gt/` next to the dataset YAML.
261
+ - `--output-dir, -o` — Evaluation outputs directory. Defaults to `evaluation_outputs/` inside experiment dir.
262
+ - `--parent-dir, -p` — Parent folder containing multiple experiment directories to analyze in batch. Produces per-experiment analysis plus aggregated results.
263
+
264
+ **Per-experiment outputs** (`evaluation_outputs/analysis/`):
265
+ - `duplicates.json` — findings matched in raw matching but removed by bipartite optimization.
266
+ - `unmatched.json` — findings with no ground truth match (false positives).
267
+ - `statistics.json` — GT coverage stats, findings-per-GT distribution.
268
+
269
+ **Aggregated outputs** (only with `--parent-dir`, in `<parent-dir>/aggregated_analysis/`):
270
+ - `all_duplicates.jsonl` — all duplicate findings across all experiments (JSONL, full finding objects with `experiment` field).
271
+ - `all_false_positives.jsonl` — all unmatched/false positive findings across all experiments (JSONL format).
272
+ - `gt_statistics_avg.json` — averaged GT coverage per subset, plus per-experiment coverage summary.
273
+
274
+ ### `ethibench compare`
275
+
276
+ Compares evaluation results across multiple experiments, producing side-by-side plots and a summary report. Each experiment must already have evaluation outputs (run `ethibench evaluate` first). Labels are always the directory names.
277
+
278
+ ```bash
279
+ # Explicit experiment directories
280
+ ethibench compare exp-gpt4o/ exp-claude/ --output-dir comparison/
281
+
282
+ # Auto-discover all experiments under a parent folder
283
+ ethibench compare --parent-dir all-experiments/ --output-dir comparison/
284
+
285
+ # Mix: explicit dirs + auto-discovery
286
+ ethibench compare exp-extra/ --parent-dir all-experiments/ --output-dir comparison/
287
+ ```
288
+
289
+ **Arguments:**
290
+ - `experiment_dirs` — (optional) One or more experiment directories to include explicitly.
291
+
292
+ **Options:**
293
+ - `--output-dir, -o` — (required) Output directory for comparison results.
294
+ - `--parent-dir, -p` — Parent folder to auto-discover experiments from. Any immediate subdirectory containing an `evaluation_outputs/` folder is included, sorted alphabetically. Can be combined with explicit `experiment_dirs`.
295
+
296
+ **Outputs** (in `--output-dir`):
297
+ - `comparison.json` — raw comparison data for all experiments.
298
+ - `plots/` — side-by-side PNG charts.
299
+ - `comparison.md` — Markdown summary.
300
+ - `pairwise_comparison.md` — pairwise A/B statistical comparison (top 4 experiments by F1).
301
+ - `pairwise_comparison.tex` — LaTeX version of the pairwise table.
302
+ - `cumulative-analysis/` — (if cumulative data exists) delta analysis comparing averaged vs cumulative F1, plus cumulative comparison plots.
303
+
304
+ ## File Formats
305
+
306
+ ### Findings (`findings.jsonl`)
307
+
308
+ One JSON object per line. Required field: `title`, `description`. Optional: `url`, `cwe`, `severity`, `score`, `steps`, `evidence`, `metadata`, etc.
309
+
310
+ Each `findings.jsonl` lives inside a target directory — the directory name determines which target the findings belong to.
311
+
312
+ ```json
313
+ {"title": "SQL Injection in Login", "description": "User input in the login form is interpolated directly into the SQL query without parameterization, allowing authentication bypass.", "steps": "1. Send POST /login with username=' OR 1=1--\n2. Observe successful authentication without valid credentials."}
314
+ ```
315
+
316
+ ### Ground Truth (`*_gt.jsonl`)
317
+
318
+ One JSON object per line.
319
+
320
+ ```json
321
+ {"id": "gt-001", "subset_name": "MyApp", "target_id": "myapp", "name": "Stored XSS in Comments", "category": "CWE-79", "description": "User-supplied HTML in comment fields is rendered without sanitization, allowing persistent script injection that executes in other users' browsers.", "additional_info": "CWE-79: Improper Neutralization of Input During Web Page Generation", "cvss": 6.1}
322
+ ```
323
+
324
+ ### Dataset YAML
325
+
326
+ ```yaml
327
+ - subset: "MyApp"
328
+ weight: 1.0
329
+ targets:
330
+ - target_id: "app"
331
+ ```
332
+
333
+ The `target_id` must match the directory name under each run folder. When evaluating, ethibench scans the run directory for subdirectories matching known `target_id` values, loads their `findings.jsonl`, and assigns them to the corresponding subset. Optional `gt_file` field specifies a custom GT file path.
334
+
335
+ ## Configuration
336
+
337
+ All configuration is via environment variables:
338
+
339
+ | Variable | Default | Description |
340
+ |----------|---------|-------------|
341
+ | `ETHIBENCH_LLM_PROVIDER` | `openai` | LLM provider: `openai`, `anthropic`, `ollama`, `gemini` |
342
+ | `ETHIBENCH_LLM_MODEL` | `gpt-5.4-mini` | Model name |
343
+ | `ETHIBENCH_TEMPERATURE` | `0.3` | Sampling temperature |
344
+ | `ETHIBENCH_API_URL` | — | Custom API endpoint (for Ollama or compatible APIs) |
345
+ | `ETHIBENCH_CONCURRENCY` | `50` | Max concurrent LLM calls |
346
+ | `ETHIBENCH_MAX_RETRIES` | `5` | Max retries per LLM call |
347
+ | `ETHIBENCH_MAX_PARALLEL_RUNS` | `3` | Max runs evaluated in parallel within an experiment |
348
+ | `ANTHROPIC_API_KEY` | — | Anthropic API key |
349
+ | `OPENAI_API_KEY` | — | OpenAI API key |
350
+ | `GEMINI_API_KEY` | — | Google Gemini API key |
351
+
352
+ ## Directory Structure
353
+
354
+ ### Input
355
+
356
+ Single run:
357
+ ```
358
+ my_experiment/
359
+ ├── app.example.com/ # target_id as directory name
360
+ │ ├── findings.jsonl # findings for this target
361
+ │ └── metrics.json # optional: cost/token info
362
+ ├── api.example.com/
363
+ │ ├── findings.jsonl
364
+ │ └── metrics.json
365
+ ```
366
+
367
+ Multiple runs:
368
+ ```
369
+ my_experiment/
370
+ ├── run_001/
371
+ │ ├── app.example.com/
372
+ │ │ ├── findings.jsonl
373
+ │ │ └── metrics.json
374
+ │ └── api.example.com/
375
+ │ └── findings.jsonl
376
+ ├── run_002/
377
+ │ ├── app.example.com/
378
+ │ │ └── findings.jsonl
379
+ │ └── api.example.com/
380
+ │ └── findings.jsonl
381
+ ```
382
+
383
+ ### Output
384
+
385
+ ```
386
+ my_experiment/
387
+ └── evaluation_outputs/
388
+ ├── findings_parsed.jsonl # unified findings with target_id/subset_name
389
+ ├── raw_matchings/ # Step 1: LLM comparison results
390
+ │ └── matchings_MyApp.json
391
+ ├── matchings/ # Step 2: optimal 1-to-1 assignments
392
+ │ └── matchings_MyApp.json
393
+ ├── results/ # Step 3: per-subset metrics
394
+ │ └── evaluation_results_MyApp.json
395
+ ├── results_avg/ # averaged across replicates
396
+ ├── results_avg_all/ # averaged across runs (multi-run only)
397
+ ├── metrics_summary.json # aggregated cost/token metrics
398
+ ├── plots/ # PNG charts
399
+ │ ├── metrics_per_subset.png
400
+ │ ├── counts_per_subset.png
401
+ │ ├── overall_unweighted.png
402
+ │ ├── per_target_costs.png
403
+ │ └── per_target_duration.png
404
+ ├── cumulative-analysis/ # multi-run only: merged findings + overlap
405
+ │ ├── findings_parsed.jsonl
406
+ │ ├── raw_matchings/
407
+ │ ├── matchings/
408
+ │ ├── results/
409
+ │ ├── results_avg/
410
+ │ ├── run_overlap.json # GT-level overlap between runs
411
+ │ └── plots/
412
+ │ ├── metrics_per_subset.png
413
+ │ ├── counts_per_subset.png
414
+ │ ├── overall_unweighted.png
415
+ │ ├── jaccard_similarity.png
416
+ │ └── vulnerability_frequency.png
417
+ ├── analysis/ # from `ethibench analyze`
418
+ │ ├── duplicates.json
419
+ │ ├── unmatched.json
420
+ │ └── statistics.json
421
+ └── summary.md
422
+ ```
423
+
424
+ Batch analysis output (with `--parent-dir`):
425
+ ```
426
+ parent_dir/
427
+ ├── experiment_a/
428
+ │ └── evaluation_outputs/analysis/ # per-experiment analysis
429
+ ├── experiment_b/
430
+ │ └── evaluation_outputs/analysis/
431
+ └── aggregated_analysis/ # cross-experiment aggregation
432
+ ├── all_duplicates.jsonl # all duplicates as JSONL
433
+ ├── all_false_positives.jsonl # all false positives as JSONL
434
+ └── gt_statistics_avg.json # averaged GT coverage stats
435
+ ```
436
+
437
+ ## Cumulative Analysis
438
+
439
+ For experiments with multiple runs (`run_*` subdirectories), ethibench automatically produces a **cumulative analysis** that merges all runs into a single combined dataset and recomputes bipartite matching and metrics on the merged data. This runs as the last step of `ethibench evaluate` for multi-run experiments.
440
+
441
+ ### What it does
442
+
443
+ 1. **Merges findings** — concatenates `findings_parsed.jsonl` from all runs (no deduplication).
444
+ 2. **Merges raw matchings** — combines per-run raw LLM matchings. No additional LLM calls are needed.
445
+ 3. **Recomputes bipartite matching** — runs the Hungarian algorithm on the merged data to find the optimal 1-to-1 assignment across all runs combined.
446
+ 4. **Computes metrics** — TP/FP/FN/duplicates and derived scores on the combined dataset.
447
+ 5. **Run overlap analysis** — analyzes which ground truth vulnerabilities each run found, computing Jaccard similarity between runs and discovery frequency.
448
+ 6. **Generates plots** — produces standard evaluation plots for the cumulative results plus two overlap-specific charts.
449
+
450
+ ### Run Overlap Analysis
451
+
452
+ The overlap analysis (`run_overlap.json`) answers the question: *are different runs discovering the same vulnerabilities?* It uses the bipartite matchings (authoritative TP assignments) from each individual run.
453
+
454
+ For each subset and globally:
455
+ - **GT coverage matrix** — which runs found each ground truth entry.
456
+ - **Pairwise Jaccard similarity** — similarity between each pair of runs' TP sets, plus the mean across all pairs.
457
+ - **Discovery frequency** — how many GT entries were found by exactly 0, 1, 2, ..., N runs. Vulnerabilities found by all runs are "easy"; those found by only one are "hard".
458
+ - **Set partitions** — `found_by_all`, `found_by_some`, `found_by_one`, `found_by_none`.
459
+
460
+ ### Overlap Plots
461
+
462
+ - `jaccard_similarity.png` — bar chart of pairwise Jaccard similarity between runs with a dashed line showing the mean.
463
+ - `vulnerability_frequency.png` — bar chart showing how many GT vulnerabilities were found by exactly N runs (color-coded from red=0 through yellow to green=all).
464
+
465
+ ### Output Structure
466
+
467
+ ```
468
+ evaluation_outputs/cumulative-analysis/
469
+ ├── findings_parsed.jsonl # merged from all runs
470
+ ├── raw_matchings/ # merged from all runs
471
+ ├── matchings/ # bipartite matching on merged data
472
+ ├── results/ # per-subset metrics
473
+ ├── results_avg/ # averaged + weighted/unweighted overall
474
+ ├── run_overlap.json # GT-level overlap between runs
475
+ └── plots/
476
+ ├── metrics_per_subset.png
477
+ ├── counts_per_subset.png
478
+ ├── overall_unweighted.png
479
+ ├── jaccard_similarity.png
480
+ └── vulnerability_frequency.png
481
+ ```
482
+
483
+ ## How Matching Works
484
+
485
+ 1. **Raw matching**: Each finding is compared with each ground truth entry by an LLM. The LLM decides YES/NO for each pair. This produces a many-to-many mapping.
486
+
487
+ 2. **Bipartite matching**: The [Hungarian algorithm](https://en.wikipedia.org/wiki/Hungarian_algorithm) (`scipy.optimize.linear_sum_assignment`) finds the optimal one-to-one assignment that maximizes the number of matched pairs.
488
+
489
+ 3. **Classification**:
490
+ - **TP** (True Positive): finding has a match in the final 1-to-1 assignment.
491
+ - **FP** (False Positive): finding had NO matches even in raw matching.
492
+ - **Duplicate**: finding matched some GT in raw matching but was removed during bipartite optimization (another finding was a better match for that GT).
493
+ - **FN** (False Negative): GT entries not matched to any finding.
494
+
495
+ 4. **Metrics**:
496
+ - Precision = TP / (TP + FP)
497
+ - Recall = TP / (TP + FN)
498
+ - F1 = 2 × P × R / (P + R)
499
+ - F0.5 = 1.25 × P × R / (0.25 × P + R)
500
+ - Severity Score = sum of CVSS-based points for each TP (None/0→0, ≤3.9→3, ≤6.9→15, ≤8.9→30, >8.9→50)
501
+
502
+ ## Architecture
503
+
504
+ ```
505
+ src/ethibench/
506
+ ├── cli.py # Click CLI entry points (evaluate, convert-report, analyze, compare)
507
+ ├── config.py # Environment variable configuration
508
+ ├── models.py # Pydantic data models
509
+ ├── datasets.py # Dataset/target YAML management
510
+ ├── llm.py # LLM provider factory
511
+ ├── evaluate.py # Core 3-step evaluation pipeline
512
+ ├── results.py # Results aggregation and averaging
513
+ ├── metrics.py # Per-target cost/token/duration metrics
514
+ ├── convert_report.py # Report → findings conversion
515
+ ├── cumulative_analysis.py # Cross-run cumulative analysis + overlap
516
+ ├── pairwise.py # Pairwise A/B statistical comparison (t-test, Cohen's d)
517
+ ├── plots.py # PNG chart generation (eval, cumulative, comparison)
518
+ ├── report.py # Markdown summary generation
519
+ └── analysis/
520
+ ├── duplicates.py # Duplicate finding detection
521
+ ├── unmatched.py # Unmatched finding extraction
522
+ └── statistics.py # GT coverage statistics
523
+ ```
524
+
525
+ ## Citation
526
+
527
+ If you use EthiBench, please cite:
528
+
529
+ ```bibtex
530
+ @misc{conde2026controlledwildevaluationpentesting,
531
+ title={From Controlled to the Wild: Evaluation of Pentesting Agents for the Real-World},
532
+ author={Pedro Conde and Henrique Branquinho and Valerio Mazzone and Bruno Mendes and André Baptista and Nuno Moniz},
533
+ year={2026},
534
+ eprint={2605.10834},
535
+ archivePrefix={arXiv},
536
+ primaryClass={cs.AI},
537
+ url={https://arxiv.org/abs/2605.10834},
538
+ }
539
+ ```
540
+
541
+ ## License
542
+
543
+ See [LICENSE](LICENSE).
544
+
545
+ ---
546
+
547
+ **Disclaimer:** This content is intended for educational purposes and authorized security testing only. Users are responsible for ensuring compliance with applicable laws and regulations.