Skip to main content
An opfor config is a flat JSON object. target.kind selects the pipeline; the rest configure selection, models, run shape, and telemetry. Generate one with opfor setup or hand-write it.

Common fields

FieldRequiredDescription
target.kindYes"agent" or "mcp"
selection.modeYes"suite" or "evaluators"
selection.suiteFor suiteSuite ID — see the evaluator reference
selection.evaluatorsFor evaluatorsArray of evaluator IDs
attackerLlm.providerYesSee providers
attackerLlm.modelYesModel name (e.g. gpt-4o-mini)
attackerLlm.apiKeyEnvYesEnv var name holding the API key
attackerLlm.baseURLFor openai-compatible / azureBase URL for the LLM endpoint
judgeLlm.*NoSame fields as attackerLlm; a separate judge model. Falls back to attackerLlm
effortYes"adaptive" or "comprehensive"
turnModeNo"single" (default) or "multi"
turnsYesTurns per attack. Ignored when turnMode is "single". Range 1–10 (wizard default 3)
telemetryNoTrace-aware testing — see telemetry
apiKeyEnv is the env var name holding the key, never the key itself. Don’t put raw keys in a config file.

Agent target (target.kind: "agent")

FieldRequiredDescription
target.nameYesHuman-readable name. Used as the report slug
target.descriptionYesWhat it does, data it handles, restrictions. More detail = better attacks
target.typeYes"http-endpoint" or "local-script"
target.endpointFor HTTPFull URL to POST attacks to
target.requestFormatFor HTTP"openai", "json", or "auto" (default). Ignored when stateful is false (forces OpenAI shape)
target.modelFor HTTP / openaiModel name to send in the request body
target.apiKeyEnvNoEnv var name holding the target’s API key
target.headersNoCustom HTTP headers, merged with built-ins
target.promptPathNoDot-path for the prompt field (e.g. "input.message"). Defaults to top-level prompt
target.responsePathNoDot-path to extract the reply (e.g. "data.reply")
target.sessionIdFieldNoBody field for the session ID on multi-turn requests (stateful only)
target.statefulNotrue (default): send current prompt + session ID. false: send full chat history each turn
target.scriptPathFor local-scriptPath to the .js/.py adapter, relative to cwd

Stateful vs stateless

target.statefulUse whenOpfor sends per turn
true (default)Your app keeps conversation history, keyed by a session idOnly the current prompt + a session id at target.sessionIdField
falseRaw, stateless LLM endpoints (OpenAI, Groq, vLLM, LiteLLM…)The full {role, content} history as a chat-completions messages array

MCP target (target.kind: "mcp")

FieldRequiredDescription
target.nameYesHuman-readable name. Used as the report slug
target.descriptionNoShort note on the server; helps attack prompts when provided
target.transportYes"stdio" (local process) or "url" (remote endpoint)
target.commandFor stdioExecutable to run (e.g. "node")
target.argsFor stdioArray of CLI args (e.g. ["dist/index.js"])
target.cwdNoWorking directory for the spawned process (stdio only)
target.envNoEnv vars passed to the spawned server. Values support ${VAR} expansion
target.urlFor urlFull HTTP/SSE endpoint URL
target.urlHeadersNoHTTP headers for the URL transport. Values support ${VAR} expansion

Local script contract

A local-script target receives one attack per process over stdin/stdout:
StreamContent
Stdin{"prompt":"...","context":{...},"sessionId":"..."}. sessionId is present only for multi-turn
Stdout{"response":"..."} on success, or {"error":"..."} on failure. Don’t print debug lines to stdout
StderrLog freely — the CLI forwards stderr to your terminal
The interpreter is picked from the file extension: .py/.pywpython3, .js/.mjs/.cjsnode.