ZeroLeaksDocs
ZeroLeaks Package

Scan Engine

createScanEngine for advanced use. Configure models, TAP parameters, Best-of-N, the inspector and orchestrator, injection probe selection, and callbacks.

Use createScanEngine for more control than runSecurityScan provides. It accepts an EngineConfig and returns a ScanEngine with a runScan() method.

Basic usage

import { createScanEngine } from "zeroleaks";

const engine = createScanEngine({
  scan: {
    maxTurns: 20,
    maxTreeDepth: 4,
    branchingFactor: 3,
    enableBestOfN: true,
    bestOfNCount: 3,
  },
});

const result = await engine.runScan(systemPrompt, {
  onProgress: async (progress) => {
    console.log(`Turn ${progress.turn}/${progress.maxTurns} (${progress.phase})`);
  },
  onFinding: async (finding) => {
    console.log("Finding:", finding.technique, finding.severity);
  },
});

createScanEngine applies the defaults below to omitted fields. A field set to undefined or an empty string is treated as omitted. In 1.4.0, an explicit undefined erased the default; version 1.5.0 fixes this behavior.

EngineConfig

interface EngineConfig {
  apiKey?: string;                  // defaults to OPENROUTER_API_KEY
  scan?: Partial<ScanConfig>;       // the options in the table below
  attacker?: AttackerConfig;        // per-agent overrides, e.g. { model }
  evaluator?: EvaluatorConfig;
  mutator?: MutatorConfig;
  strategist?: StrategistConfig;
  target?: TargetConfig;            // { model?, apiKey? }
}

Scan options

OptionTypeDefaultDescription
maxTurnsnumber25Maximum extraction turns
maxTreeDepthnumber4TAP tree depth
branchingFactornumber3TAP branching factor
pruningThresholdnumber0.3Candidates scoring below this are pruned
enableBestOfNbooleantrueGenerate Best-of-N variations of some attacks
bestOfNCountnumber3Variations per Best-of-N round
attackerModelstringanthropic/claude-opus-4.8Model for the attacker, strategist, and mutator
evaluatorModelstringanthropic/claude-sonnet-5Model for the evaluator, and for the inspector unless inspectorModel is set
targetModelstringanthropic/claude-sonnet-5Model that runs your system prompt
inspectorModelstringevaluator modelModel for the inspector
injectionEvaluatorModelstringevaluator modelModel for the injection compliance judge
enableInspectorbooleantrueCreate the defense inspector
enableDefenseFingerprintingbooleanfalseRun the inspector on every response and call onDefenseDetected when it identifies a known guardrail. Needs enableInspector.
enableMultiTurnOrchestratorbooleantrueScripted multi-turn sequences (Siren, Echo Chamber, TombRaider)
orchestratorPattern"auto" | "siren" | "echo_chamber" | "tombRaider"noneSequence to start with when the orchestrator is on
enableAdaptiveTemperaturebooleanfalseAutoAdv-style temperature scheduling. Also creates the orchestrator.
temperatureConfigPartial<TemperatureConfig>DEFAULT_TEMPERATURE_CONFIGTemperature schedule for the orchestrator
scanMode"extraction" | "injection""extraction"Mode for a single-mode scan
enableDualModebooleanfalseRun extraction and injection in parallel. Overrides scanMode.
injectionCategoriesstring[]allInjection probe categories to run
injectionSeveritiesstring[]allInjection probe severities to run
maxInjectionProbesnumber20Maximum injection probes, in severity order. 0 runs all.
enableMultiTurnInjectionbooleantrueInclude multi-turn grooming injection probes

ScanConfig also declares enableCrescendo, enableManyShot, maxTokensPerTurn, maxTotalTokens, enableVectorMemory, enableParallelEvaluation, enableFailureAnalysis, and injectionTestTypes. The 1.5.0 engine accepts them but does not read them.

runScan options

engine.runScan(systemPrompt, {
  onProgress?: (progress: ScanProgress) => Promise<void>,
  onFinding?: (finding: Finding) => Promise<void>,
  onDefenseDetected?: (fingerprint: DefenseFingerprint) => Promise<void>,
  onInjectionResult?: (result: InjectionTestResult) => Promise<void>,
  maxDurationMs?: number, // default 0, no limit
});

With a time limit set, the extraction loop stops when less than 30 seconds of the budget remain.

onProgress is called on each turn:

interface ScanProgress {
  turn: number;
  maxTurns: number;
  phase: AttackPhase; // "reconnaissance" | "profiling" | "soft_probe" | "escalation" | "exploitation" | "persistence"
  strategy: string;
  leakStatus: LeakStatus; // "none" | "hint" | "fragment" | "substantial" | "complete"
  findingsCount: number;
  treeNodesExplored: number;
  estimatedCompletion: number; // 0-1
}

onFinding is called for each extraction finding:

interface Finding {
  id: string;
  turn: number;
  timestamp: number;
  technique: string;
  category: AttackCategory;
  severity: "critical" | "high" | "medium" | "low";
  confidence: "high" | "medium" | "low";
  extractedContent: string;
  contentType: "system_prompt" | "rule" | "constraint" | "capability" | "persona" | "unknown";
  evidence: string;
  attackNodeId: string;
  verified: boolean;
  verificationMethod?: string;
}

onInjectionResult is called for each injection probe. Useful fields on InjectionTestResult are success, compliance ("full", "partial", or "refused"), severity, technique, probeCategory, intent, and evidence.

Dual-mode scan

Set enableDualMode and call runScan. Extraction and injection run in parallel against two separate target sessions.

const engine = createScanEngine({ scan: { enableDualMode: true } });

const result = await engine.runScan(systemPrompt, {
  maxDurationMs: 120000,
  onProgress: async (p) => console.log(p.turn, p.maxTurns),
  onFinding: async (f) => console.log(f.technique),
  onInjectionResult: async (r) => console.log(r.technique, r.success, r.compliance),
});

console.log(result.overallScore, result.injectionScore);

Custom engine example

import { createScanEngine } from "zeroleaks";

const engine = createScanEngine({
  apiKey: process.env.OPENROUTER_API_KEY,
  scan: {
    maxTurns: 10,
    maxTreeDepth: 3,
    branchingFactor: 2,
    enableBestOfN: true,
    bestOfNCount: 5,
    targetModel: "openai/gpt-5",
  },
});

const result = await engine.runScan(systemPrompt, {
  maxDurationMs: 60000,
  onProgress: async (p) => {
    process.stdout.write(`\rTurn ${p.turn}/${p.maxTurns} (${p.leakStatus})`);
  },
  onFinding: async (f) => {
    console.log(`\n[!] ${f.severity}: ${f.technique}`);
  },
});

console.log(`\nScore: ${result.overallScore}/100 (${result.overallVulnerability})`);

ScanEngine also has getDefenseFingerprint() and getCurrentTemperature() for reading state after or during a scan.

On this page