Skip to content
MoorAI
Get MoorAI
// open benchmark · apache-2.0

The Agentic Security Benchmark

A corpus and a harness for measuring what an agentic-AI security product actually stops, published in full under Apache-2.0 so a competitor can run their own product through it and publish the result. You bring a product. You write about twenty lines of adapter. You get a recall number, a false-positive number, and a distribution of outcomes graded against AMTSO's Guidelines for Testing of Agentic Security Products v1.0.

It lives at github.com/gitayg/agentic-security-benchmark ↗. There is no install step: zero dependencies, no API key, no network call, no model in the loop. Node 20 and a clone is the whole setup.

00 — moorai's accuracy, in one table

Recall, precision, F1, over-blocking and latency, each with the set it was measured on

Security vendors often publish F1, recall, false-positive and latency figures for their detection models. These are MoorAI's. Each rate below is computed when the site is built, from raw counts copied from the scorers' output, and each row names its set and size. Precision, recall and F1 are never computed across two different sets.

MetricValueWhat it meansHow to reproduce
Recall (headline)locked held-out set · 44 attacks 86.4%38 of 44 caught The share of attacks MoorAI flags, on the locked half of a mutation-generated set that detector work is never allowed to open. Deterministic detectors only, with no model in the loop. This is the out-of-sample number. The set has been scored more than once, so treat it as a good estimate, not an untouched one. score-heldout-v2.mjs ↗ on heldout-v2-test.json ↗
Precisionsame locked set · 10 benign 100.0%0 false positives of 10 benign Of what MoorAI flagged on that set, the share that really was an attack. The set holds only 10 benign samples, which is too few to rest a precision claim on. The benign-traffic row below is the over-blocking number. Same run as recall
F1same locked set · 54 samples 92.7%2·TP / (2·TP + FP + FN) The harmonic mean of the two rows above, from the same 54 samples (38 TP, 0 FP, 6 FN). It carries the same 10-benign caveat as precision. Same run as recall
Over-blockingbenign-corpus-v2 · 602 of 610 samples 3.32%20 of 602 benign flagged How often ordinary developer and agent work gets flagged. 269 of the samples are hard negatives written to look like attacks. Specificity is 96.68%. The 8 samples that discuss attacks are left out; count them and it is 3.77% (23 of 610). score-benign-v2.mjs ↗ on benign-corpus-v2.json ↗
Marginal value over Llama 3 8Bsame locked set · 21 attacks the model did not refuse 95.2%20 of 21 Every attack is first sent to llama3:latest (8B, Q4_0) on a local Ollama, 5 times, with no MoorAI in front of it. Of the 21 it did not refuse, MoorAI catches 20. That is what MoorAI adds. Over all 44 attacks, 1 (2.3%) get past both. measure-refusal-baseline.mjs ↗ reads the cached model verdicts, so a re-run makes no model call
Marginal value over Claude Sonnetsame locked set · 8 attacks the model did not refuse 75.0%6 of 8 Every attack is first sent to the deployed assistant through the claude CLI: the model plus Anthropic's own defences, 5 times, with no MoorAI in front of it. Of the 8 it did not refuse, MoorAI catches 6. That is what MoorAI adds; 11 more were rejected by the provider before the model saw them, and are counted as neither. Over all 44 attacks, 2 (4.5%) get past both. measure-refusal-baseline.mjs ↗ reads the cached model verdicts, so a re-run makes no model call
Per attack familysame locked set · 10 families 6 of 10families fully caught Where the misses are: TAP 1/3, PAIR 2/4, AdvPrefix 3/4, h4rm3l 3/4. Which families the model already refuses is on attack families. Same run as recall
Recall on the tuned halfheldout-v2-tune · 61 attacks 100.0%61 of 61 The half the detectors were developed against. It measures memorisation, not generalisation, and is shown so the 13.6-point gap to the headline is visible. It is not the headline. node scripts/score-heldout-v2.mjs --file test/redteam/heldout-v2-tune.json --json
Public benchmark, all vectors7 corpora · 286 attacks · 875 benign F1 75.4%recall 74.1% · precision 76.8% · FP rate 7.3% All three from one run of MoorAI 0.79.9 on the Apache-2.0 benchmark on this page: 212 of 286 attacks caught, 64 of 875 benign flagged. It covers indirect injection, supply chain, outbound actions and memory, not just prompts, so it is lower. It includes the tuned half above, and it is a self-run result, not an independent test. Anyone can: clone the benchmark and run MOORAI_REPO=<checkout> node scorers/run.mjs --adapter moorai --corpus all --json. Scored run
Detection latencyengine, per scan · 610 benign samples 5.1 msp50 · p95 9.2 ms Time for the detection engine to decide one prompt, in process, on the device. No network call is involved. Measured, not scripted in the repo: see the note below
Hook latencywhole hook process, per event 222 msp50 per prompt · p95 274 ms. Per Bash call 234 / 259 ms What the agent waits for each time the hook runs: a fresh Node process that starts, loads the engine, scans and exits. About 71 ms of it is Node itself starting on the same machine; the scan is the engine row above, so most of the remainder is the hook's own start-up and bookkeeping. Same note below

Where these come from. Everything except the public-benchmark row is measured in the MoorAI agent repository at commit 4f76f18 (v0.96.0), on 2026-09-27, from a clean export of that commit. The raw counts are vendored into this site with that provenance and every rate is derived from them at build time. The model verdicts behind the marginal rows were recorded on 2026-09-06 (Llama 3 8B) and 2026-09-07 (Claude Sonnet) and are re-scored against today's detectors. The refusal script's own printout credits MoorAI with one more catch against Claude Sonnet, because it scans a multi-turn sample as one joined text. The scorer behind the headline recall scans it turn by turn and misses it, so this table counts it as a miss everywhere.

Who wrote the samples. The benign corpus was hand-written by MoorAI's author, who also wrote the detectors. The attack set was generated by a mutation generator from seeds the same author wrote, across the ten HackAgent families. None of it is captured from real incidents, and no third party has reproduced these figures.

Latency. Two runs on an Apple M5 Max, 18 cores, 48 GB, macOS 27.0, Node v22.22.0, with a load average 13 to 15 during both runs: the machine was running other work; the table shows the slower run at each percentile. The engine figure is decideText at the prompt stage, warm, 5 passes over the 610 benign samples. The hook figures time one node cli/moorai-hook.mjs process per event on an unenrolled device with a sandboxed home directory: one prompt per benign sample, and 200 Bash calls with ordinary commands. The timing harness is a short script, not in the repository; the method above is all it does.

286attack samples

Across five AMTSO attack vectors, each labelled with its vector, target of protection, harm type and required capability.

875benign samples

The benign half is larger on purpose. A recall number published without a false-positive number measured on the same engine is half a result.

401hard negatives

Legitimate work deliberately shaped to look like an attack. A benign corpus of obviously-benign prompts measures nothing.

Apache-2.0licence

Deliberate. A benchmark only its author can safely use is not infrastructure. The MoorAI agent it came from is a separate MIT project.

01 — the refusal gap

A detection rate silently includes attacks the model would have refused anyway

Published detection rates for AI-agent security products are mostly uninterpretable. A percentage with no stated corpus, no false-positive line, no held-out split and no model-refusal baseline is a number about nothing, and two products quoting the same figure can be adding wildly different amounts of protection.

The gap is the number that matters method

An agent security product sits behind a model that already declines a large share of obvious attacks. Measure your product against a full attack corpus without first measuring what the bare model refuses, and you are credited with every one of those refusals. The figure that describes what a product adds is coverage of the gap: the attacks the model does not refuse on its own. AMTSO's guidelines say a model refusal must not be counted as product detection.

Running the baseline changes what you build measured

Almost nobody runs the baseline, because it can only ever lower the headline. In the reference implementation it did something more useful: it showed that an entire shipped detection wave bought almost no marginal protection, because the model refused those families unaided. That produced a rule other vendors can use: measure a family's refusal baseline before building detectors for it, and build where the model complies.

The full argument is in the repository's AMTSO.md: the outcome vocabulary, the six classification dimensions and baseline validation. How the same discipline is applied to MoorAI's own numbers is on How we test.

02 — what is in it

Seven corpora, every sample labelled

Each file is self-describing: a header comment says why the corpus exists and how it was built, and most attack samples carry a per-sample validity statement and an AMTSO label object. The counts below are read from the benchmark's own scored output, not typed by hand.

CorpusAttacksBenignHard neg.What it covers
Indirect / content-mediated injectionvector2-indirect-content.json 45 17 16 AMTSO vector 2 · harness text
8 sub-techniques across 8 content channels
Tool, skill, extension and MCP supply chainvector3-supply-chain.json 72 25 17 AMTSO vector 3 · harness text
8 sub-techniques, scanned at the tool, file, index and output stages
Outbound action / agent-initiated effectvector4-outbound-action.json 57 24 17 AMTSO vector 4 · harness action
11 sub-techniques, fed as real tool calls rather than as text
Memory, context and cross-agent propagationvector5-memory-crossagent.json 42 25 20 AMTSO vector 5 · harness text · steps · session · events
7 sub-techniques, spanning four harnesses
Direct input, keyed to the HackAgent familiesheldout-v2-tune.json 61 25 — AMTSO vector 1 · harness text
the tune half of a mutation-generated set; the matching locked half is left out of the benchmark
Benign developer and agent trafficbenign-corpus-v2.json — 610 269 AMTSO vector — · harness text
the precision denominator: 269 hard negatives, 101 deliberately obfuscation-shaped, 8 flagged ambiguous
Fetched web content, scored at the output stagebenign-web-content-tune.json 9 149 62 AMTSO vector — · harness text
tune half of a 311-sample corpus; 9 rows are labelled as attacks because an injection example on a tutorial page is a live payload
Total2868754011161 labelled samples

Of the 286 attack samples, 225 carry a written validity statement and 216 carry AMTSO labels; 102 carry all four label dimensions. The gaps are per corpus and are itemised in the repository's corpora README. Two structural gaps are listed there: vector 1 (direct input) is over-represented across the wider labelled set because it is the easiest vector to author, and vector 6 (static code artifact) has no dedicated corpus.

03 — the validity rule

A sample counts only if the attack could succeed with the product absent

This is AMTSO's rule, and it keeps a corpus from being padded. An attack that resolves to nothing executable, that targets a capability the agent does not have, or that would fail for its own reasons is an invalid sample: neither a miss nor a catch.

It costs you both ways

A corpus quietly padded with attacks that were never going to succeed inflates recall for free, and blocking something that was never going to happen is not protection. Dropping the sample removes both. Most attack samples carry the rationale inline, in a validity field; benign samples carry the mirror-image rationale: why a product firing on this would be a genuine false positive.

Hard negatives and twins

No detector fires on "write me a unit test", so 401 of the benign samples are hard negatives: legitimate security questions, benign role-play, urgent-sounding routine requests, exfiltration-shaped but innocent prompts. Where one was written as the deliberate twin of a specific attack it carries twin_of, so a false-positive result can say which kind of benign prompt a product misfires on, not only how often.

04 — the locked split

Part of the corpus is missing on purpose

Two held-out splits are left out of the benchmark corpora: the test half of the web-content benign corpus (153 samples) and a separate held-out attack set. Only the tune halves ship here. The locked halves are not secret: they are in the public MoorAI repository, where our detector work never opens them. A product that has seen them can tune against them, so treat a score on them as a check for products scored before that, not as proof.

A held-out set is a consumable

It measures generalisation exactly once per decision made in ignorance of it. Tuning against one silently converts it into a training set through ordinary iteration, not cheating, and nothing about the set looks different afterwards. Publishing these would destroy them permanently and retroactively invalidate every generalisation claim built on them, including MoorAI's own.

Generate your own reproducible

The split algorithm is published and needs no stored seed, so any vendor can produce their own from their own corpus: bucket by stratum, sort by id inside the bucket, assign alternately (even index to tune, odd to test), then never open the test half while tuning. No random number generator, so no seed to record, lose or quietly change.

The tune-half score is in the repository

MoorAI scores 100% recall (61/61) on heldout-v2-tune, the half its detectors were developed against. That figure measures memorisation, not generalisation, and it is in the repository so the gap between a tune-half score and a locked-half score is visible.

05 — score your own product

One required method

An adapter is a single ES module. Only scanText is required. Optional scanAction, scanSession and scanEvents enable the action, sequence and event-graph samples, and you are not penalised for omitting them: samples whose harness you do not implement land in a not-applicable bucket and are excluded from every rate, so a text-only product does not appear to have missed samples it was never shown.

# clone, then run the naive reference adapter to see the output shape
git clone https://github.com/gitayg/agentic-security-benchmark
cd agentic-security-benchmark
npm run score -- --adapter keyword

# then score your own
node scorers/run.mjs --adapter ./acme-adapter.mjs --corpus all --json > results/acme.json
Detection is not prevention read twice

A finding with no action is treated as notify, which is a detection and never a prevention. Prevention is derived from the enforcement action your own policy would take (justify, block or kill), never from the fact that something fired. You cannot earn a prevented outcome by returning more findings. If your policy would only log, your prevention rate correctly reads low; that is the number, not a penalty.

A malformed finding fails the run

Missing id, or an action outside the allowed list, fails the run with the adapter name, the method and the offending index. Nothing is coerced or silently dropped, because a number produced from a swallowed error is a number nobody can reproduce.

Corpora only get better if people who are not the maintainer add to them. There are structured issue forms for proposing an attack sample, proposing a benign sample, reporting a mislabelled sample, and submitting results for another product; see CONTRIBUTING.md. Additions to a held-out split are not accepted from outside, because that would compromise the split.

06 — results so far

3 scored runs, and none of them independent

The results directory holds one file per product and is built to hold more than one. Every run currently in it was executed by the benchmark maintainer, who is also the author of MoorAI; the MoorAI result says so in its own conflictOfInterest field. These are not independent tests. No third-party lab has reproduced anything here.

Two of them exist to make the third readable: a null adapter that detects nothing, so the floor of every rate is visible, and a naive keyword adapter, so a score can be compared against something rather than against nothing.

Every scored run, with its per-vector breakdown, is on the leaderboard. A vendor who wants a row opens a pull request against the benchmark repository.

07 — test any vendor yourself

Run the proof of concept on samples the vendor did not pick

A vendor demo shows the prompts the vendor knows it catches. These steps work for a proof of concept with MoorAI or with anyone else, and each one removes a way a detection claim can look better than the protection behind it.

  1. Score the product on a public corpus. Clone the benchmark, write an adapter around the vendor's product (only scanText is required), and run node scorers/run.mjs --adapter ./vendor-adapter.mjs --corpus all --json. Every product in the evaluation then faces the same samples.
  2. Find out what the model refuses before crediting the product. Send each attack to the bare model with no product in the path, several times per attack family, because one run is a single draw. Credit the product only for catches among the attacks the model lets through. That remainder is its marginal detection. The harness is model-free and reports model refusal as zero, so this step is yours to run.
  3. Check whether the tool call ran. A flagged prompt is not a stopped action. Drive the product's real enforcement path through scanAction and record whether each call executed. Only justify, block or kill count as prevention, and every vector 4 row landing in not-applicable means the action surface was never tested. Note the policy posture you ran at, and confirm each attack works with the product removed (section 03). For MoorAI, run this step on Claude Code, the host where enforcement is validated: the Codex and Copilot CLI adapters block through those agents' pre-tool hooks but are not yet validated end to end against the live agents.
  4. Count false positives in the same run. The benign corpora are scored alongside the attacks, including 269 hard negatives in benign-corpus-v2. A catch rate with no false-positive rate from the same engine and settings tells you nothing about what your developers will put up with.
  5. Keep a held-out set the vendor never sees. Split your own samples with the published algorithm (section 04), share nothing from the test half, and score it once, at the end. A vendor that tunes on your samples will score well on them. Our own locked halves sit outside the benchmark corpora but are readable in the public MoorAI repository, so they cannot play that role for a vendor that has read them.

Read MoorAI's published numbers the same way: they are raw detection rates with no refusal baseline subtracted, the 61/61 is a tune-half score, and vector 4 prevention is 18 of 57 at the default posture and 43 of 57 under an enforcing policy.

08 — independence

Not certified, and self-graded

AMTSO has not reviewed, certified or endorsed any of it

Not the benchmark, not its corpora, not its harness, not any result published in it. The Guidelines are cited as published criteria this benchmark grades against. That is self-assessment, not accreditation. AMTSO is a trademark of the Anti-Malware Testing Standards Organization.

MoorAI graded itself

The first result was produced by the author of both the product and the benchmark. It is not an independent test and is not presented as one.

Benchmark on GitHub ↗ Leaderboard → How we test → METHODOLOGY.md ↗ Glossary →

Sample counts on this page are read from the benchmark's own scored output, pinned to commit 4bc3735e of gitayg/agentic-security-benchmark; they move as corpora grow. The benchmark is Apache-2.0. The corpora and harness originated in the MoorAI agent, which is MIT and is a separate project — nothing in the benchmark imposes copyleft obligations on a product under test. AMTSO has not reviewed, certified or endorsed this benchmark, MoorAI, or any result published here; AMTSO is a trademark of the Anti-Malware Testing Standards Organization. OWASP® is a trademark of the OWASP Foundation; ATT&CK® and ATLAS™ are trademarks of The MITRE Corporation. The attack-family taxonomy the family-keyed corpora reference is HackAgent.

Frequently asked questions

What is the Agentic Security Benchmark?

A public Apache-2.0 corpus and scoring harness for agentic-AI security products. It holds 1,161 labelled samples — 286 attacks and 875 benign — across five AMTSO attack vectors, and a zero-dependency Node harness that scores any product behind a small adapter. It is published at github.com/gitayg/agentic-security-benchmark.

Is the benchmark certified or endorsed by AMTSO?

No. AMTSO has not reviewed, certified or endorsed the benchmark, its corpora, its harness, or any result published in it. AMTSO's Guidelines for Testing of Agentic Security Products v1.0 are cited as published criteria the benchmark grades against. That is self-assessment, not accreditation. AMTSO is a trademark of the Anti-Malware Testing Standards Organization.

Are the published results independent tests?

No. Every result currently in the repository was run by the benchmark maintainer, who is also the author of MoorAI. The MoorAI result declares that conflict of interest in its own conflictOfInterest field. No third-party lab has reproduced anything in the repository.

Why is part of the corpus missing?

Two held-out test splits are deliberately withheld: the test half of the web-content benign corpus, 153 samples, and a separate held-out attack set. A held-out set measures generalisation only while nobody has tuned against it, and publishing it would destroy that permanently. The split algorithm is published and needs no stored seed — bucket by stratum, sort by id inside the bucket, assign even indexes to tune and odd to test — so anyone can generate their own locked split from their own corpus.

How do I score my own product against it?

Clone the repository and write an adapter: a single ES module whose only required method is scanText, which returns an array of findings. Then run node scorers/run.mjs --adapter ./your-adapter.mjs --corpus all --json. There is no install step, no dependency, no API key and no network call; Node 20 or later and a clone is the whole setup. Samples whose harness you do not implement are scored not-applicable and excluded from your rates rather than counted against you.

Do the published recall numbers subtract a model-refusal baseline?

No. The harness is deterministic and model-free by design, so model-refusal and model-recognition are reported as measured-as-absent zeros. Every recall figure in the repository's results directory is therefore a raw detection rate over the full attack corpus, not a marginal contribution, and must not be compared against a figure that has had a refusal baseline subtracted.