Releases: 0xSteph/pentest-ai
Release list
ptai 1.1.0 — verification coverage + resilience
ptai 1.1.0 — verification coverage + resilience
Roughly doubles the vulnerability classes ptai oracle-verifies, and fixes the bug that made a scan of a live vulnerable app report 0 verified.
Added
- Ten new verification oracles (each with a control that must fail on a safe target): trusted-header bypass, JWT
alg:none, host-header poisoning, XXE, type confusion, stored XSS, sequential IDOR, mass-assignment, non-blind SSRF, SQLi login-bypass. 14 distinct classes now verify. - Path-traversal oracle (
ptai demo→ 4 findings, all 4 oracle-VERIFIED).
Fixed
- Scope safety: active tools (sqlmap/dalfox) are host-locked to the engagement target; no more attacking third-party URLs scraped from a target's content.
- Verify resilience: the verify phase waits for a wobbled target to recover instead of failing every oracle against it. Took OWASP Juice Shop from 0 → 12 oracle-verified in a scan.
Validation
- Juice Shop scoped scan: 12 verified. Multi-vuln honeypot: 23 verified across 14 classes, 100% precision, 0 false positives. Reproducible (honeypot + clean-app gates in the repo).
ptai 1.0.0 — the Trust Spine
[1.0.0] - 2026-06-25
The Trust Spine release. ptai now proves what it finds: a finding earns the VERIFIED badge only when a machine oracle re-runs the exploit and reproduces it N out of N times. No oracle, no badge, and no verdict from an LLM assertion.
Added
- Verifier + receipt contract. A verified verdict must name the machine oracle that earned it, enforced in code.
- Recipe-driven oracles: unescaped reflection, open redirect, IDOR/BOLA differential, error signature, MCP exposure, SQLi boolean and boolean-blind, SSTI (including error-based), sensitive data exposure, and HTTP request smuggling (CL.TE), plus an out-of-band OAST callback oracle for blind SSRF/XXE over a self-hosted loopback collaborator.
- Portable proof capsules and
ptai replay, plus multi-hop chain capsules, so a finding's proof travels and replays without trusting ptai. - Prove-or-kill gating: third-party scanner output (nuclei, nikto, zap) is held back until an oracle re-proves it.
- Verified-only SARIF export (
ptai export --sarif) with a frozen, versioned export-properties contract. - REST path-parameter injection in the SQLi/XSS/SSTI fuzzers, so injection in
/rest/products/<id>style routes is caught, not just query parameters. - Experimental CL.TE request-smuggling discovery probe, oracle-gated.
- CI gate (
--fail-on verified) with a composite action, and a bundledptai demo.
Changed
- Honest verdicts. An oracle miss now reads as
candidate(could not re-prove), neverrefuted;refutedis reserved for an oracle that can truly disprove a vulnerability. - Impact-honest severity: a bare out-of-band callback proves existence, not impact, so it is rated medium until impact is reproduced.
Verified
- 100% precision with zero false positives on the honeypot benchmark, and field-validated against live OWASP Juice Shop: a real broken-object-level-authorization bug verified end to end with a replayable proof capsule.
v0.17.2
Patch release: tool-installer fixes from user feedback on issue #12.
Fixed
- No more duplicate installs. Tool detection now also checks
~/go/bin,~/.cargo/bin, and~/.local/bin, not just barePATH. Go- and cargo-installed tools were being seen as missing and reinstalled on every run. - Cargo and manual install methods now work.
feroxbuster,findomain,rustscan(cargo) andevil-winrm(gem) previously fell through to "Unsupported install method" and never installed.
v0.17.1 - close issue #12 silent-exit + hang + zero-findings
Closes issue #12. Three user-facing failure modes fixed across two reporters (poeylizn on DeepSeek-via-OpenAI-compat, as8ASd3 on Ollama with no PENTEST_AI_LLM_PROVIDER).
Headline: ptai start now produces findings on a vulnerable target regardless of which LLM you have, and always exits cleanly.
Live-verified end-to-end with Ollama qwen2.5-coder:7b against the local honeypot: 63 findings (13 critical, 6 high) emerged via the safety-net escalation path, clean exit in 193 s.
What's in 0.17.1 vs 0.17.0: 0.17.1 is the same production code as 0.17.0 plus a CI test fix (0.17.0's new matrix test had two assertion bugs that blocked PyPI publish). The actual user-facing fixes are unchanged.
See CHANGELOG.md for the full surface.
Install / upgrade:
```
pipx upgrade ptai
or
pip install --upgrade ptai
```
If ptai start still surprises you, run ptai doctor for a single-command diagnostic of your provider config.
v0.16.2 — PENTEST_AI_MODEL across all providers + provider docs (issue #12 follow-up)
Issue #12 follow-up. A 0.16.1 user (poeylizn) was pointing ptai at DeepSeek deployed in the cloud and still hit a failure because the OpenAI provider hardcoded gpt-4o regardless of what their endpoint actually served. Two changes plus a new docs page close the loop.
Fixed
PENTEST_AI_MODELenv var now honoured across all four providers (engine/llm/factory.py). Previously only the LiteLLM path read it; the openai / anthropic / ollama paths used hardcoded defaults. A user withOPENAI_BASE_URL=https://api.deepseek.com/v1and a DeepSeek key got a 404 because the factory still asked forgpt-4o. Now settingPENTEST_AI_MODEL=deepseek-chat(or any model your endpoint actually serves) routes through cleanly. Same env var works for pinning Claude / Llama / Qwen variants on the other paths. Closes the silent-mismatch class for the OpenAI-compatible-endpoint case.
Added
docs/llm-providers.md— concrete configuration recipes for every supported path: Anthropic, OpenAI, OpenAI-compatible third-parties (DeepSeek cloud, Groq, Together AI, local llama.cpp/vLLM/LM Studio), Ollama, and LiteLLM (Azure, Bedrock, Vertex, OpenRouter). Each recipe is a copy-pasteable env block. Also covers troubleshooting (gpt-4o404s, Ollama hangs, 401s, LiteLLM ImportErrors) and the--no-llmescape hatch. Linked from the README's Path 3 section.
v0.16.1 — Real multi-provider CLI agent-mode (issue #12)
Patch release closing the silent-exit bug in CLI agent-mode that issue #12 reporters kept hitting. The 0.16.0 release had an earlier attempt at this fix (read the canonical OLLAMA_HOST env var instead of OLLAMA_BASE_URL); that change was correct but solved an adjacent bug, not the one users were experiencing.
Fixed
-
CLI agent-mode now actually honors
PENTEST_AI_LLM_PROVIDER(cli/main.py,engine/agents/anthropic_agent.py,engine/llm/factory.py). The previousptai startflow hardcodedAsyncAnthropic()regardless of which provider the user configured. Users who setOPENAI_API_KEYorOLLAMA_HOST(or both) sawagent_mode: NNN action handlers registered, then a spinner, then a silent exit — because the first Anthropic call failed under the spinner and the agent's exception handler returned a cleanfinishaction. The CLI now constructs the LLM client viaengine.llm.factory.create_llm_client(), which routes correctly through every supported provider:# OpenAI export OPENAI_API_KEY=sk-... ptai start http://target # Local Ollama (configurable via OLLAMA_HOST) export PENTEST_AI_LLM_PROVIDER=ollama ptai start http://target # No LLM, deterministic tools only ptai start http://target --no-llm
Three sub-changes:
cli/main.pyreplacesAsyncAnthropic()withcreate_llm_client().engine/agents/anthropic_agent.pyduck-types the client. If it exposes.complete()(the unifiedLLMClientfrom the factory), use that; otherwise fall back to the legacyclient.messages.create()path so existing test fixtures keep working.engine/llm/factory.pyauto-detects provider from whichever API key is set whenPENTEST_AI_LLM_PROVIDERisn't explicit.ANTHROPIC_API_KEYset → anthropic,OPENAI_API_KEYset → openai, neither set → openai default. Closes the "I set OPENAI_API_KEY in good faith, why is it routing to Anthropic" foot-gun.
Verified end-to-end against a live local Ollama running
qwen2.5-coder:7b: factory routes toOllamaProvider, the unifiedLLMClient.complete()round-trips successfully,AnthropicAgent.decide_next_actionreturns a realActioninstead of the silentfinish-on-failure. Closes the silent-exit class for the issue #12 reporters.
v0.16.0 — Evidence bundle, real stealth, OOB callbacks, plugin foundation
Headline release closing 3 of 4 deal-breakers from the pentester-perspective audit shipped on 2026-05-24. Every finding now carries cryptographically-hashed proof, blind-vulnerability classes are detectable via OOB callbacks, and intensity=stealth actually changes traffic shape. Plus the foundations for the Caido / Burp / ZAP plugins coming in subsequent releases.
39 commits since 0.15.3. Smoke verified against the TaskFlow honeypot: 8 findings persisted, 8/8 carry evidence_artifacts, 406/406 on-disk artifacts produce valid curl reproducers, SARIF webRequest/webResponse populated, REST /v1/health + /v1/findings return 200, CLI agent-mode parity confirmed.
Added: plugin-client surfaces (REST adapter + auth hardening)
Foundation for the pentest-ai-extensions repo's Caido / Burp / ZAP plugins.
- MCP auth: per-install token file + Host: allowlist (
mcp_server/auth_local.py).~/.pentest-ai/mcp-token(0600, freshsecrets.token_urlsafe(32)on first start) gates every SSE/HTTP request viaAuthorization: Bearer …. Host header allowlist (127.0.0.1/localhost/::1plus the configured bind host) closes the CVE-2025-49596-style DNS-rebinding pivot from a same-host browser. Stdio transport stays auth-less (caller already has process access).ptai mcp --no-authopts out on trusted hosts;--token-file <path>overrides the location. - REST adapter at
/v1/*(mcp_server/rest.py). Four routes —GET /v1/health,GET /v1/findings,POST /v1/http_request,GET /v1/evidence— delegate to the existing@mcp.tool()functions via FastMCP'scustom_route()decorator. ReusesLocalAuthMiddlewarefor identical Bearer-token + Host-header enforcement. Lets JVM/JS HTTP clients consume ptai over plain REST instead of SSE+JSON-RPC; foundation for the proxy plugins. get_findingsgainsurl=+since=filters (engine/findings_db.py,mcp_server/server.py).url=is a case-insensitiveLIKEmatch against thetargetcolumn so a proxy plugin can scope the Findings tab to the URL the user is currently inspecting.since=<iso-ts>lets the tab poll incrementally without re-downloading. Both default toNoneand combine cleanly with existing severity/status filters.health()MCP tool (mcp_server/server.py). Liveness probe for plugin status indicators. Returns{status, version, timestamp, uptime_seconds, active_engagements}with zero side effects; never raises (degrades toactive_engagements=0when the DB is unreachable).
Added: CLI agent-mode parity for evidence + proxy passthrough
ptai startagent-mode path (engine/agents/handlers/registry_bridge.py) now populatessession._ptai_extraswithengagement_id+ anEvidenceCollector(rooted at$PENTEST_EVIDENCE_DIR) + an optionalproxyfromPTAI_UPSTREAM_PROXY, then drains the pending-evidence buffer onto every emitted finding via_attach_pending_evidence_to_findings(session, findings). Closes the carry-forward from Phase 1: every CLI-driven probe now carries evidence_artifacts the same way MCP-driven probes do. Intensity-derived stealth knobs (UA rotation, jitter) on the CLI path deferred —WorkingMemorydoesn't carry intensity today; small refactor follows when needed.
Added: out-of-band collaborator (Interactsh / OAST) integration
Phase 4 of the pentester-first roadmap closes audit deal-breaker #2 — blind-vulnerability classes (blind SSRF, blind SQLi, blind XXE, blind stored XSS, SSTI, Log4Shell) are now detectable.
engine/oob/package — async Interactsh client. Generates RSA-2048 keypair per engagement,POST /registerto the configured server, pollsGET /poll, decrypts each interaction RSA-OAEP-SHA256-wrapped AES-CTR-256 (IV = first 16 bytes). Wire format verified againstgithub.qkg1.top/projectdiscovery/interactsh. Defaults tohttps://oast.fun, accepts any self-hosted server via--oast-server.pending_oobtable (engine/findings_db.py) — parks finding templates at probe-fire time; persists across MCP restarts so late-arriving callbacks don't lose their trail. Indexed by engagement, status, and payload subdomain for O(1) interaction → probe lookup.- Curated payload library (
engine/oob/payloads.py) — per-vuln-class payload templates with{OAST}placeholders: blind SSRF (HTTP/gopher/dict/ldap), blind SQLi per DBMS (MySQL/Postgres/MSSQL/Oracle), blind XXE (two-stage DTD + SVG), blind RCE (curl/wget/nslookup/dig + Windows variants + base64-wrapped WAF bypass), blind stored XSS (5 shapes), SSTI (Jinja2/Twig/Freemarker), Log4Shell (jndi:ldap/dns/rmi +${lower:}bypass). poll_oobMCP tool — the LLM driving ptai over MCP calls this after firing OOB-enabled probes. Polls the collaborator up totimeout=60seconds (capped at 300, configurable), matches arriving interactions back to their pending rows viafind_pending_oob_by_full_id, materializes the finding with the interaction record (timestamp, source IP, raw bytes) as an on-disk evidence artifact via the Phase-1 collector, flips the pending row tomatched. Stale rows past theirexpires_atget bulk-moved toexpired.- Probe wirings — SSRF (
web.ssrf_cloud_metadata), blind SQLi (web.sqli_fuzz), XXE (web.xxe_upload), stored XSS (web.stored_xss) all fire OAST payloads whenengagement_idis present in session extras. Bounded fan-out: one OOB-fire per discovered endpoint/path so request volume stays sane. Blind-RCE wiring deferred — no general command-injection probe exists today; the payloads ship for when one lands. - CLI flags —
ptai start --oast-server URL/--oast-token T/--no-oast. SetsPTAI_OAST_SERVER/PTAI_OAST_TOKEN/PTAI_NO_OAST=1for the engagement. - Privacy disclosure in README under Responsible Use — encrypted-payload + server-side-metadata model, when to self-host (paid engagements / programs forbidding third-party collaborator infra), how to disable entirely.
End-to-end verified by a test (tests/test_oob_end_to_end.py) that stands up a mock Interactsh server on a loopback port, exercises register_oob_probe → mock-/register → pending_oob → mock-/poll-with-real-encryption → poll_oob → materialized finding with on-disk OOB evidence artifact carrying the interaction's source IP. 346 / 346 across the full Phase 1 + Phase 2 + Phase 4 regression sweep.
Added: real intensity=stealth implementation
The intensity=stealth knob had been advertised on the engagement schema since 0.15.0 but only changed rate-limit behavior. Now it actually changes traffic shape — closing the credibility-gap deal-breaker from the pentester audit.
- Curated UA pool with per-call rotation (
engine/probes/primitives.py). 7 modern UAs (Chrome / Firefox / Safari across Windows / Mac / Linux / Android / iOS) selected at random per HTTP call when stealth is on. WAF / scanner fingerprinters can't pin a single engine. - Per-request jitter (
engine/probes/primitives.py).asyncio.sleepofrandom.uniform(min_ms, max_ms) / 1000before every outbound call when stealth is on (default window 250–1500 ms). Concurrent probe waves stop bursting the target. - Upstream proxy passthrough (
engine/probes/primitives.py+mcp_server/probes.py). Every HTTP call honorsextras["proxy"], populated from thePTAI_UPSTREAM_PROXYenv var or the new--upstream-proxyCLI flag. Lets pentesters route ptai through Burp / Caido for live inspection at any intensity —ptai start http://target --upstream-proxy http://127.0.0.1:8080. - Stealth knobs wired into MCP run_probe (
mcp_server/probes.py). When the engagement intensity isstealth, run_probe populates session extras withua_rotation=True,jitter_ms=(250, 1500). http_request always honorsPTAI_UPSTREAM_PROXY.
Active on the MCP path today. CLI agent-mode + legacy orchestrator wiring still tracked separately.
Added: per-finding evidence bundle
The headline change. Every finding now ships with cryptographically-hashed proof of the HTTP exchange that produced it — closing the "AI slop" reproducibility deal-breaker that's caused multiple bug-bounty programs to deprioritize LLM-driven reports through 2025-2026.
evidence_artifactscolumn onfindingstable (engine/findings_db.py). JSON list of{artifact_id, type, sha256, filename, method, url, status_code}summaries pointing at the on-disk exchanges. Migration via the existing_add_column_if_missing()pattern with a'[]'default so old rows read as an empty list.- HTTP capture in the primitives chokepoint (
engine/probes/primitives.py). Everyhttp_get/http_post_json/http_post_form/http_put_jsoncall through the primitives layer now persists its request + response to disk and appends a summary to a per-session pending buffer when anEvidenceCollectoris attached via_ptai_extras. Existing probes get evidence for free with zero probe-side changes. - MCP
run_probecollector wiring + orchestrator auto-attach (mcp_server/probes.py). Constructs anEvidenceCollectorrooted at$PENTEST_EVIDENCE_DIR/<engagement_id>/, attaches it plusengagement_idto the probe's session, then after the probe returns its findings drains the pending buffer and auto-attaches the artifact summaries to every emitted finding that didn't set its ownevidence_artifacts. - MCP
http_requestcapture parity (mcp_server/probes.py). The LLM's raw-HTTP escape hatch now captures its exchange and returns the artifact summary in the response dict underevidence_artifacts. - SARIF v2.1.0 DAST
webRequest/webResponse(engine/sarif.py). When a finding carries anhttp_requestartifact, the SARIF result populates the DAST extension fields. GitHub Code Scanning's Security tab now renders the captured exchange inline with each finding. request_to_curl()utility (engine/evidence.py). POSIX-shell-safe curl one-liner builder; used by the report rendere...
v0.15.3 — default cap $2 → $10 + docs
Default-budget bump and documentation. The standalone agent loop's spending cap defaults to $10 USD (up from $2), which fits a normal Sonnet 4.6 web-app engagement end to end without the cap firing mid-recon.
Changed
DEFAULT_PRICE_LIMIT_USDraised from $2 to $10 (engine/llm/cost.py). The previous $2 default was pre-launch hardening but aborted real Sonnet engagements mid-engagement; $10 finishes most Sonnet sweeps while still catching runaway agent loops. Override viaPTAI_PRICE_LIMIT=N;=0is still unlimited; resume after a cap-abort withptai resume <engagement_id>(engagement state is preserved ataborted_cost_limitstatus). Applies only to the standalone agent loop (ptai start) — MCP-driven engagements continue to use the AI client's own billing.
Docs
- New "Spending cap (Path 3 only)" section in README under the standalone-CLI install path. Documents the env-var override, the unlimited escape hatch, and the resume-after-cap workflow. Explicitly clarifies that Paths 1 and 2 (MCP-driven via Claude Code, Cursor, etc.) don't use this cap.
Install
pip install ptai==0.15.3PyPI: https://pypi.org/project/ptai/0.15.3/
Full notes in CHANGELOG.md.
v0.15.2 — MCP auth-cache + bearer CLI + reap
Pre-launch patch + small feature batch. The headline is the MCP-side auth-cache fix that makes http_request and run_probe inherit the engagement's authentication automatically, so an LLM driving ptai over MCP doesn't have to re-thread auth_profile on every tool call.
Fixed
- MCP
http_request/run_probeinherit engagement auth automatically (mcp_server/auth_cache.py,mcp_server/server.py). A process-local cache keyed byengagement_idstores the auth captured duringstart_engagement; subsequent tool calls without an explicitauth_profileargument resolve from the cache. Validated end-to-end against Juice Shop v19.2.1:start_engagement(auth_profile="juice_shop")populates the cache, thenhttp_request(engagement_id=..., url="/rest/basket/1")returns the admin basket with no further auth wiring.
Added
ptai start --auth-profile <name>accepts bearer-flow profiles (cli/main.py). Previously the CLI rejected anything other thanform_post; nowbearer_staticandbearer_dynamicboth work and propagate the token through every specialist agent.ptai reap --older-than <duration> [--force] [--dry-run](cli/main.py,engine/findings_db.py:FindingsDB.reap_engagements). Bulk-delete old engagements with cascade safety — skips engagements that still have findings unless--forceis passed.
Internal
- Post-publish PyPI smoke test (
.github/workflows/release.yml). Newsmokejob waits for the wheel to propagate, installs it in a fresh venv, verifiesptai --versionmatches the tag, imports key modules, and runs an MCPinitializehandshake. Catches missing-file wheels and broken entry points before users hit them. - Test hygiene: silenced async-mock warnings in file-upload tests, registered the
pytest-timeoutmarker. - Reverted a brief regression in the scorer's title-match branch (
73e0bd4reverts25d041c) that dropped the Juice Shop catch rate ~73% → ~32% on realistic data. Unit tests passed; the realistic benchmark caught it. Adding a corpus-benchmark CI gate is on deck.
Install
pip install ptai==0.15.2PyPI: https://pypi.org/project/ptai/0.15.2/
Full notes in CHANGELOG.md.
v0.15.1 — MCP bearer-flow auth fix
Patch follow-up to 0.15.0 closing a silent auth failure on the headline flow: bearer profiles.
Fixed
- MCP bearer-flow auth profile resolution (
mcp_server/auth.py). Profiles declaringflow: bearerwithpassword_source: envsilently fell back to form-post:resolve_auth_profile_to_dictignoredprof.flowand returned aform_postdict for any password-source profile. Downstream POSTed form-encoded credentials to JSON-only endpoints like Juice Shop/rest/user/loginand the login silently failed withauth_profile '<name>' could not be resolved or login failed. Adds abearer_dynamicbranch on both sides of the resolver. Two integration tests cover the dict shape and the async POST→JWT path. Validated end-to-end against Juice Shop v19.2.1 (8 phases completed, 100 findings, same 43/68 catch set as the 0.15.0 deterministic baseline).
Install
pip install ptai==0.15.1PyPI: https://pypi.org/project/ptai/0.15.1/
Full notes in CHANGELOG.md.