shieldx/scripts/benchmark.ts
Rene Fichtmueller a3793a1357 feat: ShieldX v0.1.0 — Self-Evolving LLM Prompt Injection Defense
10-layer defense pipeline with kill chain mapping, self-healing,
self-learning, and compliance reporting. Local-first, zero cloud deps.

- 72 detection rules across 7 kill chain phases
- 294 unit tests, 500+ attack corpus samples
- Management dashboard (Next.js 15, 10 pages)
- Automated resistance testing (2x daily, 31 probes)
- MITRE ATLAS, OWASP LLM Top 10, EU AI Act compliance
- Integrations: Next.js middleware, Ollama, n8n
- PostgreSQL 17 + pgvector for persistent learning
2026-03-27 15:07:27 +13:00

183 lines
6.1 KiB
TypeScript

#!/usr/bin/env tsx
/**
* ShieldX Benchmark — measures ASR, TPR, FPR, and latency.
* Usage: npm run benchmark
*/
import { readFileSync, writeFileSync, mkdirSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
import { ShieldX } from '../src/core/ShieldX.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
interface CorpusSample {
input: string
expectedPhase: string
expectedThreatLevel: string
description: string
category: string
}
interface CategoryResult {
category: string
samples: number
detected: number
tpr: number
asr: number
avgLatency: number
}
const CORPUS_DIR = join(__dirname, '..', 'tests', 'attack-corpus')
const OUTPUT_DIR = join(__dirname, '..', 'benchmarks')
function loadCorpus(filename: string): CorpusSample[] {
try {
const raw = readFileSync(join(CORPUS_DIR, filename), 'utf-8')
const data = JSON.parse(raw)
return Array.isArray(data) ? data : []
} catch {
return []
}
}
function percentile(sorted: number[], p: number): number {
const idx = Math.ceil(sorted.length * p / 100) - 1
return sorted[Math.max(0, idx)] ?? 0
}
async function main() {
console.log()
console.log('ShieldX Benchmark Results')
console.log('='.repeat(60))
console.log()
// Create ShieldX with memory backend, rule-based scanners only
const shield = new ShieldX({
learning: { enabled: false, storageBackend: 'memory', feedbackLoop: false, communitySync: false, driftDetection: false, activelearning: false, attackGraph: false },
scanners: { rules: true, sentinel: false, constitutional: false, embedding: false, embeddingAnomaly: false, entropy: true, yara: false, attention: false, canary: false, indirect: false, selfConsciousness: false, crossModel: false, behavioral: false, unicode: true, tokenizer: true, compressedPayload: true },
})
// Load all corpus files
const corpusFiles = [
'direct-injection.json', 'indirect-injection.json', 'jailbreaks.json',
'encoding-attacks.json', 'mcp-attacks.json', 'multilingual-attacks.json',
'persistence-attacks.json', 'steganographic-attacks.json', 'tokenizer-attacks.json',
'rag-poisoning.json', 'false-positives.json',
]
let totalAttacks = 0
let totalBenign = 0
let truePositives = 0
let falsePositives = 0
let correctPhase = 0
const allLatencies: number[] = []
const categoryResults: CategoryResult[] = []
for (const file of corpusFiles) {
const samples = loadCorpus(file)
if (samples.length === 0) continue
const category = file.replace('.json', '')
let catDetected = 0
let catSamples = 0
const catLatencies: number[] = []
for (const sample of samples) {
if (!sample.input) continue
catSamples++
const isAttack = sample.expectedPhase !== 'none'
const result = await shield.scanInput(sample.input)
allLatencies.push(result.latencyMs)
catLatencies.push(result.latencyMs)
if (isAttack) {
totalAttacks++
if (result.detected) {
truePositives++
catDetected++
if (result.killChainPhase === sample.expectedPhase) {
correctPhase++
}
}
} else {
totalBenign++
if (result.detected) {
falsePositives++
catDetected++
}
}
}
const isBenignCategory = category === 'false-positives'
const tpr = isBenignCategory ? 0 : (catDetected / Math.max(catSamples, 1)) * 100
const asr = isBenignCategory ? 0 : 100 - tpr
const avgLat = catLatencies.reduce((a, b) => a + b, 0) / Math.max(catLatencies.length, 1)
categoryResults.push({ category, samples: catSamples, detected: catDetected, tpr, asr, avgLatency: avgLat })
}
const sortedLatencies = [...allLatencies].sort((a, b) => a - b)
const tprTotal = (truePositives / Math.max(totalAttacks, 1)) * 100
const fprTotal = (falsePositives / Math.max(totalBenign, 1)) * 100
const asrTotal = 100 - tprTotal
const phaseAccuracy = (correctPhase / Math.max(truePositives, 1)) * 100
const avgLatency = allLatencies.reduce((a, b) => a + b, 0) / Math.max(allLatencies.length, 1)
// Print results
console.log(`Total Samples: ${totalAttacks + totalBenign}`)
console.log(`Attack Samples: ${totalAttacks}`)
console.log(`Benign Samples: ${totalBenign}`)
console.log()
console.log('DETECTION METRICS')
console.log('-'.repeat(40))
console.log(`True Positive Rate (TPR): ${tprTotal.toFixed(1)}%`)
console.log(`False Positive Rate (FPR): ${fprTotal.toFixed(1)}%`)
console.log(`Attack Success Rate (ASR): ${asrTotal.toFixed(1)}%`)
console.log(`Phase Accuracy: ${phaseAccuracy.toFixed(1)}%`)
console.log()
console.log('PER CATEGORY')
console.log('-'.repeat(60))
console.log('Category'.padEnd(30) + 'Samples'.padStart(8) + 'Detected'.padStart(10) + 'TPR'.padStart(8) + 'ASR'.padStart(8))
for (const r of categoryResults) {
console.log(
r.category.padEnd(30) +
String(r.samples).padStart(8) +
String(r.detected).padStart(10) +
`${r.tpr.toFixed(1)}%`.padStart(8) +
`${r.asr.toFixed(1)}%`.padStart(8)
)
}
console.log()
console.log('LATENCY (ms)')
console.log('-'.repeat(40))
console.log(`Average: ${avgLatency.toFixed(2)}`)
console.log(`P50: ${percentile(sortedLatencies, 50).toFixed(2)}`)
console.log(`P95: ${percentile(sortedLatencies, 95).toFixed(2)}`)
console.log(`P99: ${percentile(sortedLatencies, 99).toFixed(2)}`)
// Save to file
mkdirSync(OUTPUT_DIR, { recursive: true })
const report = {
timestamp: new Date().toISOString(),
totalSamples: totalAttacks + totalBenign,
attackSamples: totalAttacks,
benignSamples: totalBenign,
metrics: { tpr: tprTotal, fpr: fprTotal, asr: asrTotal, phaseAccuracy },
latency: {
avg: avgLatency,
p50: percentile(sortedLatencies, 50),
p95: percentile(sortedLatencies, 95),
p99: percentile(sortedLatencies, 99),
},
categories: categoryResults,
}
writeFileSync(join(OUTPUT_DIR, 'results.json'), JSON.stringify(report, null, 2))
console.log()
console.log(`Results saved to benchmarks/results.json`)
}
main().catch(console.error)