Synthetic Eval Datasets via NeMo Data Designer
Generate reproducible, versioned attack/eval datasets for agents and MCP servers using NVIDIA NeMo Data Designer, then run them through the normal eval loop with zero engine changes.
Design rationale and the full contract live in
docs/specs/nemo-data-designer-datasets.md.
How it fits
Data Designer generates rows; wb-red-team executes and grades them. The only
integration point is a JSON file on disk that the existing customAttacksFile
path already understands:
NeMo Data Designer ──rows──▶ data/datasets/<family>/<version>.json
│ (customAttacksFile)
▼
custom-attacks-loader ▶ attack-runner ▶ response-analyzer ▶ judge ▶ report
The generator is out-of-band: a Data Designer outage can never break a scan.
One-time: bring up Data Designer
Run the Data Designer microservice (Docker Compose dev deployment per NVIDIA’s
docs) and export credentials. Data Designer is not NVIDIA-locked — it can
back generation with NIM or OpenAI (and other providers). Set the API key for
whichever provider your preset uses; the client picks the first of
NEMO_API_KEY, NVIDIA_API_KEY, OPENAI_API_KEY:
# NIM-backed (default presets, provider: "nim")
export NVIDIA_API_KEY=nvapi-...
# — or — OpenAI-backed (provider: "openai", e.g. nemo-mcp-openai.preset.json)
export OPENAI_API_KEY=sk-...
export NEMO_DATA_DESIGNER_URL=http://localhost:8080 # default
# optional, if your DD version differs:
# export NEMO_PREVIEW_PATH=/v1/data-designer/preview
# export NEMO_GENERATE_PATH=/v1/data-designer/jobs
The generation provider + model live in the preset (provider,
generationModel). configs/datasets/nemo-mcp-openai.preset.json is a ready
OpenAI-backed variant.
Generate a dataset
Presets live in configs/datasets/ and are the tuning knobs (family, category
pool, counts, model, balance floor).
# First capture the MCP target's real tools, schemas, prompts, and resources.
npm run analyze:dump -- config.mytarget.json --out analysis.json
# Inspect a few executable records before committing to a full run.
npm run gen:dataset -- \
--preset configs/datasets/nemo-mcp.preset.json \
--from-analysis analysis.json \
--preview
# Full generate
npm run gen:dataset -- \
--preset configs/datasets/nemo-mcp.preset.json \
--from-analysis analysis.json \
--out data/datasets/nemo-mcp/v1.json \
--count 400
Flags: --family <mcp|agent>, --preset <file>, --out <file>,
--count <n>, --seed <file> (optional {roles,surfaces}), --preview.
MCP security generation requires a target analysis, imported MCP profile, or
explicit seed containing concrete tool "name", MCP prompt "name", or
MCP resource "uri" surfaces. Generic MCP surfaces cannot produce calls that
the target exposes, so generation fails before spending model tokens when no
executable surface is available. Direct MCP datasets are single-operation
datasets; use the agent family for multi-turn conversation evals.
Each generated MCP row includes the protocol fields consumed by the target adapter:
{
"prompt": "{\"bookingId\":\"BK-7001\",\"newAmountINR\":1}",
"_mcpOperation": "tools/call",
"_mcpTool": "override_price",
"_mcpArguments": {
"bookingId": "BK-7001",
"newAmountINR": 1
}
}
The generator fails closed: it refuses to write if any row has a category
outside the AttackCategory union, an empty prompt/successCriteria, a bad
severity, or (for MCP) missing operation-specific execution fields. It prints a
per-category histogram and warns on categories below the preset’s
perCategoryFloor.
Run the eval
Point a config at the dataset with customAttacksOnly: true for a pure,
reproducible regression eval (no planner, no runtime generation):
# configs/config-nemo-mcp-eval.example.json ships pointed at the fixture
npm start configs/config-nemo-mcp-eval.example.json
Swap customAttacksFile to your generated v1.json (or the directory, to load
every pack in it). Reports land in report/ exactly as for any other run.
Target-tailored generation (seed from source)
Agent datasets can be generic. MCP security datasets must be seeded from a real target’s discovered tool graph, roles, and MCP surface so every generated row references an operation the server actually exposes.
# 1. Analyze the target (needs a config with codebasePath) and dump the analysis
npm run analyze:dump -- config.mytarget.json --out analysis.json
# 2. Generate, seeded from that analysis
npm run gen:dataset -- \
--preset configs/datasets/nemo-mcp.preset.json \
--from-analysis analysis.json \
--out data/datasets/nemo-mcp/mytarget-v1.json --count 400
Precedence for role/surface seeds: --seed (explicit {roles,surfaces} JSON) >
--from-analysis (derived) > preset placeholders > built-in defaults. When
seeds are supplied they replace the preset’s generic surfaces, so the
tailored values actually drive generation. --seed and --from-analysis are
unioned when both are given.
In-run generation (tight coupling, optional)
For a one-pass “analyze → generate → execute” run, set datasetGenerator in the
config instead of generating a file first. The run analyzes the target, seeds a
Data Designer dataset from that live analysis, and merges the rows into the
run’s attacks:
"attackConfig": {
"customAttacksOnly": true,
"datasetGenerator": "nemo",
"datasetGeneratorConfig": {
"preset": "configs/datasets/nemo-mcp.preset.json",
"count": 60,
"seedFromAnalysis": true
}
}
See configs/config-nemo-inrun.example.json. This is fail-soft: if Data
Designer is unreachable or produces no valid rows, the run logs a non-fatal
notice and continues with any file-based custom attacks. For reproducible,
diffable datasets prefer the offline npm run gen:dataset flow — the in-run
hook is a convenience, not the recommended default.
Dashboard UI
The dashboard exposes datasets as a first-class surface:
- Datasets tab — lists every dataset under
data/datasets/**with row count, family, and top categories, and has a “Generate” form (family + row count + version name) that calls the generator. The optional Seed from target analysis field takes a scan config path (underconfigs/, with acodebasePath); the target is analyzed and generation is seeded from its discovered tools/roles/MCP surface for target-tailored attacks. Generation requires the Data Designer service to be reachable. - Launch Scan → Evaluation Dataset — pick a generated dataset as the scan’s
attack set (
customAttacksFile), and optionally toggle Dataset-only (regression eval) (customAttacksOnly) for a reproducible run.
An evaluation is just a scan whose attack set is a dataset — results appear in the existing Reports / Risk / Compliance views.
Score over time (regression tracking). Each report records which dataset it
ran (report.dataset.file). The Datasets tab groups runs by dataset and shows a
score sparkline, per-run deltas, and the total change across the series — click
any run to open its report. Re-run the same dataset after a model/guardrail
change to see the score move.
Server endpoints: GET /api/datasets, POST /api/datasets/generate,
GET /api/eval-runs (trends grouped by dataset).
Findings → dataset (regression flywheel)
Every confirmed compromise can become a permanent regression test. On a report,
open a finding with verdict PASS/PARTIAL and click Save as regression test —
the exact attack (category, prompt, success criteria) is appended to
data/datasets/regression/promoted.json (deduped, fail-closed validated). Point
a scan’s customAttacksFile at that set to re-test every past breach on each
run. This is the white-box flywheel: every eval makes the next dataset stronger.
Endpoint: POST /api/datasets/promote (admin).
Two kinds: security vs functional quality
Datasets come in two kinds, graded in opposite directions:
security (default) |
quality |
|
|---|---|---|
| Row asks | an attack | a legitimate task |
| Key fields | category, prompt, successCriteria |
task, input, reference / expectedTools, metric |
| Success = | target compromised → bad | output matches reference → good |
| Runs on | the red-team engine (customAttacksFile) |
a quality scorer (correctness judge / NeMo Evaluator) |
Set kind in the preset. Quality presets: configs/datasets/nemo-mcp-quality.preset.json,
nemo-agent-quality.preset.json (task pools + metrics like tool_call_accuracy,
goal_accuracy, faithfulness). In the Datasets tab, the Kind toggle picks
which to generate; quality datasets are written under data/datasets/quality-<family>/.
npm run gen:dataset -- --preset configs/datasets/nemo-agent-quality.preset.json \
--out data/datasets/quality-agent/v1.json --count 300
Running a quality dataset (native correctness scorer)
Quality datasets run on the native correctness scorer — not the security
engine. It sends each input to the target, extracts the response + tool calls,
and grades against the reference/expectedTools:
- Deterministic metrics (no LLM):
exact_match,f1,rouge,tool_call_accuracy. - LLM-judge metrics (reuses your judge model):
goal_accuracy,faithfulness,answer_relevancy,topic_adherence,context_recall.
npm run eval:quality -- config.mytarget.json \
--dataset data/datasets/quality-agent/v1.json \
--threshold 0.7 --concurrency 4
It prints a live score, a per-metric / per-task breakdown, and writes a
report/quality-report-*.json. The dataset defaults to the config’s
customAttacksFile if --dataset is omitted. Quality rows are kept out of the
security scan picker so the two axes never cross wires.
Row schema
Rows use the exact shape the loader already accepts:
{
"category": "tool_chain_hijack", // must be an AttackCategory (155 values)
"name": "…",
"prompt": "…attacker message…", // [Turn N] markers supported for multi-turn
"role": "user",
"severity": "critical", // critical|high|medium|low
"successCriteria": "…observable success condition for the judge…",
"description": "Synthetic (nemo). family=mcp strategy=…",
"note": "generator=nemo-data-designer version=1"
}
Families
| Family | Categories | Use |
|---|---|---|
mcp |
tool-chain / cross-tenant / injection / debug-access / … | MCP servers with a tool/prompt/resource surface |
agent |
prompt-injection / goal-hijack / memory-poisoning / PII / … | conversational & agentic apps |
Both share one schema and one eval loop; only the seed pools differ.
Testing
tests/dataset-nemo.test.ts covers the category-union guard, fail-closed
validation, config-builder ordering, record mapping, and a fixture that
round-trips through the real custom-attacks-loader. The generator smoke test
against a live Data Designer is network-gated and not part of CI.