Skip to content
BreachPilot

Config Reference (config.yaml)

Runtime source of truth for all engine behavior. This file documents every top-level section and key, where each is consumed, and which CLI flags / env vars can override it.

opencode.jsonc is NOT app config. It is editor-local config (gitignored) for the opencode.ai editor's own model provider. Application config lives only in config.yaml (AGENTS.md rule 5). mission.yaml is Flow B's mission scope file — the exploit engine reads its scope rules from config.yaml's exploit block instead (config.yaml:112-125).

Purpose

  • config.yaml is the checked-in operator defaults; tools/config/schema.py::CONFIG_SCHEMA mirrors the same defaults for when the file is missing or a key is absent (re-exported for back-compat by the tools/config_manager.py shim). The two are proven in sync by tests/test_config_manager.py::test_config_yaml_keys_subset_of_schema (assert set(yaml.safe_load(open('config.yaml')))==set(CONFIG_SCHEMA) — CI fails on drift; the same check runs as a CI lint step on top-level keys).
  • Every top-level key is consumed somewhere; a missing key almost always falls back to a schema default rather than failing. Strict sections exploit, mcp, ollama, models promote unknown nested keys (e.g. exploit.permision typo) from warning to error, so CI fails on typos (python -m pytest tests/test_config_manager.py -k unknown).
  • Secrets never live here — they are env vars (or secr.json via --setup-api-keys), named by api_key_env / token_env keys.
  • Machine-readable health: python main.py --doctor --json emits {checks:[{name, ok, error}], is_valid, unknown_keys} (see tools/doctor.py::build_doctor_report) for make doctor --json | jq -e '.is_valid'.

Load & validation flow

StepWhereBehavior
Load YAMLConfigValidator.load (tools/config/validator.py)Missing file → defaults; non-mapping → ValueError
ValidateConfigValidator.validate (tools/config/validator.py)Unknown top-level keys → warnings (plugin-registered sections exempt); type/range checks per section; errors for hard violations (e.g. api.host non-loopback)
Merge defaultsapply_defaults (tools/config/validator.py)Deep-merge loaded config over CONFIG_SCHEMA defaults
Entry pointsload_validated_config (tools/config/loader.py) raises on errors, logs warnings; main.py and mcp_*_server.py use the lighter tools/config_cli.load_config (raw YAML, no defaults merged)
Live PATCHPATCH /config (tools/api/routes/system.py)Atomic deep-merge, re-validated through ConfigValidator; loopback-only allowed_origins enforced

Required sections (warned if absent, defaults apply): ollama, models, mcp, exploit (tools/config/validator.py).

Two load paths matter for defaults: tools/kernel/config.py::load_config (raw YAML; {} when the file is missing; no defaults merged) is what main.py/mcp_shared/exploit_session use, so run-time consumers read missing keys defensively with their own fallbacks (e.g. exploit.permissionread_only). tools/config/loader.py::load_validated_config (validation + apply_defaults() over CONFIG_SCHEMA) is what policy-adjacent helpers use.

Config CLI

There is no dedicated config subcommand; config interaction is via flags on main.py and helpers in tools/config_cli.py:

Command / flagWhat it doesSource
--config <path>Path to the YAML file (default config.yaml)main.py --config
--setup-api-keys / --api-key-file / --no-api-key-promptPrompt for provider keys, persist to secr.json, load into env at bootmain.py api-keys group; bootstrap_startup_api_keys (tools/config_cli.py); tools/api_key_store.py
Start New Session (target entry)Persists target into exploit.allowed_targets via atomic, comment-preserving YAML editadd_target_to_allowlist / _add_allowed_target_to_yaml (tools/config_cli.py)
--skills* flagsMutate the in-memory config["skills"] dict only (advisory)apply_skills_cli_overrides (tools/skills_cli.py)
--doctorLoads config, checks ollama host/models/nmap/ports/workspacetools/doctor.py
--self-testSame config reads as doctor, localhost smoke testtools/self_test.py

Change config → verify with python main.py --doctor (env, nmap, Ollama reachability, model registry, port conflicts) and python main.py --self-test (a safe localhost smoke test) before running sessions.

Env var reference

Env varDefaultPurposeSet by config keyRead at
OLLAMA_API_KEYBearer token for Ollama Cloud; missing → 401 on first chatollama.api_key_env (also research.ollama.api_key_env)model_router.py:301-304, doctor.py:154, api_key_store.py:49-50
NVD_API_KEYNVD API key (raises rate limit)cve_lookup.api_key_envmcp_shared.py:109, cve_lookup.py:62
GITHUB_TOKENGitHub Search token for cve_to_poc (60/hr unauth limit)cve_lookup.github.token_envapi_key_store.py:53, exploit_search.py:190-237
SERPAPI_API_KEYSerpAPI key for web researchresearch.serpapi.api_key_envmcp_shared.py:160, web_researcher.py:182
SHODAN_API_KEYShodan key for passive OSINT (config key wins)recon.shodan_api_keyrecon_pipeline.py:287
EXPLOIT_TARGETOperator's literal --target (IP or domain); the allowlist lock's primary identity, unioned at check timeset by tools/mcp_session.py:255 from --targetmcp_shared.py:523
EXPLOIT_TARGET_IPResolved IP for a domain --targetmcp_session.py:265mcp_shared.py:523
EXPLOIT_TARGET_DOMAINDomain string for a domain --targetmcp_session.py:266mcp_shared.py:523
EXPLOIT_DISCOVERED_TARGETSComma-separated subdomains/IPs auto-authorized mid-runadd_discovered_target mcp_shared.py:537-555mcp_shared.py:528-533
EXPLOIT_WORKSPACEexploit_workspaceExploit workspace root overrideset by mcp_session.py:256cve_lookup.py:171 (KEV cache), tools/kernel/workspace.py:139
BREACHPILOT_API_TOKENtoken fileWebUI daemon bearer token override (never logged)api.token_fileapp.py:71, tools/api/auth.py:46
OPENCODE_GO_API_KEYOpenCode Go provider key (Responses API at opencode.ai)providers.opencode_go.api_key_envtools/providers/opencode_go_provider.py, api_key_store.py
CALDERA_API_KEYCaldera server API key (env-only, never config)caldera.api_key_envplugins/caldera/plugin.py:42
TICKETING_TOKENJira/GitHub ticketing token (env-only)ticketing.token_envtools/ticketing.py:33
PROXMOX_API_TOKENProxmox snapshot provider token (env-only, never logged)— (provider proxmox)tools/snapshots.py ProxmoxProvider
MCP_HTTP_TOKENOptional bearer auth for MCP HTTP transportmcp_shared.run_mcp_http_server, mcp_engine_server.py:27
MCP_ALLOW_PUBLIC_BINDSecond half of the two-person rule for non-loopback MCP bindsmcp_shared.run_mcp_http_server
AI_NMAP_ACTIVE_MODEL_ALIASActive model alias threaded into the MCP server subprocessset by mcp_session.py:270tools/mcp_tools/registry.py:201, peer_models.py:80
AI_NMAP_DEBUGDebug logging switchset by main.py:590 from --debugexploit_agent
RESEARCH_WORKSPACEresearch_workspaceFlow B research workspacedb.py:806, model_telemetry.py:111

Top-level sections

ollama: (config.yaml:2-6) — model backend

KeyTypeDefaultControlsConsumed at
hoststrhttps://api.ollama.comOllama endpoint for chat/generate (cloud default; point at a local daemon to go local). The ollama Python client auto-attaches Authorization: Bearer $OLLAMA_API_KEY.tools/config/loader.py get_ollama_host, tools/model_router.py, tools/doctor.py
modelstrglm-5.2:cloudDefault concrete model idtools/config/schema.py, tools/interactive_menu.py (menu default write)
api_key_envstrOLLAMA_API_KEYEnv var holding the bearer tokentools/api_key_store.py
embed_hoststrhttp://localhost:11434Embedding host (falls back to host) — embeddings stay local by default even on the cloud chat pathtools/exploit_agent/runner/_impl.py (SemanticMemoryManager wiring), tools/skill_embeddings.py

models: (config.yaml:15-44) — model registry

KeyTypeDefaultControlsConsumed at
providerenumopencode_go (lab config.yaml) / ollama (schema fallback; absent = ollama)Active chat/generate provider; validated against the provider registry (built-ins: ollama|opencode_go|chatgpt, via tools.config_manager.resolve_known_provider_ids) — adding provider #4 extends the whitelist automatically.tools/config/loader.py get_ai_provider, tools/providers/registry.py, tools/model_router.py build_router/build_model_client_for_provider, run_service/service.py, doctor.py, api/routes/system.py
registrymap[alias→model id]kimi/deepseek/deepseek_flash/glm/minimax/glm3Alias → concrete model mapping (glm3glm-5.3-flash, the fast GLM flash variant, 128K context in models.info)tools/config/schema.py, tools/doctor.py, tools/run_service/service.py, tools/mcp_tools/registry.py
default_aliasstrglmActive model alias (Ollama path; ChatGPT path uses chatgpt.default_model)tools/config/schema.py, tools/run_service/service.py, tools/eval_harness.py, legacy/agent_loop.py
auto_updatebooltrueAuto-update registry against the live Ollama API (GET /api/tags): at daemon boot each alias is bumped to the newest same-family version (e.g. glm-5.2:cloudglm-5.3:cloud). No pulls (cloud pull = pointer only); models.info stays operator-managed. On demand: POST /api/v1/models/refreshtools/ollama_models.py (auto_refresh_on_startup, refresh_model_registry), main.py _auto_update_models, api/routes/system.py refresh_models
info.<alias>.context_windowintper-modelSource of truth for the adaptive context compactormodel_router.py:202-221, exploit_agent/context.py:63-104
info.<alias>.label/descriptionstrper-modelDisplay metadatamodel_router.py:130, api routes/system.py:193-194

providers: (config.yaml) — per-provider chat config (canonical shape)

The modern home for chat-provider config. Each registered provider id gets its own block — exactly one normalization layer reads them (tools/config/loader.py get_provider_config): the providers.<id> block wins, the legacy top-level block (chatgpt:, opencode_go:) is the fallback, and tools/config/schema.py DEFAULT_CONFIG["<id>"] supplies schema defaults. Adding provider #4 adds a block here — no new top-level config plumbing. The checked-in config.yaml carries providers.opencode_go (the active provider) + providers.chatgpt.

Key (per provider)TypeDefaultControlsConsumed at
enabledboolfalseMaster switch (advisory; models.provider is the real selector)BaseProvider.is_configured, adapters
base_urlstrper adapterProvider endpointadapter build_client/list_models
api_key_envstrper adapterEnv var holding the API key (value is env-only, never in config)adapters, tools/api_key_store.py
request_timeout_secondsint300HTTP timeout for provider callsmodel_router.py, adapters
default_modelstrper adapterDefault concrete model id; also the session-titler modelresolve_default_model (tools/providers/registry.py), session_titler.py
modelslist[str][]Override model list; [] = live discoveryBaseProvider.list_models, build_router
context_windowint128000Conservative context window for the compactormodel_router.py, exploit_agent/context.py
discover_cache_secondsint300Live-model-discovery cache TTLadapters

Built-in id → legacy-fallback block mapping: chatgptchatgpt:, opencode_goopencode_go:, ollamaollama: (host/model only — Ollama's alias registry stays in models:). See provider-development.md for the provider #4 recipe.

chatgpt: (top-level, legacy fallback) — ChatGPT provider (opt-in)

Alternative chat/generate provider backed by the vendored openai-oauth/ loopback proxy. Active only when models.provider: chatgpt. Legacy top-level keys still resolve (see providers: above) — prefer the providers.chatgpt block in new configs. See docs/providers.md § ChatGPT provider.

KeyTypeDefaultControlsConsumed at
enabledboolfalseMaster switch (advisory; models.provider is the real selector)tools/config/loader.py get_chatgpt_config
hoststr127.0.0.1Proxy bind — loopback-only; do not point at a non-loopback interfacechatgpt_provider.py ensure_running
portint10531Proxy portchatgpt_provider.py ensure_running
base_urlstrhttp://127.0.0.1:10531/v1OpenAI-compatible endpoint the adapter POSTs tochatgpt_provider.py ChatGptProxyClient, discover_models
auto_startbooltrueStart the vendored proxy if /health is downchatgpt_provider.py ensure_running
local_repostr./oauthPath to the vendored checkout (cwd for CLI subprocess)chatgpt_provider.py _resolve_runtime/ensure_running/run_login/shutdown
runtimestrautoauto|bun|node — how to run the openai-oauth CLIchatgpt_provider.py _resolve_runtime
request_timeout_secondsint300httpx timeout for /v1/chat/completionschatgpt_provider.py ChatGptProxyClient, model_router.py
default_modelstrgpt-5.2Fallback model id when /v1/models discovery fails; also the session-titler modelmodel_router.py _build_chatgpt_router, session_titler.py
modelslist[str][]Override model list; [] = discover from /v1/modelsmodel_router.py _build_chatgpt_router
context_windowint128000Conservative context window (/v1/models returns no metadata)model_router.py, exploit_agent/context.py
login_timeout_secondsint300login CLI subprocess timeoutchatgpt_provider.py run_login
start_timeout_secondsint30/health poll budget when auto-startingchatgpt_provider.py ensure_running
discover_cache_secondsint300/v1/models discovery cache TTLchatgpt_provider.py discover_models
oauth_filestr"""" = auto-resolve ~/.codex/auth.json | $CODEX_HOME/auth.json (existence only — never read)chatgpt_provider.py is_authenticated

embeddings: (config.yaml) — embedding provider selection

Semantic memory + skill embeddings go through a separate, chat-provider- independent abstraction (tools/providers/embeddings.py):

KeyTypeDefaultControlsConsumed at
providerenumollamaollama (legacy: local Ollama embeddings) | none (zero requests — semantic memory falls back to keyword storage, skills to deterministic matching)tools/providers/embeddings.py build_embedding_provider
hoststr""Embedding endpoint; "" = ollama.embed_hostollama.host fallbackOllamaEmbeddingProvider
modelstr""Embedding model; "" = nomic-embed-textOllamaEmbeddingProvider
api_key_envstrOLLAMA_API_KEYEnv var holding the bearer token (sent unconditionally; local daemons ignore it)OllamaEmbeddingProvider
timeout_secondsint30urlopen timeoutOllamaEmbeddingProvider

With provider: none (as checked in) the engine makes ZERO Ollama requests from the embeddings path — embeddings_disabled() short-circuits consumers.

mcp: (config.yaml:45-46) — exploit MCP transport

mcp: (config.yaml:45-46) — exploit MCP transport

KeyTypeDefaultControlsConsumed at
default_transportstrstdioDefault exploit-server transport (stdio|http)tools/config/schema.py; CLI --mcp-transport is ignored on the run path — always forced to http so the target-IP lock reaches the server
http_host / http_portstr / int127.0.0.1 / 8001HTTP transport bind (schema default; absent from config.yaml)tools/doctor.py, tools/self_test.py, tools/eval_harness.py, tools/run_service/service.py

engine_mcp: (config.yaml:54-57) — advisory MCP server for foreign AI assistants

Read-only surface (skill search, NVD CVE lookup, run history); no target touching. CLI-runnable regardless; block supplies entrypoint defaults.

KeyTypeDefaultControlsConsumed at
enabledbooltrueAdvertise/enable the engine servermcp_engine_server.py:201-211 (config loaded for CLI defaults)
hoststr127.0.0.1Loopback-only bindmcp_engine_server.py:22-27
portint8002HTTP portmcp_engine_server.py:22

nmap: (config.yaml:62-65) — Linux-friendly nmap invocation

KeyTypeDefaultControlsConsumed at
pathstrnmapBinary override when not on PATHrecon_pipeline.py:289, mcp_server.py:178, doctor.py:331
sudoboolfalseRun nmap via sudo -n for root-only -O/-sSrecon_pipeline.py:290, tools/nmap_priv
priv_fallbackbooltrueAuto-downgrade -sS/-O-sT instead of failingrecon_pipeline.py:291, tools/nmap_priv

exploit: (config.yaml:66-155) — attack path

The target-IP allowlist lock is THE safety gaterequire_explicit_allowlist

  • allowed_targets unioned with the EXPLOIT_TARGET* env vars (_allowed_target_list mcp_shared.py:494-534, _check_allowlist :558-571). permission: full_access auto-approves every action; recon is always READ_ONLY regardless of config (cli_exploit_settings.py:157-159).
KeyTypeDefaultControlsConsumed at
enabledbooltrueMaster switch for exploit pathmcp_shared.py:77
modestrstandaloneRun modecli_exploit_settings.py:128
permissionenumfull_accessfull_access/approve_only/read_only; unknown or missingread_only (safe baseline)cli_exploit_settings.py:12-30, mcp-tools.md:171
attack_modebooltrueLive attack posturecli_exploit_settings.py:131
terminalstrvisibleTerminal echo modecli_exploit_settings.py:131
command_timeout_secondsint300Per-command timeoutcli_exploit_settings.py:132
max_commands_per_sessionint9999Command budgetcli_exploit_settings.py:133
max_roundsint200Round cap (recon/analysis)cli_exploit_settings.py:134
attack_max_commandsint150Attack-mode command budgetcli_exploit_settings.py:123 (long-session overrides, :119)
attack_max_roundsint50Attack-mode round capcli_exploit_settings.py:124
attack_max_duration_minutesint360Attack-mode wall clockcli_exploit_settings.py:125
context_summarize_everyint50Min gap between context compactionscli_exploit_settings.py:140, exploit_agent/context.py:596
auto_post_exploitbooltrueAuto-run post-exploit phasecli_exploit_settings.py:141
max_pivot_depthint2Pivot recursion capcli_exploit_settings.py:142, autonomous_orchestrator.py:1091,1638
workspace_dirstrexploit_workspaceWorkspace rootcli_exploit_settings.py:148, interactive_menu.py:417
loot_workspacestrexploit_workspace/lootLoot dircli_exploit_settings.py:144
attacker_osstrautoOS-aware instructions/toolstools/exploit_agent/runner/_impl.py (_resolve_attacker_os)
searchsploit_pathstrsearchsploitSearchsploit binarymcp_shared.py:78, doctor.py:123
shellstrbashShell for run_exploit_terminal (cmd.exe on Windows)cli_exploit_settings.py:146
msfconsole_pathstrmsfconsoleMetasploit console binarycli_exploit_settings.py:147, tools/mcp_tools/metasploit.py:83
web_searchbooltrueWeb search for exploit intelmcp_shared.py:73-87 (via search block)
max_query_chars / cache_ttl_seconds / cache_max_entriesint200 / 3600 / 50ExploitSearch cache limitsmcp_shared.py:85-87
require_explicit_allowlistbooltrueThe target-IP lock — when true every target-touching tool checks the allowlistmcp_shared.py:561,635; mcp_exploit_server.py:141
allowed_targetslist[str][127.0.0.1]Operator-authorized hosts (IP, domain, *.wildcard, CIDR); Start New Session persists heretools/mcp_shared.py, tools/config_cli.py, tools/exploit_agent/runner/_impl.py (_resolve_allowed_targets)
disallowed_assets / forbidden_actionslist[str][]Enforced on the full_access attack path. Parsed into the ScopeGate handed to ExploitPolicy (tools/exploit_session.py::_build_exploit_scope_gate) and consulted by ExploitPolicy._enforce_mission_scope in approve_action: a tool whose _TOOL_ACTION_CATEGORY category is listed in forbidden_actions, or a command whose destinations fall outside allowed_assets / inside disallowed_assets, is denied with a SCOPE_DENIED row in exploit_audit.jsonl. Hostname destinations are only vetted when the gate's allow rules include a domain/wildcard pattern (otherwise hostname authorization is delegated to the MCP-layer allowlist); loopback and exploit.allowed_targets hosts are pre-authorized at the policy layer. scope_gate=None (swarm without a mission gate) stays permissive. Additional enforcement: the swarm critic agent blocks actions in the swarm mission's forbidden_actions (tools/swarm/agents/critic_agent.py), and Flow B's ScopeGate enforces them (scope_gate.py).tools/exploit_session.py, tools/exploit_agent/policy.py, tools/swarm/agents/critic_agent.py, scope_gate.py
ad_kerberos.enabled + per-tool flagsboolfalse (all; smb_signing_check: true)AD/Kerberos post-exploit suite — master + per-tool must both be truetools/mcp_tools/ad.py:36, tests/test_ad_mcp_tools.py
msf.recipes_enabled / auto_local_exploit_suggesterboolfalseMSF recipe dispatch + advisory LES tasktools/mcp_tools/metasploit.py, autonomous_orchestrator.py:1114-1115
listeners.tls/dns/https_beacon/socks_pivotboolfalseExtended C2 listener types (legacy nc/socat/http ungated)persistent_session_manager.py:399-524, tests/test_listeners_extended.py

stealth: (config.yaml:156-159) — legacy stealth flags (INERT, use opsec)

Stealth is legacy/inert. stealth is kept for compat and is UI-only; it is NOT consumed by the active OPSEC engine. The canonical block is opsec (tools/opsec.py) which gates pacing, UA rotation, DoH, quiet-command hints, and target-aware local_targets_off logic. New config should set opsec.*, not stealth.*. The stealth keys are still validated but have no effect on the agent's runtime behavior (only interactive_menu.py reads them to seed legacy UI).

KeyTypeDefaultControlsConsumed at
rotate_uaboolfalseLEGACY Rotate User-Agent across HTTP egressinteractive_menu.py:387 (superseded by opsec.ua_rotation)
dns_over_httpsboolfalseLEGACY Resolve via DoHinteractive_menu.py:387 (superseded by opsec.doh)
doh_providerstrcloudflareLEGACY cloudflare|googleopsec.py:63,95

opsec: (config.yaml:268-285) — active OPSEC (canonical, replaces stealth)

The opsec block is the active detection-evasion / pacing / UA-rotation / DoH / quiet-command block consumed by tools/opsec.py. See opsec section below for full key table — do not confuse with stealth.

cve_lookup: (config.yaml:160-179) — NVD / vuln-intel

KeyTypeDefaultControlsConsumed at
enabledbooltrueNVD lookup master switchmcp_shared.py:103
max_resultsint5Results per lookupmcp_shared.py:105
rate_limit_secondsfloat6.0Per-instance NVD gap (fallback when no shared limiter)mcp_shared.py:108, cve_lookup.py:61
timeout_secondsint30HTTP timeoutmcp_shared.py:104
cache_ttl_seconds / cache_max_entriesint3600 / 100Cache boundsmcp_shared.py:106-107
api_key_envstrNVD_API_KEYKey env namemcp_shared.py:109, api_key_store.py:52
circuit_failure_thresholdint5Breaker opens after N consecutive failuresmcp_shared.py:110, cve_lookup.py:69
circuit_recovery_timeoutfloat60.0Half-open probe waitmcp_shared.py:111, cve_lookup.py:70
search_rate_limit_per_minutenumber10Process-wide shared NVD budget (0 disables)mcp_shared.py:113-114
epss_enabled / kev_enabledbooltrueEPSS/KEV enrichment (lab default ON, live out of the box)cve_lookup.py:73-74,246-247
kev_cache_ttl_secondsint86400KEV catalog refresh TTLcve_lookup.py:75,182
kev_cache_pathstr"""" = exploit_workspace/.kev_catalog.jsoncve_lookup.py:76,170-171
github.token_envstrGITHUB_TOKENGitHub token for cve_to_poc (optional; unauth 60/hr fallback)api_key_store.py:53, exploit_search.py:190-237

research: (config.yaml:180-213) — web research

KeyTypeDefaultControlsConsumed at
enabledbooltrueResearch subsystemmcp_shared.py:129, api_key_store.py:177
provider / fallback_providerstrollama / serpapiProvider and fallback (ollama|serpapi|stdlib)mcp_shared.py:130-131
timeout_secondsint15HTTP timeoutmcp_shared.py:132
max_resultsint8Result capmcp_shared.py:133
max_fetch_depthint5Page-fetch depthmcp_shared.py:134, web_researcher.py:677-681
max_content_charsint12000Fetched-content capmcp_shared.py:135
cache_ttl_seconds / cache_max_entriesint1800 / 250Cache boundsmcp_shared.py:136-137
min_source_qualitystrmediumlow|medium|high source rankingmcp_shared.py:138, web_researcher.py:889
require_api_key_for_mcp_toolsbooltrueGate MCP research tools on provider keysapi_key_store.py:179
allow_local_fetchboolfalsePermit localhost/private fetchesmcp_shared.py:139
ollama.api_key_env / max_results / use_web_search / use_web_fetchOLLAMA_API_KEY / 8 / true / trueOllama research providermcp_shared.py:153-158, web_researcher.py:319-369
serpapi.api_key_env / endpoint / engine / regionSERPAPI_API_KEY / serpapi.com / duckduckgo / us-enSerpAPI providermcp_shared.py:159-164
assistant.*see research_assistant.py:97-140enabled, automatic: true, failure_trigger: 2, budgetsRead-only in-loop research assistant (advisory)tools/exploit_agent/research_assistant.py, tools/exploit_agent/runner/_impl.py

swarm: (config.yaml:214-233) — multi-agent swarm

KeyTypeDefaultControlsConsumed at
enabledbooltrueSwarm modecli_exploit_settings.py:105, run_service/service.py:437
agentslist[str]recon/vuln/exploit/post_exploit/critic/reflectionAgent rosterswarm/orchestrator.py:564
max_parallel_agentsint3Flow B parallel caplegacy/agent_loop.py
parallel_enabledboolfalseGates route_parallel + spawn_subagent MCP tool; CLI --parallel-swarm flips it (main.py:365-370)mcp_tools/parallel_agents.py:268, prompt.py:386-390
per_phase_concurrencyint3Semaphore for same-phase parallel dispatchprompt.py
exploit_parallelboolfalseParallelize exploit/post_exploit phasesswarm/orchestrator.py:60-73, prompt.py:387
subagent_timeout_secondsint600Ceiling for await_subagentprompt.py:386
session_timeout_secondsfloat— (300s default)Plain-run swarm wall clock (schema-only override; long-session raises it via long_session.swarm_session_timeout_minutes)cli_exploit_settings.py:33-49
critic_enabled / reflection_enabledbooltrueAgent enablementcli_exploit_settings.py:106-107

autonomous: (config.yaml:239-244) — orchestrator Phase 2 (opt-in)

Read by the orchestrator from mission_config (merged from config["autonomous"]).

KeyTypeDefaultControlsConsumed at
persistence_phaseboolfalseRun PERSISTENCE phase after accessautonomous_orchestrator.py:1104
checkpoint_everyint0Save attack_states.json every N targets (0=off)autonomous_orchestrator.py:1105
adaptive_replanboolfalsePer-target replan + vuln-chainingautonomous_orchestrator.py:1106
max_cyclesint100Round cap when adaptive_replan is onautonomous_orchestrator.py:1077
max_pivot_depthint0Single-IP lock defaultautonomous_orchestrator.py:1091

orchestrator: (config.yaml) — cross-mission learning consumer

Semantic-memory consumer for the autonomous orchestrator. When true, the orchestrator builds a SemanticMemoryManager (from the memory config block's embed_host/embedding_model) and calls store_lesson on every confirmed module win so the campaign learns across missions, not just within the exploit loop. Advisory-only — read-only memory store consumer, no execution authority change. Distinct action_type='orchestrator:module_success' isolates these rows from the exploit-loop and swarm-reflection lessons. Lab default ON (matches memory.semantic_enabled: true — the orchestrator is the missing consumer of an already-on capability, not a new attack-path opt-in).

KeyTypeDefaultControlsConsumed at
semantic_memorybooltrueBuild a SemanticMemoryManager + store cross-mission lessons on confirmed winsautonomous_orchestrator.py:1095-1116

fsm: (config.yaml) — FSM / planner-executor split (opt-in, default off)

When enabled, campaign code may route plan execution through the FSM phase guard + memoryless step executor (tools/attack_planner.py: planner_context / step_context_for / record_step_result / fsm_advance, AttackModuleExecutor.execute_plan_step) instead of the LLM-does-everything loop. No command-content gates — only the target-IP allowlist at the MCP layer; recon stays read_only.

KeyTypeDefaultControlsConsumed at
enabledboolfalseRoute plan execution through the FSM guard + memoryless executorsattack_planner.py:482 (fsm_settings)
max_retries_per_stepint3Same-failure_class failures before the stuck-loop breaker blocks the step and forces a replanattack_planner.py:438 (record_step_result)

recon: (config.yaml:251-274) — recon coverage & depth

KeyTypeDefaultControlsConsumed at
extended_enumeratorsbooltrueTLS/SMTP/DB/spider/OSINT additive enumeratorsrecon_pipeline.py:294,1102
udp_top_portsint100nmap -sU --top-ports Nrecon_pipeline.py:251,2246
shodan_api_keystr""Passive OSINT key; "" = disabled (falls back to $SHODAN_API_KEY)recon_pipeline.py:287,1853
max_retriesint2Nmap retry count on timeout/crash; set 0 to skip straight to native socket fallback (faster on Windows Npcap hangs)recon_pipeline.py:234,589
retry_delayfloat5.0Initial retry delay (s); multiplied by 1.5 each retryrecon_pipeline.py:235,590
timeout_secondsint300Per-attempt nmap command timeout (s)recon_pipeline.py:233,588
domain_resolution.enabledbooltrueAccept domain --target, resolve at boottools/validation_utils.resolve_target_to_ip, main.py target threading
domain_resolution.max_subdomainsint500Cap on enumerate_subdomains resultstools/mcp_tools/domain.py:361 (tool default)
domain_resolution.subdomain_sourceslistcrt_sh/dns_bruteforce/subfinder/amassDiscovery sourcestools/mcp_tools/domain.py:360,393-448
domain_resolution.dns_zone_transferboolfalseAXFR attempt opt-intools/mcp_tools/domain.py:587-588
domain_resolution.whois_enabledbooltruedomain_whois tooltools/mcp_tools/domain.py
subdomain_enum / vhost_discovery / waf_fingerprint / asn_whois / cloud_metadata_probe / snmp_enum / dns_zone_transferboolfalseExtended depth enumerators (individually gated)recon_pipeline.py:298-302,1158

opsec: (config.yaml:283-300) — agent's own detection-evasion (opt-in, advisory)

KeyTypeDefaultControlsConsumed at
enabledboolfalseMaster switch (opt-in)opsec.py:92
ua_rotation / dohboolfalseUA rotation / DNS-over-HTTPSopsec.py:93-94
doh_providerstrcloudflarecloudflare|googleopsec.py:95
min_gap_seconds / jitter_secondsfloat0.0Pacing base + jitteropsec.py:96-97
rate_per_minuteint0Token-bucket cap (0=unlimited)opsec.py:98
quiet_command_patternslist[str][]Substrings refused when enabled (advisory)opsec.py:99
noise_budgetint0Max noisy commands (0=unlimited; dormant, not a gate)opsec.py:100, safety-model.md:181
local_targets_offbooltrueLocal/private target → OPSEC forced OFF; public → ONopsec.py:101,124-159
local_cidrslist[str][]Extra CIDRs treated as localopsec.py:102,150
public_autonomybooltruePublic target → AI chooses its own attacks (documentary)opsec.py:103

eval: (config.yaml:305-310) — eval/benchmark harness

KeyTypeDefaultControlsConsumed at
enabledbooltrueGates harness defaults (the --eval flag still works when false)eval_harness.py:376
output_dirstrreports/evalWhere reports/eval/<run_id>/ trees goeval_harness.py:377
max_roundsint30attack_max_rounds for an eval runeval_harness.py:378,421
write_markdown / write_htmlbooltrueEmit markdown/HTML reportseval_harness.py:379-380
regression_tolerancefloat0.05Graded eval: a target regresses when score < baseline_score - toleranceeval_harness.py check_regression
baseline_pathstrreports/eval/baseline.jsonGraded eval: baseline file written by --save-baseline / read by --check-regressioneval_harness.py save_baseline / check_regression

benchmark: (top-level) — reproducible benchmark suite

See docs/benchmarks.md. Defaults in tools/config/schema.py; validated in tools/config/validator.py.

KeyTypeDefaultControlsConsumed at
enabledbooltrueGates the benchmark CLI (--benchmark*)tools/benchmark_cli.py
output_dirstrreports/benchmarksWhere reports/benchmarks/<suite>/<run_id>/ trees gotools/benchmark/runner.py, storage.py
trialsint3Default repeated trials per scenario (1-20; CLI --trials overrides)tools/benchmark_cli.py, service.py
timeout_secondsint1800Per-trial mission timeouttools/benchmark/runner.py
sandbox_requiredbooltrueWhen true, runs without sandbox.enabled are INFRASTRUCTURE_ERROR (no host-execution fallback)tools/benchmark/runner.py
baseline_pathstrreports/benchmarks/baseline.jsonBaseline file written by --save-baseline / read by --check-regressiontools/benchmark/regression.py
regression.success_rate_tolerancefloat0.02Verified-success-rate drop beyond this is a HARD regression (CI exit 1)tools/benchmark/regression.py
regression.false_positive_tolerancefloat0.01False-positive-rate rise beyond this is a HARD regressiontools/benchmark/regression.py
regression.median_time_tolerancefloat0.20Relative median-solve-time rise beyond this is a warningtools/benchmark/regression.py
regression.tool_actions_tolerancefloat0.30Relative median-action rise beyond this is a warningtools/benchmark/regression.py
regression.cost_tolerancefloat0.30Relative estimated-cost rise beyond this is a warningtools/benchmark/regression.py
telemetry.events / token_usage / costbooltrueTelemetry toggles (events JSONL, token accounting, cost)tools/benchmark/agent_runner.py

long_session: (config.yaml:319-326) — multi-hour mode

Enabled by --long-session (main.py:374-376) or enabled: true.

KeyTypeDefaultControlsConsumed at
enabledbooltrue (config.yaml) / false (schema)Master switchcli_exploit_settings.py:43,75
request_timeout_secondsint600Per-LLM-call httpx timeoutrun_service/service.py:337-341, model_router.py:313-316
swarm_session_timeout_minutesint30Raises the 300s swarm capcli_exploit_settings.py:42-49
attack_max_roundsint200Budget overridecli_exploit_settings.py:120
attack_max_commandsint1000Budget overridecli_exploit_settings.py:119
attack_max_duration_minutesint72012h wall clockcli_exploit_settings.py:121
persist_messagesbooltrueCheckpoint compacted messages to session_state.json for crash-safe resumecli_exploit_settings.py:139, session_manager.py:71-99, exploit_agent/context.py:616-623

reasoning: (config.yaml:327-345) — agent reasoning

KeyTypeDefaultControlsConsumed at
chain_of_thoughtbooltrueCoT modecli_exploit_settings.py:89
reflection_every_n_actionsint10Reflection cadencecli_exploit_settings.py:93, tools/exploit_agent/runner/_impl.py (reflection cadence)
critic_enabledbooltrueCritic agent (swarm)cli_exploit_settings.py:106
observer_modestrhybridheuristic|llm|hybrid fact extractioncli_exploit_settings.py:98, main.py:641
ultrathinkbooltrue (config.yaml) / false (schema)Deep-reasoning mode; CLI --ultrathink overridescli_exploit_settings.py:90
ultrathink_reflection_intervalint3Ultrathink reflection cadencecli_exploit_settings.py:92
llm_reflectionbooltrue (config.yaml) / false (schema)LLM-driven reflection in the hot loop (extra LLM calls)cli_exploit_settings.py:94, exploit_agent/reflection.py:135
peer_consult_on_failure_thresholdint3Auto-consult peers after N consecutive exploit failures (0 disables)cli_exploit_settings.py:97, tools/exploit_agent/runner/_impl.py

memory: (config.yaml:346-352) — learning stores

KeyTypeDefaultControlsConsumed at
semantic_enabledbooltrueSemantic memory / embeddingsagent_loop.py (legacy), tools/skill_embeddings.py, tools/exploit_agent/runner/_impl.py (semantic memory wiring)
embedding_modelstrnomic-embed-textEmbedding modelskill_embeddings.py:174, semantic_memory.py:29
cross_mission_learningbooltrueLearn across missionseval_benchmark.py:176
attack_memory_enabledbooltrueAttackMemoryStore in the exploit looptools/exploit_agent/runner/_impl.py (_load_attack_memory_settings + store wiring)
attack_memory_max_context_charsint6000Attack-memory advisory sizetools/exploit_agent/runner/_impl.py, tools/exploit_agent/context.py
experience_min_samplesint3ExperienceStore soundness gateagent_loop.py (legacy), tools/exploit_agent/runner/_impl.py, tools/skill_feedback.py
experience_time_decay_daysfloat90Experience decay (≤0 disables)agent_loop.py:193, skill_feedback.py:128

outcome_judgment: (config.yaml:354-365) — evidence-grounded verdicts

KeyTypeDefaultControlsConsumed at
max_inconclusive_attemptsint3≥2 prevents one failed command exhausting a hypothesistools/config/validator.py (validation)
confirmation_threshold / refutation_thresholdfloat0.75Evidence thresholds (0.5-1.0)tools/config/validator.py
min_evidence_referencesint1Min evidence refs for a verdicttools/config/validator.py
flow_abooltrue (config.yaml) / false (schema)Wire OutcomeJudge into Flow A exploit loop (overrides shallow exit-code success)cli_exploit_settings.py:154, eval_benchmark.py:231
peer_reviewbooltrue (config.yaml lab) / false (schema)D3: cross-model outcome grading (peer_review_outcome MCP tool — one alias plans, a different alias grades evidence; advisory-only, deterministic judge stays authority)mcp_tools/peer_models.py:162

poc_verification: (config.yaml:264-271) — self-healing PoC verification (Killer Feature #3)

When enabled, cve_to_exploit_synth syntax-checks its synthesized PoC inline (py_compile, no exec) and the verify_poc MCP tool compile-tests the PoC inside a fully-isolated Docker container. The PoC is NEVER executed on the operator box — this is a compile/import gate, not a sandbox guarantee.

KeyTypeDefaultControlsConsumed at
enabledbooltrue (config.yaml lab) / false (schema)Master toggle (inline synth check + Docker compile path)mcp_tools/attack_modules.py (cve_to_exploit_synth), mcp_tools/poc_verifier.py
docker_imagestrpython:3.11-slimImage for the compile/import containertools/poc_verifier.py:docker_check
compile_timeout_secondsint30Container run timeouttools/poc_verifier.py:docker_check
max_retriesint3Self-heal loop cap (synth → verify → LLM fix → re-verify)mcp_tools/attack_modules.py (agent-driven)
docker_networkstrnoneContainer network mode (always none — PoC must never reach target/network)tools/poc_verifier.py:docker_check
docker_read_onlybooltrueMount container filesystem read-onlytools/poc_verifier.py:docker_check
docker_memorystr256mContainer memory captools/poc_verifier.py:docker_check

replay_simulator: (config.yaml:273) — pre-commit attack-plan critique (D2)

When enabled, registers the replay_simulate MCP tool — a local-only @audit_tool (no target touch) that dry-runs an attack plan against a saved ReconAssessment JSON. The LLM critiques its own plan (confidence, branches); if the LLM is unavailable, degrades to rule-based scoring. Zero target touch.

KeyTypeDefaultControlsConsumed at
enabledbooltrue (lab config.yaml) / false (schema)Registers the replay_simulate MCP toolmcp_tools/replay_simulator.py
counterfactualboolfalseExploit-loop counterfactual replay: after a failed exploit action that had a snapshot taken, the loop reverts the snapshot and retries the mutated payload against the clean state, recording both outcomes in final_result["counterfactual"]. Requires snapshots.enabled for effectexploit_agent/runner/_impl.py (_counterfactual_enabled)

killchain: (top-level) — kill-chain state machine (opt-in, default OFF)

When enabled, registers the kill-chain MCP tool family (tools/mcp_tools/killchain.py) and builds a per-target tools/killchain/machine.py::KillChainMachine inside the exploit loop. The machine tracks stage progression (recon → initial_access → escalation → objective), refuses out-of-order transitions when require_verification is true (a stage advance needs an evidence-verified exploit outcome, never an agent claim), and renders a KILLCHAIN BRIEFING block into the agent system prompt. The campaign orchestrator prefers kill-chain state for phase selection when enabled. Every transition is recorded on the audit trail.

KeyTypeDefaultControlsConsumed at
enabledboolfalseRegisters the killchain_* MCP tools + loop wiringmcp_tools/killchain.py, exploit_agent/runner/_impl.py (_build_killchain_machine)
goal_statestrshell_as_rootObjective stage the machine drives towardkillchain/machine.py
require_verificationbooltrueReporting verbosity only — stage-advance verification is always enforcedkillchain/machine.py
graph_dbstr""Kill-chain graph store path; "" = <workspace>/killchain_graph.dbkillchain/

snapshots: (top-level) — snapshot + rollback (opt-in, default OFF)

Snapshot-before-destructive infrastructure for the lab build. A pluggable provider layer (tools/snapshots.py: Docker commit/rollback is the mandatory, fully-implemented path; Proxmox / libvirt / Hyper-V / VMware are best-effort wrappers). Wired into all three dispatch funnels — the exploit loop (tools/exploit_agent/runner/_impl.py), the swarm bridge (tools/swarm_bridge.py), and the campaign executor (tools/campaign/executor.py) — plus three MCP tools. Every consumer is fail-open: a snapshot failure logs a warning and never blocks the attack path. The vm_id/container must be operator-authorized (the MCP tools are @require_allowlist("vm_id")-gated; the allowlist IS the lock).

KeyTypeDefaultControlsConsumed at
enabledboolfalseMaster gate; when false no snapshot is taken and no snapshot_* tool registerssnapshots.py should_snapshot, mcp_tools/snapshots.py
providerstrdockerActive provider (docker | proxmox | libvirt | hyperv | vmware)snapshots.py get_provider
auto_before_destructivebooltrueSnapshot automatically before destructive payloads / exploit-execution-category toolssnapshots.py should_snapshot
max_snapshots_per_targetint3Rolling cap; oldest snapshot deleted when exceededsnapshots.py _enforce_cap
vm_mapmap{}target IP → vm_id/container name (env override SNAPSHOT_VM_MAP); unmapped targets are used rawsnapshots.py _vm_id_for_target
providers.docker.compose_filestreval_targets/docker-compose.ymlDocumented compose file backing container targetssnapshots.py DockerProvider
providers.hyperv.powershell_commandstrpowershellPowerShell executable for Checkpoint-VM / Restore-VMCheckpointsnapshots.py HyperVProvider
providers.vmware.vmrun_pathstrvmrunvmrun binary pathsnapshots.py VMwareProvider
providers.proxmox.host / .nodestr""Proxmox API endpoint + node (auth via PROXMOX_API_TOKEN env only — never config, never logged)snapshots.py ProxmoxProvider
providers.libvirt.virsh_pathstrvirshvirsh binary pathsnapshots.py LibvirtProvider

adaptive_exploits: (config.yaml:366-373) — exploit mutation

KeyTypeDefaultControlsConsumed at
enabledbooltrueMutation enginecli_exploit_settings.py:99, mcp_tools/attack_modules.py:1325,1420
max_mutationsint5Mutation capcli_exploit_settings.py:100
mutation_strategieslist[str]parameter_tweak/encoding_change/delivery_swap/context_awareStrategy rostercli_exploit_settings.py:101-104

multi_model: (config.yaml:374-384) — peer-model consultation (advisory)

KeyTypeDefaultControlsConsumed at
enabledbooltrue (config.yaml) / false (schema)Exposes consult_peer_models; CLI --multi-model-consult/--no-multi-model-consult overridemain.py:607-609, tools/mcp_tools/registry.py:223
consult_aliaseslist[str]all five aliasesPeer roster (intersected with registered models)cli_exploit_settings.py:109, tools/mcp_tools/registry.py:190-201
max_consultationsint10Shared per-run budget (single counter)exploit_agent/reflection.py:325, peer_models.py:55
max_question_chars / max_answer_charsint4000 / 8000Truncation boundsreflection.py:326-327, peer_models.py:56-57

skills: (config.yaml:385-422) — runtime skill pipeline

Advisory prompt context only — never permission/scope/audit (docs/skills.md:162-168).

KeyTypeDefaultControlsConsumed at
enabledbooltrueMaster toggleskill_pipeline.py:63,196, exploit_agent/skills.py:65
rootslist[str]["skills"]Skill directoriesmcp_engine_server.py:70, skill_registry_cache.py:27
default_enabledlist[str]6 skills (nmap, pentest, red-team, mcp-audit, agentic-ai, domains)Always-active skillsskill_selector.py:164,321
include_tags / exclude_nameslist[str][]Tag include / name exclude filtersskill_selector.py
maybe_enabledboolfalseInclude skills/maybe/ skillsskill_selector.py:130
allow_model_lookupbooltrueEnable read-only skill MCP toolstools/mcp_tools/registry.py:266
inject_startup_contextboolfalseEager body injection into initial promptskill_pipeline.py (CLI --skills on sets it: skills_cli.py:37-39)
max_active_skills / min_contextual_skillsint6 / 3Selection boundsskill_selector.py:124
max_chars_per_skill / max_total_charsint2500 / 9000Prompt budget capsskill_pipeline.py
default_skill_weight / context_skill_weightint12 / 24Score weightsskill_selector.py
reselect_mid_run / reselect_max_per_run / reselect_min_interval_actions / reselect_sticky_defaultstrue / 3 / 5 / trueMid-run re-selection; --no-skills-reselect disablesskill_selector.py, exploit_agent/skills.py:44
swarm_inject / swarm_phase_hints_onlybooltrueSwarm skill sharing (hints only for non-exploit agents)skill_pipeline.py:198
feedback_enabled / feedback_skill_weight / feedback_min_observationstrue / 8 / 3Cross-mission feedback boostskill_selector.py:300, skill_feedback.py
semantic_matching / semantic_skill_weight / semantic_min_similarity / semantic_modeltrue / 16 / 0.35 / nomic-embed-textEmbedding-based rankingskill_selector.py:265
diversity_penaltyint12Penalize tag-overlapping skillsskill_selector.py:316
include_metadataboolfalseAppend references in rendered contextskills.md:127
allow_reference_listingbooltruelist_skill_references MCP toolskills.md:153

plugins: (schema default []; the lab config.yaml enables 13 shipped plugins)

KeyTypeDefaultControlsConsumed at
enabledlist[str][]Explicitly loaded plugins (schema default OFF — trusted Python, full operator-box privileges; the lab config.yaml lists 13, each no-op until its own API key/URL is configured)tools/plugins.py
disabledlist[str][]Hard-blocked regardless of manifesttools/plugins.py
search_pathslist[str]["plugins"]Filesystem dirs scanned for plugin.yamltools/plugins.py
entry_pointsbooltruebreachpilot.plugins entry-point discoverytools/plugins.py

threat_intel: (config.yaml:126-137) — threat-feed ingestion (OSV.dev + GHSA + KEV)

Advisory-only, never touches the target. Lab build ON so the feed is live out-of-the-box. Reuses cve_lookup KEV catalog (shared disk cache). GHSA needs GITHUB_TOKEN (shared with cve_lookup.github.token_env); when absent, GHSA is silently dropped and osv+kev still answer.

KeyTypeDefaultControlsConsumed at
enabledbooltrueThreat-feed master switchtools/threat_intel.py:45, mcp_tools/research.py:210
cache_dirstrexploit_workspace/.threat_intelFeed cache directorytools/threat_intel.py:50
cache_ttl_secondsint86400Cache TTLtools/threat_intel.py:52
sources.osv / ghsa / kev / exploitdb_rssbooltrue/true/true/falseSource togglestools/threat_intel.py:55-60
max_resultsint20Results per querytools/threat_intel.py:62
github_token_envstrGITHUB_TOKENGHSA token envtools/threat_intel.py:65, api_key_store.py:53
timeout_secondsint30HTTP timeouttools/threat_intel.py:66

witness: (config.yaml:187-194) — advisory audit-stream watcher (agent-on-agent safety)

Library default OFF (conservative for downstream re-use); the checked-in config.yaml flips it ON for the lab runtime. Wiring: when enabled is true, the transport-neutral run lifecycle (tools/run_service/execute.py, serving BOTH the CLI and API transports) spawns a per-run WitnessAgent side task that polls the run's audit trails (reports/<run_id>/activity.jsonl, plus the per-attempt exploit_audit.jsonl registered from the session result at teardown) and flags anomalies (allowlist breach, PoC escape, permission escalation, prompt-injection pattern, DoS drift) to log_path (process-global) and, when escalate_to_event_broker is true, as witness_flag events through the transport's event sink (WS/SSE). Detection/auditing only — it never blocks, modifies, or kills a run, and its failure never propagates into the run's result path. The WebUI reads the log via GET /api/v1/runs/{run_id}/witness (tools/api/routes/runs.py), which 404s when the log file does not exist.

KeyTypeDefaultControlsConsumed at
enabledboolfalse (schema) / true (config.yaml lab)Master switch — gates the per-run witness side tasktools/run_service/execute.py (_start_witness), tools/swarm/agents/witness_agent.py
log_pathstrreports/witness.jsonlWitness log (process-global, not per-run)witness_agent.py; tools/api/routes/runs.py (GET /runs/{id}/witness)
poll_interval_secondsint5Poll intervalwitness_agent.py; execute.py poll task
escalate_to_event_brokerbooltrueEmit witness_flag events through the event sinkwitness_agent.py; execute.py
max_flags_per_signal_per_minuteint10Per-signal rate capwitness_agent.py
dos_failure_window_secondsfloat60.0DoS drift windowwitness_agent.py
dos_failure_thresholdint8DoS drift thresholdwitness_agent.py

ics: (config.yaml:416-418) — D8 ICS write-side modules

ModbusWriteCoil/ModbusWriteRegister/S7PlcStop/S7PlcStart are DESTRUCTIVE — they change physical process state. Dual-gated: @require_allowlist on run_attack_module AND ics.allow_write: true AND ics.destructive_ics: true (both must be true). Default false so checked-in config is safe; set true only for authorized PLC testing. PHYSICAL-DAMAGE RISK.

KeyTypeDefaultControlsConsumed at
allow_writeboolfalseICS write gate (read-only enum when false)tools/attack_modules/modules/ics_iot.py
destructive_icsboolfalseSecond physical-damage gate (both must be true)tools/attack_modules/modules/ics_iot.py:42

webhook_notify: (config.yaml:438-449) — outbound Slack/Discord run-status notifications

Lab build enabled: true. No-op without a url — logs once then drops events. Set url to a Slack/Discord incoming webhook to actually receive pings.

KeyTypeDefaultControlsConsumed at
enabledbooltrueMaster switchtools/plugins/webhook_notify.py, tools/config/schema.py
urlstr""Webhook URL (secret, never logged)webhook_notify.py:35
eventslist[str]["finding","state"]Event-type filterwebhook_notify.py:36
timeout_secondsint5HTTP timeoutwebhook_notify.py:37
max_retriesint3Retry countwebhook_notify.py:38
backoff_secondsfloat2.0Backoffwebhook_notify.py:39
max_payload_charsint8192Payload capwebhook_notify.py:40

mitre: (config.yaml:450-456) — MITRE ATT&CK Navigator export

Lab build enabled: true. export_attack_navigator MCP tool writes Navigator layer JSON to navigator_output_dir for SOC handoff.

KeyTypeDefaultControlsConsumed at
enabledbooltrueMaster switchtools/mitre_export.py, tools/config/schema.py
technique_mapstrtools/mitre_technique_map.jsonATT&CK technique mapmitre_export.py:31
navigator_output_dirstrreports/mitreOutput dirmitre_export.py:32
include_skill_tagsbooltrueInclude skill tagsmitre_export.py:33

ticketing: (config.yaml:457-467) — remediation ticket generation (Jira/GitHub)

Lab build enabled: true. No-op without provider/base_url/token — logs once then drops. Set provider (jira|github), base_url, and the named token_env env var to actually create tickets.

KeyTypeDefaultControlsConsumed at
enabledbooltrueMaster switchtools/ticketing.py, tools/config/schema.py
providerstr""jira | githubticketing.py:31
base_urlstr""Ticketing base URLticketing.py:32
token_envstrTICKETING_TOKENToken env varticketing.py:33, api_key_store.py
project_keystr""Project keyticketing.py:34
max_retriesint3Retry countticketing.py:35
backoff_secondsfloat2.0Backoffticketing.py:36

caldera: (config.yaml:476-480) — D6 Caldera adversary emulation plugin

Lab build enabled: true. The Caldera server is target-side — operator adds its IP to exploit.allowed_targets. Plugin MCP tools (caldera_list_abilities, caldera_run_ability) are @require_allowlist-gated on the target IP.

KeyTypeDefaultControlsConsumed at
enabledbooltrueMaster switchplugins/caldera/plugin.py, tools/config/schema.py
urlstr""Caldera server base URLcaldera/plugin.py:41
api_key_envstrCALDERA_API_KEYCaldera API key envcaldera/plugin.py:42

agent: (config.yaml:486-494) — capability-upgrade agent block (design §23)

Toggles + budgets for the task graph, capability discovery, AI-facing state tools, planner hints, decision logging, reflection, and retry/repair budgets. Defaults preserve today's behavior. config_cli.load_config merges NO defaults, so every consumer reads defensively via cfg.get("agent", {}).get(key, default).

Wiring status: every key below has a live runtime consumer (regression-tested in tests/test_agent_config_wiring.py). All consumers read defensively — an absent agent block or key preserves the historical default behavior. reflection in the loop is also gated by reasoning.llm_reflection / reasoning.reflection_every_n_actions; swarm reflection by swarm.reflection_enabled.

KeyTypeDefaultControlsConsumed at
task_graph_enabledbooltrueWhen false, the plan-mutating update_task MCP tool is not registered (read-only state tools remain)tools/mcp_tools/assessment_state.py
capability_discovery_enabledbooltrueGate the capability-discovery prompt block + the query_capabilities/get_capability_details MCP toolstools/exploit_agent/runner/_impl.py, tools/exploit_agent/prompt.py, tools/mcp_tools/assessment_state.py
state_tools_enabledbooltrueWhen false, the whole assessment-state MCP tool family is unregistered and its prompt section droppedtools/mcp_tools/assessment_state.py, tools/exploit_agent/prompt.py
planner_hints_enabledbooltrueWhen false, the hypothesis-workflow advisory bullets are dropped from the capability-guidance prompt blocktools/exploit_agent/prompt.py:build_capability_guidance
decision_log_enabledbooltrueWhen false, the §17 decision-log hook writes nothing to decision_log.jsonltools/exploit_agent/runner/_impl.py
reflection_enabledbooltrueWhen false, inline reflection rounds in the exploit loop are skippedtools/exploit_agent/runner/_impl.py
max_retries_per_taskint2Per-module failure cap for autonomous campaigns (drop a module from the retry set after N failures); absent key falls back to the campaign class default of 3tools/campaign/orchestrator.py
max_actionsint0Hard cap on agent actions per run; 0 = sentinel (legacy attack_max_commands / max_commands_per_session budgets apply)tools/cli_exploit_settings.pyExploitSettings.effective_max_commands
generated_code_repair_attemptsint3Default for poc_verification.max_retries (explicit poc_verification.max_retries still wins)tools/poc_verifier.py:poc_verification_config

api: (config.yaml:386-407) — WebUI daemon (--demon / --daemon / --web)

KeyTypeDefaultControlsConsumed at
enabledbooltrueDaemon enablementapp.create_app
hoststr127.0.0.1Loopback-only in v1; any other value is a validation ERROR; CLI --api-host overridesmain._run_daemon, tools/config/validator.py
portint8765Daemon port; CLI --api-port overridesmain._run_daemon
token_filestr.webui_secret_keyAuto-generated bearer token file (gitignored); BREACHPILOT_API_TOKEN env overridesapp.py:70, tools/api/auth.py:42-46
allowed_originslist[str][]Extra loopback origins for CORS/WS; null and non-loopback always rejectedapp.py:108
event_buffer_sizeint256In-memory ring buffer per run for WS subscribersapp.py:81
shutdown_timeout_secondsint15Graceful shutdown waittools/api/run_manager.py:320
serve_webuiboolfalseMount webui/dist/ at /; --web sets this in memory onlyapp.py:145, main.py:542
max_concurrent_runsint3D3: N concurrent runs (1 = legacy 409)tools/api/run_manager.py, tools/config/schema.py
multi_operatorbooltrueD4: user accounts + annotations (loopback-only)tools/api/auth.py:60
graph_routebooltrueAttack-path DAG API routetools/api/routes/graph_explorer.py:30

operator_connection: (config.yaml:514-521) — persistent RCE beacons / operator callbacks

KeyTypeDefaultControlsConsumed at
enabledbooltrueMaster switch for beacon/listener managementtools/operator_connection/manager.py, tools/mcp_tools/operator_connection.py
auto_start_listenerbooltrueAuto-start callback listeneroperator_connection/manager.py
default_callback_portint4444Default callback portoperator_connection/manager.py
default_listener_typestrnetcatDefault listener typeoperator_connection/manager.py
beacon_interval_secondsint300Beacon callback intervaloperator_connection/manager.py
health_check_interval_secondsint60Beacon health-check intervaloperator_connection/manager.py
workspace_dirstrexploit_workspaceCallback workspace rootoperator_connection/manager.py

sandbox: (config.yaml:541-566) — disposable execution sandbox (isolation boundary)

Every attack command (terminal commands, generated Python, nmap, Metasploit, etc.) runs inside a hardened per-run Docker worker. Any sandbox failure blocks offensive execution with a structured SANDBOX_* error — host execution is never an automatic fallback. Full architecture + threat model: docs/sandbox.md.

KeyTypeDefaultControlsConsumed at
enabledbooltrueMaster switch; false = explicit legacy host-execution opt-out (uncontained)tools/sandbox/manager.py:resolve_manager
backendstrdockerExecution backendtools/sandbox/models.py
imagestrbreachpilot-sandbox:latestWorker image (build: docker build -t <image> docker/sandbox)tools/sandbox/docker_backend.py
fallback_nativebooltrueBoot-time degrade: unusable Docker (CLI missing, daemon down, image not built) degrades the whole session to legacy uncontained native mode with warning + WebUI banner + SANDBOX_FALLBACK: lines; false = strict fail-closed (executions denied until Docker works)tools/sandbox/manager.py:resolve_manager_with_fallback, docs/sandbox.md
auto_manage_dockerboolfalseWhen true, start Docker for a sandbox session if it is stopped, then stop it on exit only when BP started it and no containers remain; Linux requires cached/non-interactive sudo authorizationtools/sandbox/docker_lifecycle.py
docker_start_timeout_seconds / docker_stop_timeout_secondsint60 / 30Bounds automatic daemon startup/shutdown polling and service callstools/sandbox/docker_lifecycle.py
userstrsandboxContainer user (non-root default)tools/sandbox/docker_backend.py:_build_create_args
read_only_rootfsbooltrueRead-only container rootfs; /workspace + tmpfs stay writable_build_create_args
env_passthroughlist[str][]Extra host env var names the worker may receive (allowlist; never the whole env)tools/sandbox/manager.py:_build_env
resources.memory_mbint4096Container memory cap (min 256)_build_create_args
resources.cpusfloat2CPU cap (min 0.1)_build_create_args
resources.pidsint512Process-count cap (min 32)_build_create_args
resources.timeout_secondsint300Per-command default timeouttools/sandbox/manager.py:execute
resources.output_max_bytesint2000000Per-stream output clamp (min 1024)tools/sandbox/manager.py:_clamp_output
network.enforcebooltrueInstall the netns firewall; false = Docker bridge isolation only (NOT containment)tools/sandbox/manager.py:_apply_policy
network.fail_closedbooltruePolicy failures block executiontools/sandbox/*
network.allow_dnsstrcontrolledcontrolled (host-side validated resolution) or none (port 53 blocked everywhere)tools/sandbox/policy.py, network.py
network.map_host_loopbackboolfalseDev-only mapping of sandbox loopback targets to the host gateway; never enable for production runstools/sandbox/policy.py
network.extra_allow_cidrslist[str][]Operator-authorized extra CIDRstools/sandbox/policy.py
network.allow_gatewayboolfalseAuthorize the Docker bridge gateway (path to host services + Docker daemon) — keep falsetools/sandbox/network.py
network.allow_research_hostsbooltruePinned exploit-research egress (github.com et al., host-resolved + audited)tools/sandbox/policy.py
cleanup.remove_on_exitbooltrueDestroy worker + network after the runtools/sandbox/manager.py:destroy
cleanup.remove_stale_on_startupbooltrueSweep exited labeled containers / empty networks at startup (running concurrent-session workers kept)tools/sandbox/manager.py:cleanup_stale
multi_net_rawbooltrueGrant NET_RAW for raw-packet scanning (nmap -sS); NET_ADMIN is never granted to the workertools/sandbox/manager.py:resolve_manager

browser: (top-level) — browser-native web agent (Playwright, default OFF)

Sandboxed Chromium agent behind the prepared seam. tools/browser/ holds the BrowserBackend ABC, the Playwright adapter (playwright_backend.py), the sandbox launcher (one Chromium op per docker exec, no host fallback), and the fail-closed BrowserManager. Capabilities report available only when enabled + registered + runnable (host SDK or sandbox worker). Full design: docs/browser-agent-design.md.

KeyTypeDefaultControls
enabledboolfalseMaster switch; stock installs never enable
backendstrnonenone or playwright (requires a BACKEND_REGISTRY entry — declared ≠ available)
headlessbooltrueSessions run headless (headed refused in the sandbox worker)
max_sessionsint2Concurrent session cap (manager-enforced)
session_timeout_secondsint300Session idle budget (reaper closes idle sessions)
navigation_timeout_secondsint30Per-navigation budget
capture_screenshotsbooltruePersist screenshots as hashed artifacts
capture_networkbooltrueCapture request/response records (redacted at serialization)
capture_consoleboolfalseConsole capture (opt-in)
persist_storageboolfalseStorage harvest goes to the credential store, never plaintext logs
allow_mutating_actionsboolfalseLab opt-in for browser_execute_js (read-only otherwise)
console_max_eventsint200Console ring-buffer cap per session
network_max_eventsint500Converted network-event history cap per session
body_sample_max_bytesint4096Truncated body sample cap per event
dom_summary_max_charsint8000DOM text summary cap (huge pages truncate)
artifact_dirstr""Screenshot dir override ("" = <workspace>/browser/<session>/)
executable_pathstr""Explicit Chromium binary ("" = Playwright default)
worker_imagestr""Browser worker image override ("" = breachpilot-sandbox:browser)

Other consumed keys

  • reports_dir (not in schema): Path(config.get("reports_dir", "reports")) — app.py:76; also mcp_engine_server.py:74 defaults to reports.

models.roles — model-role routing (design §23)

Nested under the existing models key in CONFIG_SCHEMA (tools/config/schema.py, "models"["roles"]). Mirrored into config.yaml under models.roles. Validation: ConfigValidator.validate warns when a value is not a string or when a non-empty alias is not in models.registry (warn-not-reject).

Each role maps to a model alias; an empty string means "use models.default_alias" so first-run behavior is unchanged. Consumed by tools/model_router.py::ModelRouter.get_client_for_role, which falls back to models.default_alias when the role's alias is empty.

Live call sites (regression-tested in tests/test_agent_config_wiring.py):

  • critic — swarm critic pre-check: tools/swarm/orchestrator.py::_ensure_role_clients stashes critic_model_client into the shared context (resolved once, lazily, best-effort) and tools/swarm/agents/critic_agent.py prefers it over the shared client for its LLM calls.
  • critic — exploit-loop inline reflection: tools/exploit_agent/runner/_impl.py routes _llm_reflect_inline through get_client_for_role("critic", ...) when a role router is resolvable (falls back to the run's default model).
RoleDefaultPurpose
planner""Task-graph / attack-plan generation.
executor""Tool-call driving / terminal + MSF execution.
interpreter""Recon / output parsing / evidence interpretation.
code_generator""PoC synthesis + repair.
critic""Pre-action risk critique.
summarizer""Run reporting / outcome summarization.

Precedents for per-role model overrides: research.assistant.model_alias, multi_model.consult_aliases. Keep models.registry / models.info synchronized (context-window metadata feeds tools/exploit_agent adaptive context handling).

CLI vs config precedence

Explicit CLI flags win over config values; config wins over schema defaults:

Config keyCLI override
models.default_alias--model <alias>
long_session.enabled--long-session (cli_exploit_settings.py:43,75)
swarm.parallel_enabled--parallel-swarm
multi_model.enabled--multi-model-consult / --no-multi-model-consult
exploit.attack_max_commands/roundsagent.max_actions (0 = sentinel → legacy budgets apply) + --long-session raises budgets (cli_exploit_settings.py, ExploitSettings.effective_max_commands)
skills.*--skills on|off|hints|lookup, --skills-include, --skills-exclude, --no-skills-reselect (tools/skills_cli.py)
api.host / api.port--api-host / --api-port
mcp.default_transport--mcp-transport (ignored on the run path — always http)
api.serve_webui--web (in-memory only, never persisted)
reasoning.ultrathink--ultrathink
source: repo docs (build sync)Edit this page on GitHub →