Skip to content

Commit 13738f1

Browse files
authored
Merge pull request #19 from 0xSteph/2.0-trust-spine
ptai 1.0.0 — the Trust Spine
2 parents 0d1918a + 3912cca commit 13738f1

129 files changed

Lines changed: 13907 additions & 235 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ concurrency:
4141

4242
jobs:
4343
# Dedicated job to satisfy the branch-protection rule, which requires a
44-
# status check literally named "pytest (Python 3.12)". The matrix-driven
45-
# job below covers ubuntu+macos+windows × 3.10/3.11/3.13; ubuntu+3.12 is
46-
# excluded there to avoid double-running, since this job already covers it.
44+
# status check literally named "pytest (Python 3.12)". This file runs the
45+
# full suite once on ubuntu + Python 3.12; there is no cross-OS/version
46+
# matrix here, so this is the only unguarded pytest invocation to harden.
4747
pytest:
4848
name: pytest (Python 3.12)
4949
runs-on: ubuntu-latest
@@ -63,7 +63,17 @@ jobs:
6363
6464
- name: Run tests
6565
# Engagement-lifecycle E2E is the e2e-juiceshop job's job; ignore here.
66-
run: pytest tests/ -v --tb=short --ignore=tests/test_engagement_lifecycle_e2e.py
66+
# rm -f first so a leftover report.xml from any earlier step can never
67+
# satisfy the guard if this run dies before writing its own.
68+
run: |
69+
rm -f report.xml
70+
pytest tests/ -v --tb=short --junitxml=report.xml --ignore=tests/test_engagement_lifecycle_e2e.py
71+
72+
- name: Verify pytest result is complete (guard against os._exit truncation)
73+
# The load-bearing defense: an os._exit(0) mid-run kills pytest before it
74+
# writes report.xml, so a green return code yields no report at all. The
75+
# guard fails the build on a missing/truncated report or any failure/error.
76+
run: python scripts/check_pytest_result.py report.xml
6777

6878
lint:
6979
name: lint + type check (ubuntu / py3.12)

.github/workflows/release.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,18 @@ jobs:
5151
uv pip install -e ".[dev]"
5252
5353
- name: Run tests
54+
# rm -f first so a stale report.xml can never satisfy the guard, and
55+
# emit junitxml so the guard can detect an os._exit truncation. A
56+
# green-but-truncated release gate would ship a broken package.
5457
run: |
5558
source .venv/bin/activate
56-
python -m pytest tests/ --tb=short -q
59+
rm -f report.xml
60+
python -m pytest tests/ --tb=short -q --junitxml=report.xml
61+
62+
- name: Verify pytest result is complete (guard against os._exit truncation)
63+
run: |
64+
source .venv/bin/activate
65+
python scripts/check_pytest_result.py report.xml
5766
5867
build:
5968
needs: test

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@
22

33
All notable changes to ptai are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/), versioning follows [Semantic Versioning](https://semver.org/).
44

5+
## [1.0.0] - 2026-06-25
6+
7+
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.
8+
9+
### Added
10+
11+
- **Verifier + receipt contract.** A verified verdict must name the machine oracle that earned it, enforced in code.
12+
- **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.
13+
- **Portable proof capsules and `ptai replay`**, plus multi-hop chain capsules, so a finding's proof travels and replays without trusting ptai.
14+
- **Prove-or-kill gating:** third-party scanner output (nuclei, nikto, zap) is held back until an oracle re-proves it.
15+
- **Verified-only SARIF export** (`ptai export --sarif`) with a frozen, versioned export-properties contract.
16+
- **REST path-parameter injection** in the SQLi/XSS/SSTI fuzzers, so injection in `/rest/products/<id>` style routes is caught, not just query parameters.
17+
- **Experimental CL.TE request-smuggling discovery probe**, oracle-gated.
18+
- **CI gate** (`--fail-on verified`) with a composite action, and a bundled `ptai demo`.
19+
20+
### Changed
21+
22+
- **Honest verdicts.** An oracle miss now reads as `candidate` (could not re-prove), never `refuted`; `refuted` is reserved for an oracle that can truly disprove a vulnerability.
23+
- **Impact-honest severity:** a bare out-of-band callback proves existence, not impact, so it is rated medium until impact is reproduced.
24+
25+
### Verified
26+
27+
- 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.
28+
529
## [0.17.2] - 2026-06-09
630

731
Patch release: tool-installer fixes from user feedback on issue #12 (`lukeswitz`).

README.md

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<h1>pentest-ai</h1>
66

7-
**Autonomous pentests from one command. Real tools, working PoCs, audit-ready reports.**
7+
**The pentest tool that proves its findings. No oracle, no badge.**
88

99
[![PyPI](https://img.shields.io/pypi/v/ptai?color=red&label=pypi&style=flat-square)](https://pypi.org/project/ptai/)
1010
[![Python](https://img.shields.io/badge/python-3.10%2B-red?style=flat-square)](https://pypi.org/project/ptai/)
@@ -13,29 +13,47 @@
1313
[![Stars](https://img.shields.io/github/stars/0xSteph/pentest-ai?color=red&style=flat-square)](https://github.com/0xSteph/pentest-ai/stargazers)
1414
[![Discord](https://img.shields.io/badge/discord-join-red?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/6weeTAubJw)
1515

16-
[**Website**](https://pentestai.xyz) · [**Install**](#install) · [**Docs**](docs/) · [**Benchmarks**](docs/benchmarks/juice-shop.md) · [**Agents**](https://github.com/0xSteph/pentest-ai-agents) · [**Discord**](https://discord.gg/6weeTAubJw)
16+
[**Website**](https://pentestai.xyz) · [**Install**](#install) · [**Why verification**](docs/why-verification.md) · [**Docs**](docs/) · [**Benchmarks**](docs/benchmarks/juice-shop.md) · [**Agents**](https://github.com/0xSteph/pentest-ai-agents) · [**Discord**](https://discord.gg/6weeTAubJw)
1717

1818
</div>
1919

2020
> ⚠️ **Offensive tooling, authorized testing only.** By installing you accept the [AUP](https://pentestai.xyz/aup) and [Terms](https://pentestai.xyz/terms). Full text in [Responsible use ↓](#responsible-use)
2121
22-
Point ptai at a target. It runs recon, logs in, and ties findings into multi-step attack paths. Every finding comes with a working PoC. The report writes itself.
22+
ptai is an AI-driven pentest tool that re-runs every exploit to confirm it. It runs recon, logs in, and chains findings into multi-step attack paths, but it does not ask you to trust the results. The way TruffleHog confirms a leaked secret by logging in with it, ptai confirms a web finding by re-running the exploit: a finding stays a candidate until a machine oracle reproduces it N out of N, and only then does it earn a VERIFIED badge. Third-party scanner output (nuclei, nikto, zap) is held back until an oracle re-proves it. Scanner noise is what trains teams to ignore their tools, so the report carries only what ptai could prove, each VERIFIED finding with a portable proof capsule you can replay yourself.
2323

24-
Runs on your laptop. No cloud, no telemetry.
24+
On a private 20-bug honeypot: 19/20 caught, 100% precision, zero false positives, four bug classes oracle-verified. Runs on your laptop. No cloud, no telemetry.
2525

26-
> **What's new in 0.15.1 (2026-05-16).** Three new flags for pointing ptai at real production targets without breaking the engagement rules. `intensity=safe` skips probes that mutate server state (no mass-assignment, no stored XSS, no race conditions). `respect_rate_limits=true` honors HTTP 429 and `Retry-After` instead of hammering through them. `strict_scope=true` refuses any request whose host doesn't match the engagement target, and turns off redirect-following so a 302 to attacker.com can't pull the scan off-target. All three default off - existing 0.14.x behavior is unchanged unless you opt in. There's also a working `bearer` auth flow now: POST your creds, ptai pulls the JWT out of a JSON path you configure, then attaches it to every probe. JWT-style APIs (Juice Shop, crAPI, most modern bug-bounty stacks) actually work. Measured: **63.24% catch rate (43/68 in-scope challenges)** on OWASP Juice Shop v19.2.1 with `intensity=aggressive` + `strict_scope=true`. See [CHANGELOG](CHANGELOG.md#0151--2026-05-16).
27-
>
28-
> Also in 0.14.0 (2026-05-13): 200+ wrapped security tools (wpscan, dalfox, hydra, hashcat, paramspider, ffuf, gobuster, sqlmap, ...) are reachable from Claude Code, Cursor, Codex, and any MCP client via `list_tools`, `run_tool`, `plan_tools`, `ensure_tools_installed`. **No Anthropic API key required.** Pick a tool, install missing ones in one batched prompt, run, get findings.
26+
## Prove it yourself
27+
28+
<p align="center">
29+
<img src="assets/ptai-demo.gif" alt="ptai demo: oracle-VERIFIED findings on a vulnerable app, then zero on the hardened twin" width="900">
30+
</p>
31+
32+
```bash
33+
pip install ptai && ptai demo
34+
```
35+
36+
`ptai demo` scans a bundled vulnerable app and reports `4 findings, 3 oracle-VERIFIED`, replays one live from a proof capsule (`replay 3/3`), then runs the same routes hardened and reports `0 findings`. The only thing that changed between the two runs is the fix, so the findings appear and disappear with the vulnerability, not because the tool went quiet. Two minutes, no API key, no target of your own. Re-prove any capsule yourself with `ptai replay`.
37+
38+
> **Honest numbers.** The 19/20 at 100% precision figure is one run against a private 20-bug honeypot (39 findings total), not a field false-positive rate. The oracle gate buys precision without costing recall: it removes false positives, it does not raise catch rate. "Four oracle-verified classes" means four bug classes have a concrete oracle today; more land each release. OAST proves the HTTP-callback blind tier; DNS-only callbacks are a follow-on. The honeypot harness (`tests/honeypot/`) and a clean-app zero-FP gate (`tests/cleanapp/`) ship in the repo, so the claims are reproducible rather than screenshots.
39+
40+
## What's new: the Trust Spine
41+
42+
Every VERIFIED finding now comes from a named machine oracle, never an LLM assertion, and it is enforced in code: a verdict that cannot name its oracle is rejected. This release adds:
43+
44+
- **Eight machine oracles** that re-run the exploit to confirm it: reflection, open-redirect, IDOR/BOLA, error-disclosure, MCP-exposure, SQLi (boolean and blind), and out-of-band (OAST) for blind SSRF/XXE over a self-hosted collaborator.
45+
- **Prove-or-kill gating**: third-party scanner output stays hidden until an oracle re-proves it.
46+
- **Portable proof capsules** with `ptai replay`, a live TUI that shows verdicts flip to VERIFIED on screen, and a CI gate (`--fail-on verified`) that breaks a build only on proven findings.
2947

3048
## See it run
3149

3250
<p align="center">
33-
<img src="assets/demo.gif" alt="ptai-via-Claude-Code-MCP scanning OWASP Juice Shop: 17 critical findings, 7 attack chains, 264 detection rules generated" width="900">
51+
<img src="assets/demo.gif" alt="ptai scanning OWASP Juice Shop via Claude Code over MCP, streaming findings with working PoCs back into the session" width="900">
3452
</p>
3553

3654
One prompt to Claude Code. The MCP server ran ptai's tools against the target, and Claude streamed findings back into the session. Subscription-driven, no API key.
3755

38-
The scan returned **17 critical, 53 high, 107 total findings, 7 confirmed attack chains, and 264 generated detection rules** against a stock OWASP Juice Shop instance. JWT `alg:none` accepted on 8+ protected endpoints, SQLi auth bypass on `/rest/user/login`, UNION-based SQLi on `/rest/products/search`, path-filter bypass via NUL byte, XXE disclosing `/etc/passwd`, file upload polyglot, mass assignment, password reset bypass. Each one has a working PoC.
56+
Pointed at a stock OWASP Juice Shop through Claude Code over MCP, ptai found and PoC'd the headline bugs: JWT `alg:none` accepted on 8+ protected endpoints, SQLi auth bypass on `/rest/user/login`, UNION-based SQLi on `/rest/products/search`, path-filter bypass via NUL byte, XXE disclosing `/etc/passwd`, file-upload polyglot, mass assignment, password reset bypass. Each one has a working PoC. Juice Shop is a heavily-studied target, so read the raw finding volume as breadth; the precision story, only what an oracle could prove, is the demo and honeypot above.
3957

4058
> Recording is the actual output of `claude -p` against a local OWASP Juice Shop with `pentest-ai` registered as an MCP server. Cast file in [`assets/realdemo.cast`](assets/realdemo.cast); the time-paced re-render used for the GIF is in [`assets/realdemo-paced.cast`](assets/realdemo-paced.cast). Findings are real; inter-line timing was reconstructed for watchability since `claude -p` buffers and dumps in non-interactive mode. A deterministic synthesized fallback ([`assets/demo.tape`](assets/demo.tape) + [`assets/demo.sh`](assets/demo.sh)) is kept for reproducible re-renders.
4159

action.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: "ptai scan"
2+
description: "Run ptai's deterministic web sweep and fail the build only on oracle-VERIFIED findings."
3+
author: "0xSteph"
4+
branding:
5+
icon: "shield"
6+
color: "purple"
7+
8+
inputs:
9+
target:
10+
description: "Target URL to scan (must be in scope / authorized)."
11+
required: true
12+
fail-on:
13+
description: "Build gate: 'verified' (fail only on an oracle-VERIFIED finding), 'high' (fail on any critical/high finding), or 'never'."
14+
required: false
15+
default: "verified"
16+
intensity:
17+
description: "stealth | safe | normal | aggressive."
18+
required: false
19+
default: "normal"
20+
python-version:
21+
description: "Python version to run ptai on."
22+
required: false
23+
default: "3.12"
24+
25+
runs:
26+
using: "composite"
27+
steps:
28+
- uses: actions/setup-python@v5
29+
with:
30+
python-version: ${{ inputs.python-version }}
31+
- name: Install ptai
32+
shell: bash
33+
run: pipx install ptai || pip install --user ptai
34+
- name: Scan and gate
35+
shell: bash
36+
# PENTEST_AI_AUP_ACCEPTED: you assert the target is authorized for testing.
37+
env:
38+
PENTEST_AI_AUP_ACCEPTED: "1"
39+
run: >
40+
ptai start "${{ inputs.target }}"
41+
--scope web --no-llm --ci --no-sync
42+
--intensity "${{ inputs.intensity }}"
43+
--fail-on "${{ inputs.fail-on }}"

0 commit comments

Comments
 (0)