Repository navigation
Configuration
All PentestAI settings and how to change them.
Docker is the supported runtime.
install.shis Docker-only by default; setPENTESTAI_ALLOW_LOCAL_TOOLS=1only for the legacy bare-metal install.
| Setting | Default | Env Var | Description |
|---|---|---|---|
ANTHROPIC_API_KEY |
"" |
ANTHROPIC_API_KEY |
Anthropic API key |
ANTHROPIC_MODEL |
claude-sonnet-4-6 |
ANTHROPIC_MODEL |
Default model |
SESSIONS_DIR |
./sessions/ |
— | Session files directory |
LOGS_DIR |
./logs/ |
— | Audit logs directory |
REPORTS_DIR |
./reports/output/ |
— | Report output directory |
MAX_AUTO_ITERATIONS |
20 |
— | Max autonomous iterations (SkillMode caps: junior 15, mid 12, advanced 20) |
TOOL_TIMEOUT |
120 |
— | Tool timeout (seconds) |
LLM_MAX_TOKENS |
4096 |
— | Max tokens per LLM request |
LLM_TEMPERATURE |
0.2 |
— | LLM temperature |
NVD_TIMEOUT |
5 |
NVD_TIMEOUT |
NVD request timeout (seconds); offline CVE bundle is used first |
HIGH_RISK_ACTIONS |
{sqlmap,nikto,brute_force,exploit} |
— | Actions that require confirmation |
SKILL_MODE |
junior |
PENTESTAI_MODE |
Active skill mode (junior/mid/advanced) |
PENTESTAI_MAX_SECONDS |
0 (unlimited) |
PENTESTAI_MAX_SECONDS |
Wall-clock budget for the ReAct loop |
PENTESTAI_MAX_TOKENS |
0 (unlimited) |
PENTESTAI_MAX_TOKENS |
Approximate token budget (4 chars ≈ 1 token) |
PENTESTAI_REQUIRE_FLAG |
0 |
PENTESTAI_REQUIRE_FLAG |
Force the flag-evidence stop guard even when the objective does not mention a flag |
REPORT_AUTHOR |
PentestAI |
REPORT_AUTHOR |
Name in reports |
REPORT_COMPANY |
Security Assessment |
REPORT_COMPANY |
Company in reports |
APP_VERSION |
0.1.0 |
— | Application version |
EVIDENCE_DIR |
reports/evidence/ |
— | PoC evidence files directory |
PENTESTAI_MODE=junior|mid|advanced or mode junior from inside the shell.
| Setting | Junior | Mid | Advanced |
|---|---|---|---|
| Verbose output | ✅ every iteration | ✅ | ❌ findings only |
| Phase gates | ✅ ask before transition | ❌ | ❌ |
| RAG enrichment | ✅ CVE context | ✅ | ❌ skip |
| AutoPlanner | ✅ auto-generate | ❌ ask first | ❌ ask first |
| Scanner explain | ✅ show payload | ✅ | ❌ raw output |
| Notifications | ✅ | ❌ | ❌ |
| Max ReAct iterations | 15 | 12 | 20 |
| Default autonomy | semi_auto | semi_auto | autonomous |
from config.settings import SkillMode, mode_cfg, SKILL_MODE
# Read the current config
verbose = mode_cfg("react_verbose") # True/False
max_iter = mode_cfg("max_react_iterations") # intSet at the start of every engagement to enforce client-specific rules:
from memory.session import EngagementBrief
brief = EngagementBrief(
client_name="AcmeCorp",
engagement_type="external", # external/internal/red_team/web_app
no_brute_force=True, # block hydra/kerberoast/cred_spray
no_dos=True, # always True unless explicitly disallowed
notify_before_exploit=True, # warning before exploit action
production_systems=["10.0.0.1"], # block direct actions on prod
emergency_contact="08xx-xxxx",
reporting_deadline="2026-12-01",
)
session.set_brief(brief)
# Automatic enforcement in PermissionGate
allowed, msg = session.check_action_brief("hydra", "10.0.0.1")
# → (False, "Brute force prohibited...")CLI: brief set (interactive wizard), brief no-brute-force, brief production <ip>
# ════ LLM PROVIDERS ════
ANTHROPIC_API_KEY= # required if using Anthropic
ANTHROPIC_MODEL=claude-sonnet-4-6
OPENAI_API_KEY= # optional
GROQ_API_KEY= # optional, cheap fast tasks
GOOGLE_API_KEY= # Gemini
MISTRAL_API_KEY= # Mistral AI
XAI_API_KEY= # xAI Grok
DEEPSEEK_API_KEY= # DeepSeek, very cheap
TOGETHER_API_KEY= # Together AI, open-source models
PERPLEXITY_API_KEY= # Perplexity, online models
COHERE_API_KEY= # Cohere
OPENROUTER_API_KEY= # 100+ models via a single key
AZURE_OPENAI_API_KEY= # Azure OpenAI (also requires ENDPOINT)
AZURE_OPENAI_ENDPOINT= # https://your-resource.openai.azure.com
AZURE_OPENAI_DEPLOYMENT= # deployment name (default: gpt-4o)
NINEROUTER_API_KEY= # 9router local gateway
# Custom OpenAI-compatible endpoint (WorkBuddy, LM Studio, vLLM, etc.)
CUSTOM_LLM_BASE_URL=
CUSTOM_LLM_API_KEY=
CUSTOM_LLM_MODEL=
CUSTOM_LLM_NAME=
CUSTOM_LLM_TIER=all
# Ollama (local, no API key)
OLLAMA_BASE_URL=http://localhost:11434
# ════ SKILL MODE ════
PENTESTAI_MODE=junior # junior | mid | advanced (default: junior)
# ════ RUN BUDGET (0 = unlimited) ════
PENTESTAI_MAX_SECONDS=0 # wall-clock limit for the ReAct loop
PENTESTAI_MAX_TOKENS=0 # approximate token limit (4 chars ≈ 1 token)
PENTESTAI_LLM_STEP_TIMEOUT=60 # hard ceiling for one reasoning call (seconds)
PENTESTAI_MAX_RECON_STEPS=4 # scanner actions before the M3 pivot guard blocks them
PENTESTAI_REQUIRE_FLAG=0 # 1 = reject `done` until a flag is captured (auto-on for flag objectives)
PENTESTAI_BENCH_HARD_GRACE=30 # benchmark-only: exit after max_seconds + grace
PENTESTAI_STRATEGY=1 # 1 = retrieve/store generic strategy lessons
PENTESTAI_BUDGET_PROFILE=benchmark # benchmark | engagement
PENTESTAI_IDLE_TIMEOUT=120 # engagement only: stop after no new progress
PENTESTAI_MAX_EXTENSIONS=2 # engagement only: max +60s extensions
PENTESTAI_EXTENSION_SECONDS=60 # engagement only: extension size
# ════ AGENT LLM CALLS (M1: off by default = 1 call/step) ════
PENTESTAI_LLM_ANALYSIS=0 # 1 = extra per-step output analysis
PENTESTAI_LLM_FINDINGS=0 # 1 = LLM finding extraction (adds candidates)
PENTESTAI_NATIVE_TOOLS=1 # 1 = native tool calling, 0 = JSON plan
PENTESTAI_AUTO_LOGIN=0 # 1 = auto-submit login form when detected
PENTESTAI_AUTO_IDOR=0 # 1 = probe IDOR after a successful login
PENTESTAI_AUTO_EXPLOIT=0 # 1 = probe forms for SSTI / command injection
PENTESTAI_AUTO_JWT=0 # 1 = test JWT weaknesses and forge claims
PENTESTAI_AUTO_MONGO=0 # 1 = predict Mongo ObjectIds from timestamps
PENTESTAI_AUTO_PRIV=0 # 1 = probe privilege-escalation paths
PENTESTAI_AUTO_SSRF=0 # 1 = run the verified form-SSRF chain after a URL field
PENTESTAI_EXPERIENCE=1 # 1 = inject generic cross-run lessons (0 = off)
PENTESTAI_LOGIN_EMAIL= # single credential for auto-login
PENTESTAI_LOGIN_PASSWORD=
# Or a JSON list (max 3 attempts), e.g. [["admin","password"],["test","test"]]
PENTESTAI_LOGIN_CREDENTIALS=
# ════ TOOLS (optional, if not on PATH) ════
NMAP_PATH=nmap
GOBUSTER_PATH=gobuster
SQLMAP_PATH=sqlmap
NIKTO_PATH=nikto
PENTESTAI_LHOST= # override LHOST for MSF reverse shells
# ════ TOOL INTEGRATIONS (optional) ════
MSF_HOST=127.0.0.1
MSF_PORT=55553
MSF_PASSWORD=msfrpc
BURP_API_URL=http://localhost:8090
BURP_API_KEY=
# ════ MCP SERVERS (opsional) ════
# JSON array; servers start on first use.
PENTESTAI_MCP_SERVERS=
# ════ CVE / NVD ════
NVD_API_KEY= # optional, increases the NVD rate limit
NVD_TIMEOUT=5 # offline bundle is still used first
# ════ REPORT ════
REPORT_AUTHOR=Security Assessor
REPORT_COMPANY=Your Company
# ════ API SERVER ════
API_HOST=127.0.0.1
API_PORT=8080
# ════ TEST TARGET (web app engagements only) ════
TEST_LOGIN_URL=
TEST_LOGIN_EMAIL_FIELD= # NAME of the HTML field, not an email address
TEST_LOGIN_PASS_FIELD=
TEST_ACCOUNT_A_EMAIL=
TEST_ACCOUNT_A_PASS=
TEST_ACCOUNT_B_EMAIL=
TEST_ACCOUNT_B_PASS=
TEST_TENANT_A=
TEST_TENANT_B=
TEST_OBJECT_ID=
TEST_OBJECT_TYPE=| Mode | Mechanism |
|---|---|
| Bare metal |
python-dotenv (load_dotenv()) reads .env in the project root |
docker run |
--env-file .env |
docker compose |
env_file: .env in the pentestai service + ${VAR} interpolation in the environment: block |
docker-compose.yml defines env_file: .env, so all the variables above get into the container — not just the handful of keys explicitly listed in environment:.
.env is never included in the image: .dockerignore excludes .env, .env.*, and *.env.local. Do not remove those entries.
Verify from inside the container:
docker compose exec -T pentestai sh -c 'env | grep -cE "^(ANTHROPIC|OPENAI|GROQ|GOOGLE|DEEPSEEK|PENTESTAI|REPORT|TEST)_"'
# 35 (= number of relevant variables injected)scripts/env_test.py will flag empty parameters as FAIL. That condition is not a container bug — the values simply have not been filled in .env yet. Providers with empty keys are automatically skipped by build_router_from_env().
Baseline 2026-10-20 (only Anthropic filled in; Ollama was later verified locally on 2026-10-24):
| Empty | Impact |
|---|---|
OPENAI_API_KEY, XAI_API_KEY, MISTRAL_API_KEY, DEEPSEEK_API_KEY, GROQ_API_KEY, GOOGLE_API_KEY, PERPLEXITY_API_KEY, COHERE_API_KEY, TOGETHER_API_KEY, OPENROUTER_API_KEY
|
The relevant provider is inactive; routing goes only to Anthropic |
AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT
|
Azure OpenAI is inactive |
TEST_TENANT_A, TEST_TENANT_B, TEST_OBJECT_ID
|
Multi-tenant test metadata is incomplete |
After filling in keys: docker compose up -d pentestai (the container is recreated and reads env_file again), then run the smoke test again.
⚠️ TEST_LOGIN_EMAIL_FIELD/TEST_LOGIN_PASS_FIELDmust contain the HTML element name (e.g.password), not an email address. If both contain the same email, the test login form will not be filled in correctly.
Every new finding starts as candidate. It becomes validated only when there is reproducible evidence:
| Evidence | Method label |
|---|---|
| Evidence-vault hash attached | evidence_vault |
Captured HTTP request and response in evidence
|
http_exchange |
Verified PoC (poc verify ...) |
poc |
Benchmark flag FLAG{...}
|
benchmark_flag |
Operator approval (validate <id>) |
operator |
A bare curl command is not enough: it shows intent, not proof. It stays a
candidate until a response or PoC is attached.
validate # re-run the gate over all findings
validate <id> # approve one finding manually
report sarif # export validated findings only
report sarif all # include candidates (marked as such)Machine-readable output uses SARIF 2.1.0 with validationStatus in result
properties. The CI gate (scripts/ci_gate.py) blocks only on validated
findings at/above the configured severity.
PentestAI can use external Model Context Protocol servers over stdio. Configure them as a JSON array:
PENTESTAI_MCP_SERVERS=[{"name":"fs","command":["npx","-y","@modelcontextprotocol/server-filesystem","/tmp"]}]Optional per-server keys: env (object) and timeout (seconds, default 30).
mcp list # configured servers and status
mcp tools fs # tools exposed by one server
mcp call fs read_file '{"path":"/tmp/report.txt"}'
mcp close fs # stop a server (or all)Servers start lazily on first use, so a broken server cannot block shell startup. A non-zero server exit or timeout is reported as an error and does not crash the session.
from core.scope import ScopeEnforcer
sc = ScopeEnforcer()
sc.add_ip("10.10.11.0/24")
sc.add_ip("10.10.11.100")
sc.add_domain("htb.local")
sc.add_domain("*.htb.local")
# Scope expansion workflow
req = sc.request_expansion("192.168.1.5", "Trust relationship via SMB")
sc.approve_expansion(req.id) # add to scope, log approved
sc.deny_expansion(req.id) # log denied, do not add to scope
log = sc.expansion_log() # audit trail of all requestsCLI: expand <target> <reason> → scope approve <ref> → scope deny <ref>
anthropic · openai · groq · ollama · openrouter · google · mistral · xai · deepseek · together · perplexity · azure · cohere · 9router · custom
simple · medium · complex · all
orchestrate [target] [--seq]
Reads the target from the active session if not provided. The --seq flag for sequential (default: parallel). Skill mode affects this: advanced = no phase gates, junior = ask approval per phase.
ptes
Output to reports/output/*_ptes.json and *_ptes.md.
iso27001
Output to reports/output/*_iso27001.json.
marketplace [list|search <q>|install <id>|uninstall <id>|info <id>|installed|fetch]
Plugins are stored in the plugins/ directory. Default registry: github.com/pentestai/marketplace.
Override with the MARKETPLACE_REGISTRY_URL env var.