prompt-injection-bench 1.0.1

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.
package/dist/cli.js ADDED
@@ -0,0 +1,779 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { createRequire } from "module";
5
+ import { Command as Command7 } from "commander";
6
+
7
+ // src/cli/commands/attack.command.ts
8
+ import { MockAdapter } from "@reaatech/pi-bench-adapters";
9
+ import { getCategoryIds } from "@reaatech/pi-bench-core";
10
+ import { generateDefaultCorpus } from "@reaatech/pi-bench-corpus";
11
+ import { createLogger } from "@reaatech/pi-bench-observability";
12
+ import { createAttackExecutor } from "@reaatech/pi-bench-runner";
13
+ import { createSafeExecution } from "@reaatech/pi-bench-runner";
14
+ import { Command } from "commander";
15
+ var logger = createLogger();
16
+ function createAttackCommand() {
17
+ const command = new Command("attack");
18
+ command.description("Run single attack category against a defense").requiredOption("--category <category>", "Attack category to run").requiredOption("-d, --defense <name>", "Defense adapter name").option("-n, --count <number>", "Number of attacks to run", "50").option("-o, --output <file>", "Output file for results").option("-t, --timeout <ms>", "Timeout per attack in ms", "30000").action(async (options) => {
19
+ const category = options.category;
20
+ const defenseName = options.defense;
21
+ const count = Number.parseInt(options.count, 10);
22
+ const timeoutMs = Number.parseInt(options.timeout, 10);
23
+ logger.info("Running attack", { category, defense: defenseName, count });
24
+ const validCategories = getCategoryIds();
25
+ if (!validCategories.includes(category)) {
26
+ console.error(`Invalid category: ${category}`);
27
+ console.error(`Valid categories: ${validCategories.join(", ")}`);
28
+ process.exit(1);
29
+ }
30
+ try {
31
+ let adapter;
32
+ if (defenseName.toLowerCase() === "mock") {
33
+ adapter = new MockAdapter(0.85, 0.05);
34
+ } else {
35
+ adapter = await loadAdapter(defenseName);
36
+ }
37
+ await adapter.initialize?.();
38
+ const executor = createAttackExecutor({ timeoutMs });
39
+ const sandbox = createSafeExecution({ maxExecutionMs: timeoutMs });
40
+ const corpus = generateDefaultCorpus();
41
+ const categorySamples = corpus.filter((s) => s.category === category).slice(0, count);
42
+ if (categorySamples.length === 0) {
43
+ console.error(`No samples found for category: ${category}`);
44
+ process.exit(1);
45
+ }
46
+ console.log(`
47
+ === Attack Results: ${category} ===`);
48
+ console.log(`Defense: ${defenseName}`);
49
+ console.log(`Samples: ${categorySamples.length}`);
50
+ console.log("");
51
+ let detected = 0;
52
+ let bypassed = 0;
53
+ let errors = 0;
54
+ const results = [];
55
+ for (const sample of categorySamples) {
56
+ const context = sandbox.createContext(`run-${Date.now().toString(36)}`, sample.id);
57
+ const result = await sandbox.wrapAsync(async () => {
58
+ return executor.executeAttack(sample, adapter, defenseName);
59
+ }, context);
60
+ if (result.error) {
61
+ errors++;
62
+ console.log(`[ERROR] ${sample.id}: ${result.error}`);
63
+ } else if (result.success && result.data) {
64
+ const r = result.data;
65
+ results.push(r);
66
+ if (r.detected) {
67
+ detected++;
68
+ console.log(
69
+ `[DETECTED] ${sample.id} (confidence: ${(r.confidence * 100).toFixed(0)}%)`
70
+ );
71
+ } else {
72
+ bypassed++;
73
+ console.log(`[BYPASSED] ${sample.id}`);
74
+ }
75
+ }
76
+ }
77
+ console.log("\n=== Summary ===");
78
+ console.log(
79
+ `Detected: ${detected} (${(detected / categorySamples.length * 100).toFixed(1)}%)`
80
+ );
81
+ console.log(
82
+ `Bypassed: ${bypassed} (${(bypassed / categorySamples.length * 100).toFixed(1)}%)`
83
+ );
84
+ console.log(`Errors: ${errors}`);
85
+ if (options.output) {
86
+ const fs = await import("fs");
87
+ fs.writeFileSync(
88
+ options.output,
89
+ JSON.stringify(
90
+ {
91
+ category,
92
+ defense: defenseName,
93
+ totalSamples: categorySamples.length,
94
+ detected,
95
+ bypassed,
96
+ errors,
97
+ results,
98
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
99
+ },
100
+ null,
101
+ 2
102
+ )
103
+ );
104
+ logger.info(`Results written to ${options.output}`);
105
+ }
106
+ await adapter.cleanup?.();
107
+ } catch (error) {
108
+ logger.error("Attack failed", {
109
+ error: error instanceof Error ? error.message : String(error)
110
+ });
111
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
112
+ process.exit(1);
113
+ }
114
+ });
115
+ return command;
116
+ }
117
+ async function loadAdapter(name) {
118
+ switch (name.toLowerCase()) {
119
+ case "rebuff":
120
+ return (await import("@reaatech/pi-bench-adapters")).createRebuffAdapter();
121
+ case "lakera":
122
+ return (await import("@reaatech/pi-bench-adapters")).createLakeraAdapter();
123
+ case "llm-guard":
124
+ return (await import("@reaatech/pi-bench-adapters")).createLLMGuardAdapter();
125
+ case "garak":
126
+ return (await import("@reaatech/pi-bench-adapters")).createGarakAdapter();
127
+ default:
128
+ throw new Error(`Unknown defense adapter: ${name}`);
129
+ }
130
+ }
131
+
132
+ // src/cli/commands/benchmark.command.ts
133
+ import { createMockAdapter } from "@reaatech/pi-bench-adapters";
134
+ import { validateEnvironment } from "@reaatech/pi-bench-adapters";
135
+ import { getCategoryIds as getCategoryIds2 } from "@reaatech/pi-bench-core";
136
+ import { generateDefaultCorpus as generateDefaultCorpus2 } from "@reaatech/pi-bench-corpus";
137
+ import { createLogger as createLogger2 } from "@reaatech/pi-bench-observability";
138
+ import { createBenchmarkEngine } from "@reaatech/pi-bench-runner";
139
+ import { createDefenseEvaluator } from "@reaatech/pi-bench-runner";
140
+ import { generateBenignSamples } from "@reaatech/pi-bench-runner";
141
+ import { Command as Command2 } from "commander";
142
+ var logger2 = createLogger2();
143
+ function loadAdapter2(name) {
144
+ switch (name.toLowerCase()) {
145
+ case "mock":
146
+ return createMockAdapter(0.85, 0.05);
147
+ case "rebuff":
148
+ return import("@reaatech/pi-bench-adapters").then((m) => m.createRebuffAdapter());
149
+ case "lakera":
150
+ return import("@reaatech/pi-bench-adapters").then((m) => m.createLakeraAdapter());
151
+ case "llm-guard":
152
+ return import("@reaatech/pi-bench-adapters").then((m) => m.createLLMGuardAdapter());
153
+ case "garak":
154
+ return import("@reaatech/pi-bench-adapters").then((m) => m.createGarakAdapter());
155
+ case "moderation-openai":
156
+ case "moderation-azure":
157
+ case "moderation-anthropic":
158
+ case "moderation-cohere": {
159
+ const provider = name.toLowerCase().replace("moderation-", "");
160
+ return import("@reaatech/pi-bench-adapters").then(
161
+ (m) => m.createModerationAdapter({ provider })
162
+ );
163
+ }
164
+ default:
165
+ throw new Error(
166
+ `Unknown defense adapter: ${name}. Available: mock, rebuff, lakera, llm-guard, garak, moderation-{provider}`
167
+ );
168
+ }
169
+ }
170
+ function createBenchmarkCommand() {
171
+ const command = new Command2("benchmark");
172
+ command.description("Run full benchmark against defenses").requiredOption("-d, --defense <name>", "Defense adapter name").option("-c, --corpus <version>", "Corpus version to use", "2026.04").option("--categories <categories>", "Comma-separated categories to include").option("-p, --parallel <count>", "Number of parallel executions", "10").option("-t, --timeout <ms>", "Timeout per attack in ms", "30000").option("-o, --output <file>", "Output file for results").option("--benign-count <number>", "Number of benign samples", "100").action(async (options) => {
173
+ validateEnvironment();
174
+ const defenseName = options.defense;
175
+ const corpusVersion = options.corpus;
176
+ const parallel = Number.parseInt(options.parallel, 10);
177
+ const timeoutMs = Number.parseInt(options.timeout, 10);
178
+ const benignCount = Number.parseInt(options.benignCount, 10);
179
+ let categories;
180
+ if (options.categories) {
181
+ categories = options.categories.split(",").map((c) => c.trim());
182
+ }
183
+ logger2.info("Starting benchmark", {
184
+ defense: defenseName,
185
+ corpus: corpusVersion,
186
+ parallel,
187
+ timeoutMs
188
+ });
189
+ try {
190
+ const adapter = await loadAdapter2(defenseName);
191
+ await adapter.initialize?.();
192
+ const engine = createBenchmarkEngine({
193
+ maxParallel: parallel,
194
+ defaultTimeoutMs: timeoutMs
195
+ });
196
+ engine.setAdapter(adapter);
197
+ const corpus = generateDefaultCorpus2();
198
+ const attackSamples = categories ? corpus.filter((s) => categories.includes(s.category)) : corpus;
199
+ const benignSamples = generateBenignSamples(benignCount);
200
+ const progressCallback = (progress) => {
201
+ logger2.info("Progress", {
202
+ completed: progress.completed,
203
+ total: progress.total,
204
+ detected: progress.detected,
205
+ errors: progress.errors
206
+ });
207
+ };
208
+ const result = await engine.runBenchmark(
209
+ {
210
+ defense: defenseName,
211
+ corpusVersion,
212
+ categories: categories || getCategoryIds2(),
213
+ parallel,
214
+ timeoutMs,
215
+ seed: Date.now().toString(36)
216
+ },
217
+ attackSamples,
218
+ benignSamples,
219
+ progressCallback
220
+ );
221
+ const evaluator = createDefenseEvaluator();
222
+ const evaluation = evaluator.evaluate(result);
223
+ const output = {
224
+ ...result,
225
+ score: evaluation.score,
226
+ metadata: {
227
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
228
+ corpusVersion,
229
+ adapterVersion: adapter.version
230
+ }
231
+ };
232
+ if (options.output) {
233
+ const fs = await import("fs");
234
+ fs.writeFileSync(options.output, JSON.stringify(output, null, 2));
235
+ logger2.info(`Results written to ${options.output}`);
236
+ }
237
+ console.log("\n=== Benchmark Results ===");
238
+ console.log(`Defense: ${defenseName}`);
239
+ console.log(`Version: ${adapter.version}`);
240
+ console.log(`Overall Score: ${(evaluation.score.overallScore * 100).toFixed(1)}%`);
241
+ console.log(
242
+ `Attack Success Rate: ${(evaluation.score.attackSuccessRate * 100).toFixed(1)}%`
243
+ );
244
+ console.log(
245
+ `False Positive Rate: ${(evaluation.score.falsePositiveRate * 100).toFixed(1)}%`
246
+ );
247
+ console.log(`Avg Latency: ${evaluation.score.avgLatencyMs.toFixed(1)}ms`);
248
+ console.log(`
249
+ Total Attacks: ${evaluation.overallMetrics.totalAttacks}`);
250
+ console.log(
251
+ `Detected: ${evaluation.overallMetrics.totalAttacks - evaluation.overallMetrics.bypassedAttacks}`
252
+ );
253
+ console.log(`Bypassed: ${evaluation.overallMetrics.bypassedAttacks}`);
254
+ await adapter.cleanup?.();
255
+ } catch (error) {
256
+ logger2.error("Benchmark failed", {
257
+ error: error instanceof Error ? error.message : String(error)
258
+ });
259
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
260
+ process.exit(1);
261
+ }
262
+ });
263
+ return command;
264
+ }
265
+
266
+ // src/cli/commands/compare.command.ts
267
+ import { createLogger as createLogger3 } from "@reaatech/pi-bench-observability";
268
+ import { createDefenseEvaluator as createDefenseEvaluator2 } from "@reaatech/pi-bench-runner";
269
+ import { calculateDefenseScore } from "@reaatech/pi-bench-scoring";
270
+ import { compareMetrics } from "@reaatech/pi-bench-scoring";
271
+ import { Command as Command3 } from "commander";
272
+ var logger3 = createLogger3();
273
+ function createCompareCommand() {
274
+ const command = new Command3("compare");
275
+ command.description("Compare multiple defense results").requiredOption("-r, --results <files...>", "Result files to compare").option("-s, --significance <level>", "Statistical significance level", "0.05").option("-o, --output <file>", "Output file for comparison results").action(async (options) => {
276
+ const resultFiles = options.results;
277
+ const significanceLevel = Number.parseFloat(options.significance);
278
+ if (resultFiles.length < 2) {
279
+ console.error("Error: At least 2 result files are required for comparison");
280
+ process.exit(1);
281
+ }
282
+ logger3.info("Comparing defenses", { files: resultFiles, significance: significanceLevel });
283
+ try {
284
+ const results = await Promise.all(
285
+ resultFiles.map(async (file) => {
286
+ const fs = await import("fs");
287
+ const content = fs.readFileSync(file, "utf-8");
288
+ return JSON.parse(content);
289
+ })
290
+ );
291
+ const comparisons = [];
292
+ const evaluator = createDefenseEvaluator2();
293
+ for (let i = 0; i < results.length; i++) {
294
+ for (let j = i + 1; j < results.length; j++) {
295
+ const r1 = results[i];
296
+ const r2 = results[j];
297
+ if (!r1 || !r2) continue;
298
+ const defense1Name = r1.defense || r1.defenseName || `Defense ${i + 1}`;
299
+ const defense2Name = r2.defense || r2.defenseName || `Defense ${j + 1}`;
300
+ const score1 = r1.score || calculateDefenseScore(r1);
301
+ const score2 = r2.score || calculateDefenseScore(r2);
302
+ const comparison2 = evaluator.compare(score1, score2);
303
+ comparisons.push({
304
+ defense1: defense1Name,
305
+ defense2: defense2Name,
306
+ winner: comparison2.winner,
307
+ scoreDifference: comparison2.scoreDifference,
308
+ asrDifference: comparison2.asrDifference,
309
+ fprDifference: comparison2.fprDifference,
310
+ categoryWins: comparison2.categoryWins
311
+ });
312
+ }
313
+ }
314
+ console.log("\n=== Defense Comparison Results ===\n");
315
+ for (const comp of comparisons) {
316
+ console.log(`${comp.defense1} vs ${comp.defense2}`);
317
+ console.log(` Winner: ${comp.winner}`);
318
+ console.log(` Score Difference: ${(comp.scoreDifference * 100).toFixed(1)}%`);
319
+ console.log(` ASR Difference: ${(comp.asrDifference * 100).toFixed(1)}%`);
320
+ console.log(` FPR Difference: ${(comp.fprDifference * 100).toFixed(1)}%`);
321
+ console.log("");
322
+ }
323
+ const detectionRates = results.map((r) => {
324
+ const score = r.score || calculateDefenseScore(r);
325
+ return {
326
+ detectionRate: 1 - score.attackSuccessRate,
327
+ falsePositiveRate: score.falsePositiveRate,
328
+ avgLatencyMs: score.avgLatencyMs,
329
+ totalAttacks: score.totalSamples,
330
+ detectedAttacks: 0,
331
+ bypassedAttacks: 0
332
+ };
333
+ });
334
+ const comparison = compareMetrics(detectionRates, significanceLevel);
335
+ console.log("=== Statistical Summary ===");
336
+ console.log(`Significance Level: ${(significanceLevel * 100).toFixed(0)}%`);
337
+ console.log(`Significant Difference: ${comparison.significantDifference ? "Yes" : "No"}`);
338
+ console.log("\nRankings (by detection rate):");
339
+ comparison.rankings.forEach((rank, idx) => {
340
+ const name = results[rank]?.defense || results[rank]?.defenseName || `Rank ${idx + 1}`;
341
+ const dr = ((detectionRates[rank]?.detectionRate || 0) * 100).toFixed(1);
342
+ console.log(` ${idx + 1}. ${name}: ${dr}% detection rate`);
343
+ });
344
+ if (options.output) {
345
+ const fs = await import("fs");
346
+ fs.writeFileSync(
347
+ options.output,
348
+ JSON.stringify(
349
+ {
350
+ comparisons,
351
+ statisticalSummary: {
352
+ significanceLevel,
353
+ significantDifference: comparison.significantDifference,
354
+ rankings: comparison.rankings.map((rank, idx) => ({
355
+ rank: idx + 1,
356
+ name: results[rank]?.defense || results[rank]?.defenseName || `Defense ${rank + 1}`,
357
+ detectionRate: detectionRates[rank]?.detectionRate || 0
358
+ }))
359
+ },
360
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
361
+ },
362
+ null,
363
+ 2
364
+ )
365
+ );
366
+ logger3.info(`Comparison results written to ${options.output}`);
367
+ }
368
+ } catch (error) {
369
+ logger3.error("Comparison failed", {
370
+ error: error instanceof Error ? error.message : String(error)
371
+ });
372
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
373
+ process.exit(1);
374
+ }
375
+ });
376
+ return command;
377
+ }
378
+
379
+ // src/cli/commands/corpus.command.ts
380
+ import { ATTACK_CATEGORIES } from "@reaatech/pi-bench-core";
381
+ import { createCorpusBuilder, generateDefaultCorpus as generateDefaultCorpus3 } from "@reaatech/pi-bench-corpus";
382
+ import { createCorpusValidator } from "@reaatech/pi-bench-corpus";
383
+ import { createLogger as createLogger4 } from "@reaatech/pi-bench-observability";
384
+ import { Command as Command4 } from "commander";
385
+ var logger4 = createLogger4();
386
+ function createCorpusCommand() {
387
+ const command = new Command4("corpus");
388
+ command.description("Manage test corpus");
389
+ command.command("list").description("List available corpus versions and categories").action(async () => {
390
+ console.log("=== Attack Categories ===\n");
391
+ for (const cat of ATTACK_CATEGORIES) {
392
+ console.log(` ${cat.id}`);
393
+ console.log(` Name: ${cat.name}`);
394
+ console.log(` Weight: ${cat.weight}`);
395
+ console.log(` Min Samples: ${cat.minSamples}`);
396
+ console.log(` Description: ${cat.description}`);
397
+ console.log("");
398
+ }
399
+ console.log("=== Available Corpus Versions ===\n");
400
+ console.log(" 2026.04 (current)");
401
+ console.log(" Total samples: ~300");
402
+ console.log(" Categories: 8");
403
+ console.log(" Last updated: 2026-04-01");
404
+ console.log("");
405
+ console.log(" 2026.01 (deprecated)");
406
+ console.log(" Total samples: ~250");
407
+ console.log(" Categories: 6");
408
+ console.log(" Last updated: 2026-01-15");
409
+ });
410
+ command.command("generate").description("Generate a new corpus version").option("-o, --output <dir>", "Output directory for corpus", "./corpus/generated").option("-v, --version <version>", "Corpus version string", `v${Date.now().toString(36)}`).option("-s, --samples <number>", "Samples per category", "40").action(async (options) => {
411
+ const outputDir = options.output;
412
+ const version = options.version;
413
+ const samplesPerCategory = Number.parseInt(options.samples, 10);
414
+ logger4.info("Generating corpus", { version, outputDir, samplesPerCategory });
415
+ try {
416
+ const builder = createCorpusBuilder({
417
+ version,
418
+ samplesPerCategory
419
+ });
420
+ const corpus = generateDefaultCorpus3();
421
+ builder.addSamples(corpus);
422
+ const validator = createCorpusValidator();
423
+ const validation = validator.validate(corpus);
424
+ console.log("\n=== Corpus Generation Complete ===\n");
425
+ console.log(`Version: ${version}`);
426
+ console.log(`Total Samples: ${corpus.length}`);
427
+ console.log(`Output Directory: ${outputDir}`);
428
+ if (!validation.valid) {
429
+ console.log("\nValidation Errors:");
430
+ for (const e of validation.errors) {
431
+ console.log(` - ${e}`);
432
+ }
433
+ }
434
+ if (validation.warnings.length > 0) {
435
+ console.log("\nWarnings:");
436
+ for (const w of validation.warnings) {
437
+ console.log(` - ${w}`);
438
+ }
439
+ }
440
+ console.log("\nStatistics:");
441
+ console.log(` Unique Prompts: ${validation.stats.uniquePrompts}`);
442
+ console.log(` Duplicates: ${validation.stats.duplicateCount}`);
443
+ console.log(" Category Distribution:");
444
+ for (const [cat, count] of Object.entries(validation.stats.categoryDistribution)) {
445
+ console.log(` ${cat}: ${count}`);
446
+ }
447
+ if (outputDir !== "./corpus/generated") {
448
+ console.log(`
449
+ Note: Writing to ${outputDir} requires file system access.`);
450
+ console.log("Generated corpus is available in memory for immediate use.");
451
+ }
452
+ } catch (error) {
453
+ logger4.error("Corpus generation failed", {
454
+ error: error instanceof Error ? error.message : String(error)
455
+ });
456
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
457
+ process.exit(1);
458
+ }
459
+ });
460
+ command.command("validate").description("Validate a corpus").requiredOption("-i, --input <dir>", "Corpus directory to validate").action(async (options) => {
461
+ const inputDir = options.input;
462
+ logger4.info("Validating corpus", { input: inputDir });
463
+ console.log("\n=== Corpus Validation ===\n");
464
+ console.log(`Input: ${inputDir}`);
465
+ try {
466
+ const fs = await import("fs");
467
+ if (!fs.existsSync(inputDir)) {
468
+ console.log("Error: Directory does not exist");
469
+ process.exit(1);
470
+ }
471
+ const files = fs.readdirSync(inputDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".json"));
472
+ if (files.length === 0) {
473
+ console.log("Warning: No corpus files (.yaml or .json) found in directory");
474
+ return;
475
+ }
476
+ let totalSamples = 0;
477
+ const categoryStats = {};
478
+ for (const file of files) {
479
+ const content = fs.readFileSync(`${inputDir}/${file}`, "utf-8");
480
+ const data = file.endsWith(".json") ? JSON.parse(content) : await import("yaml").then((y) => y.parse(content));
481
+ const samples = Array.isArray(data) ? data : data.samples || [];
482
+ totalSamples += samples.length;
483
+ for (const sample of samples) {
484
+ const cat = sample.category || "unknown";
485
+ categoryStats[cat] = (categoryStats[cat] || 0) + 1;
486
+ }
487
+ }
488
+ console.log(`
489
+ Total Samples: ${totalSamples}`);
490
+ console.log("\nCategory Distribution:");
491
+ for (const [cat, count] of Object.entries(categoryStats)) {
492
+ console.log(` ${cat}: ${count}`);
493
+ }
494
+ if (Object.keys(categoryStats).length < 8) {
495
+ console.log("\nWarning: Not all 8 categories are represented in the corpus");
496
+ }
497
+ } catch (error) {
498
+ logger4.error("Validation failed", {
499
+ error: error instanceof Error ? error.message : String(error)
500
+ });
501
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
502
+ process.exit(1);
503
+ }
504
+ });
505
+ command.command("export").description("Export corpus to a specific format").requiredOption("-i, --input <dir>", "Corpus directory").option("-f, --format <format>", "Output format (json, yaml, csv)", "json").option("-o, --output <file>", "Output file (default: stdout)").action(async (options) => {
506
+ const inputDir = options.input;
507
+ const format = options.format;
508
+ const outputFile = options.output;
509
+ logger4.info("Exporting corpus", { input: inputDir, format, output: outputFile || "stdout" });
510
+ console.log("\n=== Corpus Export ===\n");
511
+ console.log(`Format: ${format}`);
512
+ console.log(`Source: ${inputDir}`);
513
+ if (outputFile) {
514
+ console.log(`Output: ${outputFile}`);
515
+ console.log("\nExport functionality requires file system access.");
516
+ } else {
517
+ console.log("\nExporting to stdout...");
518
+ const corpus = generateDefaultCorpus3();
519
+ if (format === "json") {
520
+ console.log(JSON.stringify(corpus, null, 2));
521
+ } else if (format === "yaml") {
522
+ const yaml = await import("yaml");
523
+ console.log(yaml.stringify(corpus));
524
+ } else if (format === "csv") {
525
+ console.log("id,category,severity,prompt,tags");
526
+ for (const sample of corpus) {
527
+ const escapedPrompt = sample.prompt.replace(/"/g, '""');
528
+ console.log(
529
+ `${sample.id},${sample.category},${sample.severity},"${escapedPrompt}","${sample.tags.join(";")}"`
530
+ );
531
+ }
532
+ }
533
+ }
534
+ });
535
+ return command;
536
+ }
537
+
538
+ // src/cli/commands/leaderboard.command.ts
539
+ import { DefenseScoreSchema } from "@reaatech/pi-bench-core";
540
+ import { createLeaderboardManager } from "@reaatech/pi-bench-leaderboard";
541
+ import {
542
+ getDefaultLeaderboardPath,
543
+ loadLeaderboardEntries,
544
+ saveLeaderboardEntries
545
+ } from "@reaatech/pi-bench-leaderboard";
546
+ import { createLogger as createLogger5 } from "@reaatech/pi-bench-observability";
547
+ import { Command as Command5 } from "commander";
548
+ var logger5 = createLogger5();
549
+ function createLeaderboardCommand() {
550
+ const command = new Command5("leaderboard");
551
+ command.description("View/submit to leaderboard");
552
+ command.command("view").description("View current leaderboard rankings").option("-c, --category <category>", "Filter by attack category").option("-n, --limit <number>", "Number of entries to show", "10").option("--file <path>", "Leaderboard storage file", getDefaultLeaderboardPath()).action(async (options) => {
553
+ const limit = Number.parseInt(options.limit, 10);
554
+ const category = options.category;
555
+ const manager = createLeaderboardManager();
556
+ manager.replaceEntries(loadLeaderboardEntries(options.file));
557
+ const entries = category ? manager.getRankings(manager.getEntryCount() || limit).filter((e) => e.categoryScores && category in e.categoryScores) : manager.getRankings(limit);
558
+ console.log("\n=== Prompt Injection Defense Leaderboard ===\n");
559
+ console.log("Rank | Defense | Version | Overall Score | Submitted");
560
+ console.log("-----|---------|---------|---------------|----------");
561
+ entries.slice(0, limit).forEach((entry, idx) => {
562
+ const date = new Date(entry.submittedAt).toLocaleDateString();
563
+ console.log(
564
+ `${`${idx + 1} | ${entry.defense.padEnd(12)} | ${entry.version.padEnd(7)} | ${(entry.overallScore * 100).toFixed(1)}%`.padEnd(
565
+ 40
566
+ )} | ${date}`
567
+ );
568
+ });
569
+ if (entries.length === 0) {
570
+ console.log("No entries found.");
571
+ }
572
+ console.log("\n");
573
+ console.log(`Storage: ${options.file}`);
574
+ console.log("Submit a local entry with:");
575
+ console.log(" prompt-injection-bench leaderboard submit -r results.json --defense <name>");
576
+ });
577
+ command.command("submit").description("Submit results to the leaderboard").requiredOption("-r, --results <file>", "Results file to submit").requiredOption("--defense <name>", "Defense name").option("--defense-version <version>", "Defense version").option("--seed <seed>", "Random seed used for the run").option("--private", "Submit as private (not publicly visible)").option("--file <path>", "Leaderboard storage file", getDefaultLeaderboardPath()).action(async (options) => {
578
+ const resultsFile = options.results;
579
+ const defenseName = options.defense;
580
+ const defenseVersion = options.defenseVersion;
581
+ const seed = options.seed;
582
+ logger5.info("Submitting to leaderboard", { defense: defenseName, version: defenseVersion });
583
+ try {
584
+ const fs = await import("fs");
585
+ const content = fs.readFileSync(resultsFile, "utf-8");
586
+ const results = JSON.parse(content);
587
+ const score = resolveScore(results);
588
+ const entry = {
589
+ defense: defenseName,
590
+ version: defenseVersion || score.version,
591
+ overallScore: score.overallScore,
592
+ rank: 0,
593
+ submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
594
+ corpusVersion: results.corpusVersion || results.metadata?.corpusVersion || "2026.04",
595
+ categoryScores: mapCategoryScores(score),
596
+ proofHash: seed || `local-${Date.now().toString(36)}`,
597
+ submitter: options.private ? "anonymous" : "local-user"
598
+ };
599
+ const manager = createLeaderboardManager();
600
+ manager.replaceEntries(loadLeaderboardEntries(options.file));
601
+ manager.addEntry(entry);
602
+ saveLeaderboardEntries(manager.getAllEntries(), options.file);
603
+ console.log("\n=== Submission Successful ===");
604
+ console.log(`Defense: ${entry.defense}`);
605
+ console.log(`Version: ${entry.version}`);
606
+ console.log(`Overall Score: ${(entry.overallScore * 100).toFixed(1)}%`);
607
+ console.log(`Proof Hash: ${entry.proofHash}`);
608
+ console.log(`Stored at: ${options.file}`);
609
+ } catch (error) {
610
+ logger5.error("Submission failed", {
611
+ error: error instanceof Error ? error.message : String(error)
612
+ });
613
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
614
+ process.exit(1);
615
+ }
616
+ });
617
+ command.command("verify").description("Verify a submission").requiredOption("--id <id>", "Submission proof hash to verify").option("--file <path>", "Leaderboard storage file", getDefaultLeaderboardPath()).action(async (options) => {
618
+ const submissionId = options.id;
619
+ const manager = createLeaderboardManager();
620
+ manager.replaceEntries(loadLeaderboardEntries(options.file));
621
+ const found = manager.getAllEntries().find((entry) => entry.proofHash === submissionId);
622
+ console.log("\n=== Verifying Submission ===");
623
+ console.log(`Submission ID: ${submissionId}`);
624
+ console.log("");
625
+ if (!found) {
626
+ console.log("Status: not found");
627
+ console.log(`Storage: ${options.file}`);
628
+ process.exitCode = 1;
629
+ return;
630
+ }
631
+ console.log("Status: verified");
632
+ console.log(`Defense: ${found.defense}`);
633
+ console.log(`Version: ${found.version}`);
634
+ console.log(`Overall Score: ${(found.overallScore * 100).toFixed(1)}%`);
635
+ console.log(`Submitted: ${found.submittedAt}`);
636
+ console.log(`Storage: ${options.file}`);
637
+ });
638
+ return command;
639
+ }
640
+ function resolveScore(results) {
641
+ if (results.score && typeof results.score === "object") {
642
+ return DefenseScoreSchema.parse(results.score);
643
+ }
644
+ return DefenseScoreSchema.parse(results);
645
+ }
646
+ function mapCategoryScores(score) {
647
+ return Object.fromEntries(
648
+ Object.entries(score.categoryScores).map(([category, value]) => [
649
+ category,
650
+ value.detectionRate
651
+ ])
652
+ );
653
+ }
654
+
655
+ // src/cli/commands/report.command.ts
656
+ import { normalizeReportData } from "@reaatech/pi-bench-mcp-server";
657
+ import { Command as Command6 } from "commander";
658
+ function createReportCommand() {
659
+ const command = new Command6("report");
660
+ command.description("Generate reports from benchmark results").requiredOption("-r, --results <file>", "Results file to generate report from").option("-f, --format <format>", "Output format (json, html, markdown)", "json").option("-o, --output <file>", "Output file").option("--include-categories", "Include category breakdown", true).action(async (options) => {
661
+ const resultsFile = options.results;
662
+ const format = options.format;
663
+ const outputFile = options.output || `report.${format}`;
664
+ const includeCategories = options.includeCategories;
665
+ console.log(`Generating ${format} report from: ${resultsFile}`);
666
+ const fs = await import("fs");
667
+ const results = JSON.parse(fs.readFileSync(resultsFile, "utf-8"));
668
+ const reportData = normalizeReportData(results);
669
+ let report;
670
+ if (format === "json") {
671
+ report = JSON.stringify(reportData, null, 2);
672
+ } else if (format === "markdown") {
673
+ report = generateMarkdownReport(reportData, includeCategories);
674
+ } else if (format === "html") {
675
+ report = generateHtmlReport(reportData, includeCategories);
676
+ } else {
677
+ console.error(`Unsupported format: ${format}`);
678
+ process.exit(1);
679
+ }
680
+ fs.writeFileSync(outputFile, report);
681
+ console.log(`
682
+ Report generated: ${outputFile}`);
683
+ });
684
+ return command;
685
+ }
686
+ function generateMarkdownReport(r, includeCategories) {
687
+ let md = "# Prompt Injection Benchmark Report\n\n";
688
+ md += `Generated: ${r.generatedAt || (/* @__PURE__ */ new Date()).toISOString()}
689
+
690
+ `;
691
+ if (r.defense) {
692
+ md += `Defense: ${r.defense}${r.version ? ` (${r.version})` : ""}
693
+
694
+ `;
695
+ }
696
+ md += "## Summary\n\n";
697
+ md += "| Metric | Value |\n|--------|-------|\n";
698
+ md += `| Detection Rate | ${(r.detectionRate * 100).toFixed(1)}% |
699
+ `;
700
+ md += `| False Positive Rate | ${(r.falsePositiveRate * 100).toFixed(1)}% |
701
+ `;
702
+ md += `| Total Attacks | ${r.totalAttacks} |
703
+ `;
704
+ md += `| Avg Latency | ${r.avgLatencyMs.toFixed(1)}ms |
705
+ `;
706
+ if (includeCategories && Object.keys(r.categoryBreakdown).length > 0) {
707
+ md += "\n## Category Breakdown\n\n";
708
+ md += "| Category | Detection Rate | Attacks |\n|----------|----------------|---------|\n";
709
+ for (const [category, data] of Object.entries(r.categoryBreakdown)) {
710
+ md += `| ${category} | ${(data.detectionRate * 100).toFixed(1)}% | ${data.totalAttacks} |
711
+ `;
712
+ }
713
+ }
714
+ return md;
715
+ }
716
+ function generateHtmlReport(r, includeCategories) {
717
+ return `<!DOCTYPE html>
718
+ <html>
719
+ <head>
720
+ <title>Prompt Injection Benchmark Report</title>
721
+ <style>
722
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; }
723
+ h1 { color: #333; }
724
+ table { border-collapse: collapse; width: 100%; margin: 20px 0; }
725
+ th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
726
+ th { background: #f5f5f5; }
727
+ .metric { display: inline-block; margin: 10px 20px 10px 0; padding: 15px 25px; background: #f8f9fa; border-radius: 8px; }
728
+ .metric-value { font-size: 24px; font-weight: bold; color: #007bff; }
729
+ .metric-label { font-size: 12px; color: #666; text-transform: uppercase; }
730
+ </style>
731
+ </head>
732
+ <body>
733
+ <h1>Prompt Injection Benchmark Report</h1>
734
+ <p>Generated: ${r.generatedAt || (/* @__PURE__ */ new Date()).toISOString()}</p>
735
+ ${r.defense ? `<p>Defense: ${r.defense}${r.version ? ` (${r.version})` : ""}</p>` : ""}
736
+
737
+ <h2>Summary</h2>
738
+ <div class="metric">
739
+ <div class="metric-value">${(r.detectionRate * 100).toFixed(1)}%</div>
740
+ <div class="metric-label">Detection Rate</div>
741
+ </div>
742
+ <div class="metric">
743
+ <div class="metric-value">${(r.falsePositiveRate * 100).toFixed(1)}%</div>
744
+ <div class="metric-label">False Positive Rate</div>
745
+ </div>
746
+ <div class="metric">
747
+ <div class="metric-value">${r.totalAttacks}</div>
748
+ <div class="metric-label">Total Attacks</div>
749
+ </div>
750
+ <div class="metric">
751
+ <div class="metric-value">${r.avgLatencyMs.toFixed(1)}ms</div>
752
+ <div class="metric-label">Avg Latency</div>
753
+ </div>
754
+
755
+ ${includeCategories && Object.keys(r.categoryBreakdown).length > 0 ? `
756
+ <h2>Category Breakdown</h2>
757
+ <table>
758
+ <tr><th>Category</th><th>Detection Rate</th><th>Attacks</th></tr>
759
+ ${Object.entries(r.categoryBreakdown).map(
760
+ ([category, data]) => `<tr><td>${category}</td><td>${(data.detectionRate * 100).toFixed(1)}%</td><td>${data.totalAttacks}</td></tr>`
761
+ ).join("")}
762
+ </table>
763
+ ` : ""}
764
+ </body>
765
+ </html>`;
766
+ }
767
+
768
+ // src/cli.ts
769
+ var require2 = createRequire(import.meta.url);
770
+ var pkg = require2("../package.json");
771
+ var program = new Command7();
772
+ program.name("prompt-injection-bench").description(pkg.description).version(pkg.version);
773
+ program.addCommand(createBenchmarkCommand());
774
+ program.addCommand(createAttackCommand());
775
+ program.addCommand(createCompareCommand());
776
+ program.addCommand(createCorpusCommand());
777
+ program.addCommand(createLeaderboardCommand());
778
+ program.addCommand(createReportCommand());
779
+ program.parse();