Benchmark results¶
How SPOT's LLM-free workflows score on three public benchmark sets that no model of SPOT trained on. Every number below comes from the runs listed in What was measured and from the model cards linked there. The intervals are 95 % Wilson score intervals (see Metrics); a set shows only the rates its own counts allow, and n/a marks a rate it cannot measure.
What was measured¶
- Platform. SPOT 2.0.0-beta.7 on the test VM: 4 vCPU, 7.7 GiB, no GPU. The plugins keep their default limits of 1 CPU and 1 GiB; sampled during the baseline, analyzer-nlp was at 100 % of its CPU, which is what bounds the speed of the runs that include it. The platform's rate limit (100 requests per minute) is unchanged.
- Plugins and models.
analyzer-nlp2.0.0b1: the baseline. Its DistilBERT model was trained before spot-models; no repository records its training data, and it was never checked against these sets.analyzer-ml2.0.0b3: servesspot-tfidf-phishing1.0.0, a TF-IDF model with a logistic regression, trained by spot-models on legitimate mail (TREC 2005-2007 ham, through MeAJOR v2) against phishing (the Nazario and Nigerian Fraud corpora). Its model card gives the data, the training and the limitations.- Workflows.
nlp-only-workflow(core's,analyzer-nlp),ml-only-workflow(the same withanalyzer-ml) andnlp-ml-workflow(both in one parallel stage, weight 1.0 each,required: false, weighted mean with the default thresholds 80 and 50). - Commands. On the VM, with the set in
$SPOT_BENCHMARK_DIR:
spot-cli benchmark run -d nahmiasd_spear_phishing-distinct -d miltchev-distinct \
-d pashakhin-distinct --workflow <workflow> -c 4 -b 100
spot-cli benchmark report <run id> --per-dataset --ci --json
The runs used the spot-cli of SPOT 2.0.0-beta.7 on the VM; the reports were made with spot-cli at core a56d5a79, which is part of 2.0.0-beta.7.
The baseline was started with the defaults (batches of 500, concurrency 10); the gateway answered 503 once its queue filled, so after 28 minutes it was resumed with -c 4 -b 100 (--resume re-submits the failed messages and re-polls the submitted ones). Every message has exactly one result. - A model not released. A DistilBERT model was trained for spot-models 1.0.0 and not released: scored on these sets, it flagged 7,960 of the 13,061 distinct pashakhin messages (false-positive rate 0.609, interval 0.601-0.618), all legitimate, where the TF-IDF model flags 1.6 %. A retrain follows in a later release of spot-models, and its numbers will join this page.
| Run | Workflow | Run id | Started (UTC) | Ended (UTC) |
|---|---|---|---|---|
| Baseline | nlp-only-workflow | run_2026-10-13_16-56-49 | 2026-10-13 16:56 | 2026-10-14 00:53 |
| TF-IDF model | ml-only-workflow | run_2026-10-14_05-53-55 | 2026-10-14 05:53 | 2026-10-14 10:27 |
| Both | nlp-ml-workflow | run_2026-10-14_10-28-12 | 2026-10-14 10:28 | 2026-10-14 18:32 |
The held-out sets¶
Counted over distinct messages: two messages with the same normalised text and the same label count once (spot-models distinct), so that near-identical messages do not make the intervals too narrow.
- miltchev (Zenodo 13474746, CC BY 4.0): 2,000 emails, bodies only, 1,000 phishing and 1,000 legitimate. 93 distinct bodies: 19 phishing and 74 legitimate. A small set: its intervals are wide.
- nahmiasd_spear_phishing (GitHub, CC BY-NC-SA 4.0, non-commercial and share-alike: the numbers are published here, the set is not): 334 spear-phishing emails, all phishing, 334 distinct.
- pashakhin (the Google Scholar Emails Dataset, Zenodo 14831438, CC BY 4.0): 26,900 messages, all legitimate, all HTML; 13,061 distinct. Not all in English: of the 13,061 distinct messages, 58 have more than half of their letters in Cyrillic, 807 more than 30 %, and 3,869 (30 %) have a subject written mostly in Cyrillic (letters counted in the visible text and the subject, Cyrillic against Latin).
Why they are held out: spot-models' leak check removed every training row it found in them (five MeAJOR ham rows, all one common closing phrase, as spot-models' 1.0.0 changelog states; the exclusion list holds their hashes), by the words of the text or by half of its 8-word shingles. It cannot find a short anonymised copy of a held-out email, nor one quoted inside a longer training row.
Results¶
A message counts as phishing when its verdict is malicious or suspicious, and as legitimate when it is benign or spam, as benchmark report defines it (the tables below); the next section also gives the counts for malicious alone. Unknown is the messages whose verdict is unknown, left out of the counts; Failed those that failed or timed out: both are 0 in every row. Mean duration is the time between a message's submission and its result, over the messages that report one, queueing included: it measures the deployment under this load, not one inference.
The baseline: nlp-only-workflow¶
| Set | Distinct | Unknown | Failed | TP | FP | TN | FN | Precision | Recall | F1 | FPR | Mean duration |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| nahmiasd_spear_phishing | 334 | 0 | 0 | 164 | 0 | 0 | 170 | n/a | 0.491 (0.438-0.544) | n/a | n/a | 11.7 s |
| miltchev | 93 | 0 | 0 | 13 | 7 | 67 | 6 | 0.650 (0.433-0.819) | 0.684 (0.460-0.846) | 0.667 | 0.095 (0.047-0.183) | 8.9 s |
| pashakhin | 13,061 | 0 | 0 | 0 | 9,369 | 3,692 | 0 | n/a | n/a | n/a | 0.717 (0.710-0.725) | 18.5 s |
The 13,488 messages took 8 h with the resume; 0 unknown, 0 failed. Its pashakhin false-positive rate is the first thing to read: 72 % of the legitimate Google Scholar alerts are called phishing.
ml-only-workflow¶
| Set | Distinct | Unknown | Failed | TP | FP | TN | FN | Precision | Recall | F1 | FPR | Mean duration |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| nahmiasd_spear_phishing | 334 | 0 | 0 | 278 | 0 | 0 | 56 | n/a | 0.832 (0.789-0.869) | n/a | n/a | 0.20 s |
| miltchev | 93 | 0 | 0 | 17 | 29 | 45 | 2 | 0.370 (0.245-0.514) | 0.895 (0.686-0.971) | 0.523 | 0.392 (0.289-0.506) | 0.23 s |
| pashakhin | 13,061 | 0 | 0 | 0 | 215 | 12,846 | 0 | n/a | n/a | n/a | 0.016 (0.014-0.019) | 0.22 s |
The 13,488 messages took 4 h 34 min (the platform's rate limit bounds this run); 0 unknown, 0 failed. These counts are the ones on the model's card: the workflow adds nothing to the model's answer here.
nlp-ml-workflow¶
| Set | Distinct | Unknown | Failed | TP | FP | TN | FN | Precision | Recall | F1 | FPR | Mean duration |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| nahmiasd_spear_phishing | 334 | 0 | 0 | 179 | 0 | 0 | 155 | n/a | 0.536 (0.482-0.589) | n/a | n/a | 11.6 s |
| miltchev | 93 | 0 | 0 | 15 | 8 | 66 | 4 | 0.652 (0.449-0.812) | 0.789 (0.567-0.915) | 0.714 | 0.108 (0.056-0.199) | 1.9 s |
| pashakhin | 13,061 | 0 | 0 | 0 | 8,529 | 4,532 | 0 | n/a | n/a | n/a | 0.653 (0.645-0.661) | 18.5 s |
The 13,488 messages took 8 h 4 min; 0 unknown, 0 failed.
Counting malicious alone¶
Counting a suspicious verdict as phishing is what the tables above do. A reader who acts only on malicious gets these rates from the same runs (computed from each run's results.jsonl, with the same intervals):
| Workflow | nahmiasd recall | miltchev recall / FPR | pashakhin FPR | pashakhin verdicts (malicious / suspicious / benign) |
|---|---|---|---|---|
nlp-only-workflow | 0.464 (0.411-0.518) | 0.579 (0.363-0.769) / 0.081 (0.038-0.166) | 0.685 (0.677-0.693) | 8,944 / 425 / 3,692 |
ml-only-workflow | 0.572 (0.518-0.624) | 0.737 (0.512-0.882) / 0.135 (0.075-0.231) | 0.002 (0.001-0.003) | 24 / 191 / 12,846 |
nlp-ml-workflow | 0.398 (0.347-0.452) | 0.526 (0.317-0.727) / 0.081 (0.038-0.166) | 0.010 (0.009-0.012) | 136 / 8,393 / 4,532 |
The convention changes the combination's pashakhin false-positive rate from 0.653 to 0.010: its verdicts there are almost all suspicious. Quote a rate with its convention.
Reading the numbers¶
What each set measures: nahmiasd_spear_phishing, recall (it holds phishing only); pashakhin, the false-positive rate (it holds legitimate mail only); miltchev, precision, recall and F1 as well, but on 93 distinct bodies, so its intervals are wide and overlap between the workflows.
| Workflow | nahmiasd recall | miltchev precision / recall / FPR | pashakhin FPR |
|---|---|---|---|
nlp-only-workflow (baseline) | 0.491 (0.438-0.544) | 0.650 / 0.684 / 0.095 | 0.717 (0.710-0.725) |
ml-only-workflow | 0.832 (0.789-0.869) | 0.370 / 0.895 / 0.392 | 0.016 (0.014-0.019) |
nlp-ml-workflow | 0.536 (0.482-0.589) | 0.652 / 0.789 / 0.108 | 0.653 (0.645-0.661) |
- The baseline calls most of the legitimate alerts phishing. 9,369 of the 13,061 distinct pashakhin messages (72 %), and it finds half of nahmiasd's spear phishing.
- The TF-IDF model is the reverse. It finds 83 % of nahmiasd and flags 1.6 % of pashakhin, but it flags 39 % of miltchev's legitimate bodies (29 of 74).
- The two together keep the baseline's direction. With equal weights and the default thresholds, the combination calls 65 % of pashakhin's legitimate alerts phishing (0.645-0.661, against the baseline's 0.710-0.725: the intervals do not overlap) and finds 54 % of nahmiasd (the baseline's 49 % overlaps it). What differs is the verdicts: on pashakhin the baseline gives 8,944
maliciousand 425suspicious, the combination 136maliciousand 8,393suspicious. A score of 100 from one analyzer and 0 from the other averages 50, which the default thresholds callsuspicious; the weights and thresholds are a workflow's to tune. - Model and workflow agree. The
ml-onlycounts are the TF-IDF model's card counts, to the message: the workflow adds nothing to the model's answer. - Speed. A message takes 0.2 s through
ml-only, and from 2 to 19 s through the workflows that include analyzer-nlp, which was at its 1-CPU limit: the durations include the time a message waits in the queue. - Small sets. miltchev has 93 distinct bodies (19 phishing, 74 legitimate): no difference between the workflows on it is established by these intervals.
- Not a verdict on the models' worth. pashakhin is one sender family of Google Scholar alerts, and nahmiasd 334 spear-phishing emails: the numbers say how each workflow does on these sets, not on mail in general.
No LLM workflow¶
The test VM has no GPU, and an 8B model on 4 vCPU cannot answer within the stage timeouts. An LLM's training data is not known either, so it could not be said to be held out.
Sets that are not held out¶
champa_trec05,champa_trec06andchampa_trec07are the mail MeAJOR's ham comes from.champa_nazarioandchampa_nigerianare the training positives.champa_ceas08andchampa_enrontrained the earlier analyzer-nlp model (its merged CEAS and Enron dataset).
Their numbers are never quoted as held out.
How to reproduce¶
- Get the three sets from their sources (see The held-out sets) and normalise them with spot-cli (core
f5eb5d4b):spot-cli benchmark normalize --dataset <set>. Theemails.jsonlsha256 were:
| Set | sha256 of emails.jsonl |
|---|---|
| nahmiasd_spear_phishing | 2492cf9209ed35ad54c89006b971fde5530586e1b0bb9a2a0918070663bba1f7 |
| miltchev | 0fe1c8b1899c39cb4729593791566fd9e28c4119a0186b735b4e8a0db71c0df8 |
| pashakhin | ac4636c697d0cc869766704ab2bc2f98384645eb034ecca2b7e93cf6c9674d70 |
- Write the distinct sets with spot-models 1.0.0 (
spot-models distinct --benchmarks $SPOT_BENCHMARK_DIR). Theiremails.jsonlsha256 were:
| Set | sha256 of emails.jsonl |
|---|---|
| nahmiasd_spear_phishing-distinct | 7b69e4c6d0ce6fca5783ce62cf07dcf84ca1a15d336e08afdb58adc3beb03e84 |
| miltchev-distinct | 45bfd0719f74c23ff3835cb751ea6387b589eb1e89bf5017b7507d4f3554e972 |
| pashakhin-distinct | 8f1f12a8d963cb641c19182623b193bce14378ece150483d7a71f2b50bc24dde |
- Install analyzer-ml 2.0.0b3 beside analyzer-nlp 2.0.0b1, create the two workflows below (
spot-cli workflow create <file>;nlp-only-workflowis core's default one), and run the commands of What was measured for each workflow, then report with--per-dataset --ci --json.
id: "ml-only-workflow"
name: "ML-Only Detection Workflow"
version: 1
stages:
- name: "ml-analysis"
type: "sequential"
depends_on: []
analyzers:
- id: "analyzer-ml"
weight: 1.0
timeout_ms: 30000
required: true
failure_strategy: "retry"
retry_config: {max_attempts: 3, backoff_ms: 1000, max_backoff_ms: 5000, exponential_backoff: true}
timeout_ms: 60000
verdict_policy: {method: "most_confident", decision_stages: ["ml-analysis"]}
id: "nlp-ml-workflow"
name: "NLP and ML Detection Workflow"
version: 1
stages:
- name: "analysis"
type: "parallel"
depends_on: []
analyzers:
- id: "analyzer-nlp"
weight: 1.0
timeout_ms: 30000
required: false
failure_strategy: "retry"
retry_config: {max_attempts: 3, backoff_ms: 1000, max_backoff_ms: 5000, exponential_backoff: true}
- id: "analyzer-ml"
weight: 1.0
timeout_ms: 30000
required: false
failure_strategy: "retry"
retry_config: {max_attempts: 3, backoff_ms: 1000, max_backoff_ms: 5000, exponential_backoff: true}
timeout_ms: 60000
verdict_policy: {method: "weighted_mean", decision_stages: ["analysis"]}
A run of the 13,488 messages takes from 4.5 hours (ml-only) to 8 hours (the workflows with analyzer-nlp) on a 4 vCPU VM.