feat(3.6.7): CVE exploitation pipeline, PoC-in-report, any-primitive chaining, --only, whitebox doctrine (#41)

Version 3.6.6 -> 3.6.7. +5 agents (430 -> 435).

CVE exploitation pipeline (agents_md/vulns)
- cve_version_fingerprint: pin exact component versions for precise CVE mapping.
- cve_research_analyst: map versions -> NVD/GHSA CVEs, judge reachability/exploitability.
- cve_poc_finder: locate/vet/adapt a public PoC, run non-destructively.
- cve_exploit_scripter: write a custom exploit to $NEUROSPLOIT_POCS when none exists.

Reproducibility
- report::pocs_section lists the run's pocs/ scripts in a "Reproduction — PoC
  scripts" section; write_all appends it to report.md. Whitebox/CVE agents told
  to write repro scripts to $NEUROSPLOIT_POCS and cite the path.

Chaining (any primitive)
- CHAIN_DOCTRINE: reduce any foothold to a primitive and pivot (upload->RCE,
  SSRF->cloud creds, IDOR->takeover, ...), reuse looted creds, reason about
  business logic. New chain_cve_to_rce_to_pivot recipe. Non-destructive guardrails
  (no data loss / DB overwrite / DoS) kept via SAFETY_DOCTRINE.

Re-test one vuln
- --only <agent> on run/whitebox/greybox sets cfg.pinned to run exactly those
  agents, skipping recon selection (implements the previously-unused pinned field).

White-box scoping
- WHITEBOX_DOCTRINE prepended to code agents: static source-only, symbolic
  file:line receipts, source->sink taint, manifest version->CVE; blocks
  hallucinated live/black-box actions.

Verified: cargo build/test (29 passed), clippy -D warnings (exit 0), agents load
(vulns 245, chains 13, total 435), --only flag present.


Claude-Session: https://claude.ai/code/session_01QDses7zTSa9YF7pPRjphvh

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joas A Santos
2026-08-04 10:44:18 -03:00
committed by GitHub
co-authored by Claude Fable 5
parent 76b56898d1
commit cb19e2194d
14 changed files with 423 additions and 40 deletions
+28 -12
View File
@@ -1,4 +1,4 @@
<h1 align="center">🧠 NeuroSploit v3.6.6</h1> <h1 align="center">🧠 NeuroSploit v3.6.7</h1>
<p align="center"> <p align="center">
<a href="https://trendshift.io/repositories/22624?utm_source=trendshift-badge&amp;utm_medium=badge&amp;utm_campaign=badge-trendshift-22624" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/22624/daily?language=Python" alt="JoasASantos%2FNeuroSploit | Trendshift" width="250" height="55"/></a> <a href="https://trendshift.io/repositories/22624?utm_source=trendshift-badge&amp;utm_medium=badge&amp;utm_campaign=badge-trendshift-22624" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/22624/daily?language=Python" alt="JoasASantos%2FNeuroSploit | Trendshift" width="250" height="55"/></a>
@@ -12,10 +12,10 @@
</p> </p>
<p align="center"> <p align="center">
<img src="https://img.shields.io/badge/Version-3.6.6-blue?style=flat-square"> <img src="https://img.shields.io/badge/Version-3.6.7-blue?style=flat-square">
<img src="https://img.shields.io/badge/Harness-Rust%20%7C%20tokio-e6b673?style=flat-square"> <img src="https://img.shields.io/badge/Harness-Rust%20%7C%20tokio-e6b673?style=flat-square">
<img src="https://img.shields.io/badge/License-MIT-green?style=flat-square"> <img src="https://img.shields.io/badge/License-MIT-green?style=flat-square">
<img src="https://img.shields.io/badge/MD%20Agents-430-red?style=flat-square"> <img src="https://img.shields.io/badge/MD%20Agents-435-red?style=flat-square">
<img src="https://img.shields.io/badge/Models-16%20providers-success?style=flat-square"> <img src="https://img.shields.io/badge/Models-16%20providers-success?style=flat-square">
<img src="https://img.shields.io/badge/Modes-Black%20%7C%20White%20%7C%20Grey%20%7C%20Host%20%7C%20AI-9cf?style=flat-square"> <img src="https://img.shields.io/badge/Modes-Black%20%7C%20White%20%7C%20Grey%20%7C%20Host%20%7C%20AI-9cf?style=flat-square">
<img src="https://img.shields.io/badge/Auth-API%20key%20%7C%20Subscription-orange?style=flat-square"> <img src="https://img.shields.io/badge/Auth-API%20key%20%7C%20Subscription-orange?style=flat-square">
@@ -36,7 +36,7 @@ LLMs** — via **API key** or local **subscription** (Claude Code / Codex / Gemi
Grok) — recons the target, **intelligently selects only the agents that match the Grok) — recons the target, **intelligently selects only the agents that match the
discovered surface**, runs them in parallel, **chains** findings into deeper discovered surface**, runs them in parallel, **chains** findings into deeper
impact, and **validates every claim by cross-model voting + tool-receipt impact, and **validates every claim by cross-model voting + tool-receipt
grounding** before reporting. It ships **430 markdown agents** and a **Mission grounding** before reporting. It ships **435 markdown agents** and a **Mission
Control TUI**. Control TUI**.
### Engagement modes ### Engagement modes
@@ -69,9 +69,14 @@ Control TUI**.
flags, CORS reflection, tech fingerprint, linked JS, 404 baseline, high-signal flags, CORS reflection, tech fingerprint, linked JS, 404 baseline, high-signal
paths) and feeds those observed facts into recon, so agent selection and paths) and feeds those observed facts into recon, so agent selection and
exploitation decisions are grounded in evidence — not the model's guess. exploitation decisions are grounded in evidence — not the model's guess.
- 🔗 **Attack chaining** 12 multi-stage chain agents (SQLi→RCE→LPE, SSRF→AWS - 🔗 **Attack chaining — any primitive pivots.** 13 multi-stage chain agents
creds, upload→LFI→RCE→LPE, default-creds→domain, …); each stage proven before (SQLi→RCE→LPE, SSRF→cloud creds, upload→LFI→RCE→LPE, CVE→RCE→pivot, …) **plus a
advancing. chaining doctrine** that turns *any* confirmed foothold into the next step:
reduce it to a primitive (exec / read / write / request-forgery / identity /
secret) and pivot — file-upload→RCE, SSRF→metadata creds, IDOR→takeover — reusing
looted creds and reasoning about **business logic** (payment/tenancy/workflow
abuse). Each stage proven; strictly non-destructive (no data loss, no DB
overwrite, no DoS).
- ☁️ **Cloud testing** — AWS / GCP / Azure agents that drive the provider CLIs - ☁️ **Cloud testing** — AWS / GCP / Azure agents that drive the provider CLIs
(`aws`/`gcloud`/`az`). Connect via `creds.yaml`: AWS keys, a Google (`aws`/`gcloud`/`az`). Connect via `creds.yaml`: AWS keys, a Google
service-account JSON, or an Azure service principal — see service-account JSON, or an Azure service principal — see
@@ -84,12 +89,23 @@ Control TUI**.
attacker→**LLM-judge** loop (baseline refusal → technique → verdict) and proves attacker→**LLM-judge** loop (baseline refusal → technique → verdict) and proves
the bypass with a **benign, redacted** receipt. Maps to OWASP LLM Top 10 (2025), the bypass with a **benign, redacted** receipt. Maps to OWASP LLM Top 10 (2025),
MCP threats & OWASP AI Exchange; Skill/plugin & **n8n** files audited white-box. MCP threats & OWASP AI Exchange; Skill/plugin & **n8n** files audited white-box.
- 🧰 **Misconfig & CVE hunting, safely**dedicated agents for absurd - 🧰 **Misconfig & CVE hunting → exploitation, safely**a full CVE pipeline:
misconfigs (exposed `.git`/`.env`, debug/actuator, default creds, dashboards, **version fingerprint** (pin exact versions) → **research analyst** (map to
CORS), a **CVE Hunter** (smart, targeted `nuclei`), a **PoC Developer** (writes NVD/GHSA CVEs, judge reachability) → **PoC finder** (locate/vet/adapt a public
reproducible scripts to the run's `pocs/`), and **rate-limit** testing — all PoC) → **exploit scripter** (write a custom exploit when none exists). Every PoC
under a strict **data-safety/PII guardrail** (no destructive or state-changing is written to the run's **`pocs/` folder and referenced in the report** so
findings are reproducible. Plus absurd-misconfig agents (exposed `.git`/`.env`,
debug/actuator, default creds, dashboards, CORS) and rate-limit testing — all
under a strict **data-safety/PII guardrail** (no destructive/state-changing
actions; PII proven with a masked sample, never dumped). actions; PII proven with a masked sample, never dumped).
- 🎯 **Re-test one vulnerability**`--only <agent>` (repeatable /
comma-separated) runs exactly the agent(s) you name and skips recon-based
selection — re-test a single finding fast. Works on `run` / `whitebox` /
`greybox`; `neurosploit agents` lists the names.
- 🔬 **White-box stays white-box** — code agents run under a static-review
doctrine (symbolic `file:line` receipts, source-to-sink taint tracing, manifest
version→CVE) that forbids hallucinated live/black-box network actions, and can
emit a repro PoC to `pocs/`.
- 🗣️ **Natural-language REPL** — in the interactive session, just describe what - 🗣️ **Natural-language REPL** — in the interactive session, just describe what
you want, in any language: *"testa https://loja.com com opus, foco em SQLi, you want, in any language: *"testa https://loja.com com opus, foco em SQLi,
fora de escopo /admin, roda"*. A hybrid parser sets target/models/focus/ fora de escopo /admin, roda"*. A hybrid parser sets target/models/focus/
+34
View File
@@ -1,3 +1,37 @@
# NeuroSploit v3.6.7 — Release Notes
**Release Date:** August 2026
**Codename:** Chain & Exploit
**License:** MIT
**Credits:** Joas A Santos & Red Team Leaders
## Highlights
- **CVE exploitation pipeline — 4 new agents.** `cve_version_fingerprint` (pin
exact versions) → `cve_research_analyst` (map to NVD/GHSA, judge reachability) →
`cve_poc_finder` (locate/vet/adapt a public PoC) → `cve_exploit_scripter` (write
a custom exploit when none exists). Focus: actually exploiting vulns that have
CVEs, not just flagging versions.
- **PoCs land in the run's `pocs/` folder and are listed in the report.** Every
agent writes runnable proofs to `$NEUROSPLOIT_POCS`; the report gains a
**"Reproduction — PoC scripts"** section so findings replay end-to-end.
- **Chaining for any primitive.** New `CHAIN_DOCTRINE` + a `chain_cve_to_rce_to_pivot`
recipe turn any confirmed foothold into the next step (upload→RCE, SSRF→cloud
creds, IDOR→takeover, CVE→RCE→pivot), reusing looted creds and reasoning about
**business logic** — strictly non-destructive (no data loss / DB overwrite / DoS).
- **`--only <agent>` — re-test a single vulnerability.** Runs exactly the named
agent(s), skipping recon selection. On `run` / `whitebox` / `greybox`; repeatable
or comma/semicolon-separated. (Implements the previously-dead `pinned` allowlist.)
- **White-box stays white-box.** A `WHITEBOX_DOCTRINE` keeps code agents in static
source-review mode (symbolic `file:line` receipts, source→sink taint, manifest
version→CVE) and blocks hallucinated live/black-box network actions; agents can
emit a repro PoC.
- **435 markdown agents** (was 430).
**Full changelog:** https://github.com/JoasASantos/NeuroSploit/compare/v3.6.6...v3.6.7
---
# NeuroSploit v3.6.6 — Release Notes # NeuroSploit v3.6.6 — Release Notes
**Release Date:** August 2026 **Release Date:** August 2026
@@ -0,0 +1,42 @@
# Known-CVE → RCE → Pivot Chain Agent
## User Prompt
You are executing a multi-stage ATTACK CHAIN against **{target}**: a known CVE in a fingerprinted component → code execution → post-exploitation pivot.
**Recon Context / prior findings:**
{recon_json}
**GOAL:** Turn a version-matched, reachable CVE into demonstrated RCE/access, then pivot — safely.
**CHAIN — advance stage by stage; PROVE every stage with raw tool output before advancing:**
### Stage 1. Pin the target CVE
- From the component+version inventory, pick the highest-impact reachable CVE (unauth RCE/SQLi/SSRF/deserialization first). Confirm preconditions are met
### Stage 2. Obtain a safe PoC
- Reuse a vetted public PoC or write one to `$NEUROSPLOIT_POCS`. STRIP any destructive payload; use a benign marker (`id`, unique echo, OOB callback)
### Stage 3. Execute & confirm
- Run it non-destructively against the authorized target; capture output proving exploitation (marker/OOB/leak)
### Stage 4. Pivot
- From the foothold: loot creds/keys/config/source, reuse them, escalate privileges, reach internal services/cloud metadata, or expand to adjacent hosts — each step proven, none destructive
### 5. Report Format
Report the chain as ONE finding (plus per-stage evidence):
```
FINDING:
- Title: [CVE-id] → RCE → Pivot Chain
- Severity: Critical
- CWE: CWE-1395
- Endpoint: [entry point]
- Vector: [full chain, stage by stage]
- Payload: [PoC path in $NEUROSPLOIT_POCS + key commands per stage]
- Evidence: [raw output proving EACH stage]
- Impact: [demonstrated compromise + what the pivot reached]
- Remediation: Patch to the fixed version; segment/limit blast radius; rotate exposed secrets
- chains_from: [ids of the prerequisite findings this builds on]
```
## System Prompt
You are an exploit-chaining specialist for known CVEs. Only advance a stage after the previous one is proven with a real tool receipt — never assume. Save any PoC to $NEUROSPLOIT_POCS and cite it. If a stage can't be proven, stop and report the chain up to the last proven stage. AUTHORIZED engagement. DATA SAFETY: benign proof only — never destroy/overwrite/encrypt/mass-exfiltrate data, drop databases, or DoS; mask PII; reuse looted creds only against the authorized target. Credits: Joas A Santos & Red Team Leaders.
+39
View File
@@ -0,0 +1,39 @@
# CVE Exploit Scripter Agent
## User Prompt
You are testing **{target}**: when no clean public PoC exists for a confirmed-candidate CVE, WRITE a custom exploitation script and prove it safely.
**Recon Context:**
{recon_json}
**METHODOLOGY:**
### 1. Decide
- Use this when the CVE is reachable but there's no usable public PoC, or the public one is destructive/unsuitable and must be rebuilt safely
### 2. Build from the advisory
- From the CVE/advisory and the component's behaviour, derive the exact request/steps that trigger the bug. Write a runnable script (python/bash/curl) to `$NEUROSPLOIT_POCS` with a header comment: target, CVE id, what it proves, usage
### 3. Make it safe by construction
- Use a BENIGN proof: echo a unique marker, trigger an OOB DNS/HTTP callback, read a non-sensitive indicator, or run `id`/version — never a payload that deletes/overwrites data, drops the DB, or DoSes. Idempotent and minimal
### 4. Run & confirm
- Execute against the authorized target; capture raw output proving exploitation. Keep the script in `$NEUROSPLOIT_POCS` and reference its path so the finding is fully reproducible
### 5. Report Format
For each CONFIRMED finding:
```
FINDING:
- Title: [CVE-id] exploited via custom script on [component]
- Severity: [CVSS/impact]
- CWE: [CVE's CWE]
- Endpoint: [full URL/resource]
- Vector: [technique built from the advisory]
- Payload: [script path in $NEUROSPLOIT_POCS + exact invocation]
- Evidence: [raw output proving exploitation - marker/OOB/leak]
- Impact: [demonstrated impact - up to full compromise]
- Remediation: Upgrade to the fixed version; apply advisory mitigations
```
## System Prompt
You are a custom-exploit developer for known CVEs. AUTHORIZED engagement. Build the exploit from the advisory and PROVE it with a benign, non-destructive marker only. ALWAYS write the script to $NEUROSPLOIT_POCS with a header comment and cite its path — reproducibility is mandatory. Report ONLY what a real tool receipt proves; if you cannot reach a working benign PoC, report the CVE as a reachable exposure, not a confirmed exploit. DATA SAFETY: never destroy/overwrite/encrypt/mass-exfiltrate data or change state beyond the minimal proof; mask PII; no destructive/DoS. Credits: Joas A Santos and Red Team Leaders.
+39
View File
@@ -0,0 +1,39 @@
# CVE PoC Finder Agent
## User Prompt
You are testing **{target}**: find, vet and run a PUBLIC proof-of-concept for a confirmed-candidate CVE, safely.
**Recon Context:**
{recon_json}
**METHODOLOGY:**
### 1. Locate a PoC
- Search `searchsploit`/Exploit-DB, GitHub (CVE id + component), NVD references, `nuclei` templates (`-t` for the CVE/tech tags — targeted, not a blind full scan), packet-storm, vendor advisories
### 2. Vet before you run
- READ the PoC first. Reject/neutralise anything destructive (drops tables, wipes files, ransomware-style, mass requests/DoS, backdoors). Understand exactly what it does and what it proves
### 3. Adapt & stage
- `git clone`/download into the run's `$NEUROSPLOIT_POCS` directory. Parameterise it for THIS target (URL, port, path, auth). Replace any harmful payload with a benign marker (`id`, unique echo string, OOB DNS/HTTP callback)
### 4. Run & confirm
- Execute non-destructively against the authorized target; capture raw output that proves the CVE (marker echoed, OOB hit, expected leak). Keep the exact script in `$NEUROSPLOIT_POCS` so the finding is reproducible
### 5. Report Format
For each CONFIRMED finding:
```
FINDING:
- Title: [CVE-id] exploited via public PoC on [component]
- Severity: [CVSS/impact]
- CWE: [CVE's CWE]
- Endpoint: [full URL/resource]
- Vector: [technique + PoC source]
- Payload: [PoC path in $NEUROSPLOIT_POCS + exact invocation]
- Evidence: [raw output proving exploitation - marker/OOB/leak]
- Impact: [demonstrated impact]
- Remediation: Upgrade to the fixed version; apply advisory mitigations
```
## System Prompt
You are a public-PoC exploitation specialist. AUTHORIZED engagement. ALWAYS read a third-party PoC before running it and STRIP any destructive/DoS/backdoor behaviour — swap harmful payloads for benign markers. Save the adapted PoC to $NEUROSPLOIT_POCS and cite its path so the result is reproducible. Report ONLY what a real tool receipt proves. DATA SAFETY: never modify/delete/overwrite/exfiltrate data or change state beyond the minimal benign proof; mask PII; no destructive/DoS. Credits: Joas A Santos and Red Team Leaders.
+40
View File
@@ -0,0 +1,40 @@
# CVE Research Analyst Agent
## User Prompt
You are testing **{target}**: research known CVEs for the fingerprinted components and decide which are actually exploitable HERE.
**Recon Context:**
{recon_json}
**METHODOLOGY:**
### 1. Map versions → CVEs
- For each component+version, enumerate CVEs (NVD, GitHub Security Advisories/GHSA, vendor advisories, distro trackers, `searchsploit`). Record CVE id, CVSS, affected/fixed versions, vulnerability class
### 2. Assess exploitability HERE
- Filter to CVEs whose preconditions the target actually meets (reachable endpoint/feature, required config/module enabled, auth level you can reach). Prioritise unauth **RCE / SQLi / auth-bypass / SSRF / deserialization**
- Note whether a public PoC/exploit exists (feeds `cve_poc_finder`) or a custom script is needed (feeds `cve_exploit_scripter`)
### 3. Rank
- Order candidates by (impact × exploitability × reachability). Discard theoretical/unreachable CVEs
### 4. Confirm safely
- Where a benign version/behaviour check can confirm the CVE is present (without exploiting), run it and cite the output
### 5. Report Format
For each candidate (Confirmed if a benign check proves presence, else a version-match lead):
```
FINDING:
- Title: [CVE-id] in [component] [version]
- Severity: [map from CVSS/impact]
- CWE: [CVE's CWE, e.g. CWE-1395]
- Endpoint: [reachable resource]
- Vector: [class + preconditions met]
- Payload: [benign confirmation check, if run]
- Evidence: [raw output / advisory + version match]
- Impact: [what the CVE yields — up to full compromise]
- Remediation: Upgrade to [fixed version]; apply advisory mitigations
```
## System Prompt
You are a CVE research analyst. AUTHORIZED engagement. Distinguish "version matches a CVE" (lead) from "CVE is present and reachable here" (confirmed by a benign check) — never inflate a version match into a confirmed exploit. Cite the advisory and the exact affected/fixed version. Hand exploitation to the PoC finder / exploit scripter. DATA SAFETY: read-only research + benign checks only; no state change; mask PII; no destructive/DoS. Credits: Joas A Santos and Red Team Leaders.
@@ -0,0 +1,37 @@
# CVE Version Fingerprint Agent
## User Prompt
You are testing **{target}** to pin the EXACT version of every component so known CVEs can be mapped precisely.
**Recon Context:**
{recon_json}
**METHODOLOGY:**
### 1. Fingerprint every layer
- Server/proxy (`Server`, `Via`, `X-Powered-By`), app framework, CMS + plugins/themes, JS libraries (from `<script>` src, source maps, `/package.json`, bundle comments), API framework, TLS stack
- Pull versions from: response headers, default/readme/changelog files (`/readme.html`, `/CHANGELOG.md`, `/*.txt`), favicon hash, static asset hashes, error pages, `/.well-known`, `robots.txt`, JS build manifests
### 2. Disambiguate
- When only a range is visible, narrow it: compare asset hashes/behaviour between adjacent releases, check feature/endpoint presence, read embedded build ids/commit hashes
### 3. Build the inventory
- Produce a component → EXACT version table; mark confidence (exact vs range). This inventory feeds `cve_research_analyst` / `cve_hunter`
### 4. Report Format
For each identified component (report as a finding only when the version has known CVEs; otherwise fold into the inventory):
```
FINDING:
- Title: Version Fingerprint - [component] [version]
- Severity: Info
- CWE: CWE-200
- Endpoint: [source header/file/asset]
- Vector: [how the version was determined]
- Payload: [exact request/hash used]
- Evidence: [raw header/file snippet proving the version]
- Impact: Enables precise CVE mapping and targeted exploitation
- Remediation: Suppress version banners; keep components patched
```
## System Prompt
You are a software version-fingerprinting specialist. AUTHORIZED engagement. Report ONLY versions you proved from a real receipt (raw header/file/hash) — never guess a version. Prefer EXACT versions; state confidence when only a range is provable. Your inventory is the input to CVE mapping, so accuracy matters more than volume. DATA SAFETY: read-only; no state change; mask any PII. No destructive/DoS actions. Credits: Joas A Santos and Red Team Leaders.
+2 -2
View File
@@ -871,7 +871,7 @@ dependencies = [
[[package]] [[package]]
name = "neurosploit" name = "neurosploit"
version = "3.6.6" version = "3.6.7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -888,7 +888,7 @@ dependencies = [
[[package]] [[package]]
name = "neurosploit-harness" name = "neurosploit-harness"
version = "3.6.6" version = "3.6.7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures", "futures",
+1 -1
View File
@@ -3,7 +3,7 @@ members = ["crates/harness", "app"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "3.6.6" version = "3.6.7"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/JoasASantos/NeuroSploit" repository = "https://github.com/JoasASantos/NeuroSploit"
+36 -7
View File
@@ -1,4 +1,4 @@
//! NeuroSploit v3.6.6 — interactive harness + CLI (`run` / `whitebox` / `agents` / `models`). //! NeuroSploit v3.6.7 — interactive harness + CLI (`run` / `whitebox` / `agents` / `models`).
mod repl; mod repl;
mod tui; mod tui;
@@ -11,8 +11,8 @@ use std::path::{Path, PathBuf};
#[command( #[command(
name = "neurosploit", name = "neurosploit",
version, version,
about = "NeuroSploit v3.6.6 — multi-model autonomous pentest harness", about = "NeuroSploit v3.6.7 — multi-model autonomous pentest harness",
long_about = "NeuroSploit v3.6.6 — a Rust multi-model harness that drives a pool of LLMs \ long_about = "NeuroSploit v3.6.7 — a Rust multi-model harness that drives a pool of LLMs \
(API key or local subscription: Claude/Codex/Gemini/Grok) to autonomously test a target. \ (API key or local subscription: Claude/Codex/Gemini/Grok) to autonomously test a target. \
After recon it INTELLIGENTLY selects only the agents matching the discovered surface, runs \ After recon it INTELLIGENTLY selects only the agents matching the discovered surface, runs \
them in parallel, then validates every finding by cross-model voting before reporting.\n\n\ them in parallel, then validates every finding by cross-model voting before reporting.\n\n\
@@ -77,6 +77,11 @@ enum Cmd {
/// Open a Jira card per finding (needs the jira integration enabled). /// Open a Jira card per finding (needs the jira integration enabled).
#[arg(long)] #[arg(long)]
jira: bool, jira: bool,
/// Re-test ONLY these agent(s), skipping recon-based selection — repeatable
/// or comma/semicolon-separated (e.g. `--only sqli --only cve_hunter`).
/// Run `neurosploit agents` for the names.
#[arg(long = "only")]
only: Vec<String>,
/// Verbose: log each agent as it launches, recon, and votes. /// Verbose: log each agent as it launches, recon, and votes.
#[arg(short, long)] #[arg(short, long)]
verbose: bool, verbose: bool,
@@ -105,6 +110,9 @@ enum Cmd {
/// Open a Jira card per finding (needs the jira integration enabled). /// Open a Jira card per finding (needs the jira integration enabled).
#[arg(long)] #[arg(long)]
jira: bool, jira: bool,
/// Re-test ONLY these code agent(s) — repeatable or comma/semicolon-separated.
#[arg(long = "only")]
only: Vec<String>,
#[arg(short, long)] #[arg(short, long)]
verbose: bool, verbose: bool,
}, },
@@ -139,6 +147,9 @@ enum Cmd {
subscription: bool, subscription: bool,
#[arg(long)] #[arg(long)]
mcp: bool, mcp: bool,
/// Re-test ONLY these agent(s) — repeatable or comma/semicolon-separated.
#[arg(long = "only")]
only: Vec<String>,
#[arg(short, long)] #[arg(short, long)]
verbose: bool, verbose: bool,
}, },
@@ -379,7 +390,7 @@ async fn main() -> anyhow::Result<()> {
} }
} }
} }
Cmd::Run { url, models, max_agents, vote_n, chain_depth, recon, offline, subscription, mcp, creds, focus, objective, out_of_scope, jira, verbose } => { Cmd::Run { url, models, max_agents, vote_n, chain_depth, recon, offline, subscription, mcp, creds, focus, objective, out_of_scope, jira, only, verbose } => {
let url = if url.starts_with("http") { url } else { format!("https://{url}") }; let url = if url.starts_with("http") { url } else { format!("https://{url}") };
let mut cfg = RunConfig::new(&url); let mut cfg = RunConfig::new(&url);
cfg.max_agents = max_agents; cfg.max_agents = max_agents;
@@ -392,6 +403,7 @@ async fn main() -> anyhow::Result<()> {
cfg.instructions = focus; cfg.instructions = focus;
cfg.objective = objective; cfg.objective = objective;
cfg.out_of_scope = out_of_scope; cfg.out_of_scope = out_of_scope;
cfg.pinned = parse_only(&only);
if !models.is_empty() { if !models.is_empty() {
cfg.models = models; cfg.models = models;
} }
@@ -401,7 +413,7 @@ async fn main() -> anyhow::Result<()> {
let ig = harness::integrations::Integrations::load(&repl::proj_dir()); let ig = harness::integrations::Integrations::load(&repl::proj_dir());
post_integrations(&ig, &url, &out, jira, false, None).await; post_integrations(&ig, &url, &out, jira, false, None).await;
} }
Cmd::Whitebox { path, models, max_agents, vote_n, chain_depth, recon, offline, subscription, jira, verbose } => { Cmd::Whitebox { path, models, max_agents, vote_n, chain_depth, recon, offline, subscription, jira, only, verbose } => {
let path = resolve_source(&base, &path)?; // local path OR github URL/owner/repo let path = resolve_source(&base, &path)?; // local path OR github URL/owner/repo
let mut cfg = RunConfig::new(&path); let mut cfg = RunConfig::new(&path);
cfg.max_agents = max_agents; cfg.max_agents = max_agents;
@@ -411,6 +423,7 @@ async fn main() -> anyhow::Result<()> {
cfg.offline = offline; cfg.offline = offline;
cfg.subscription = subscription; cfg.subscription = subscription;
cfg.verbose = verbose; cfg.verbose = verbose;
cfg.pinned = parse_only(&only);
if !models.is_empty() { if !models.is_empty() {
cfg.models = models; cfg.models = models;
} }
@@ -419,7 +432,7 @@ async fn main() -> anyhow::Result<()> {
let ig = harness::integrations::Integrations::load(&repl::proj_dir()); let ig = harness::integrations::Integrations::load(&repl::proj_dir());
post_integrations(&ig, &path, &out, jira, false, None).await; post_integrations(&ig, &path, &out, jira, false, None).await;
} }
Cmd::Greybox { repo, url, models, creds, focus, max_agents, vote_n, chain_depth, recon, offline, subscription, mcp, verbose } => { Cmd::Greybox { repo, url, models, creds, focus, max_agents, vote_n, chain_depth, recon, offline, subscription, mcp, only, verbose } => {
let repo = resolve_source(&base, &repo)?; // local path OR github URL/owner/repo let repo = resolve_source(&base, &repo)?; // local path OR github URL/owner/repo
let url = if url.starts_with("http") { url } else { format!("https://{url}") }; let url = if url.starts_with("http") { url } else { format!("https://{url}") };
let mut cfg = RunConfig::new(&url); let mut cfg = RunConfig::new(&url);
@@ -432,6 +445,7 @@ async fn main() -> anyhow::Result<()> {
cfg.subscription = subscription; cfg.subscription = subscription;
cfg.verbose = verbose; cfg.verbose = verbose;
cfg.instructions = focus; cfg.instructions = focus;
cfg.pinned = parse_only(&only);
if !models.is_empty() { if !models.is_empty() {
cfg.models = models; cfg.models = models;
} }
@@ -751,7 +765,7 @@ pub(crate) fn spawn_engagement(base: &Path, mut cfg: RunConfig, mcp: bool, mode:
println!(" │ ua : {ua}"); println!(" │ ua : {ua}");
write_status(&workdir, "running", &format!("\"target\":{:?}", cfg.target)); write_status(&workdir, "running", &format!("\"target\":{:?}", cfg.target));
println!(" ┌─ NeuroSploit v3.6.6 · by Joas A Santos & Red Team Leaders"); println!(" ┌─ NeuroSploit v3.6.7 · by Joas A Santos & Red Team Leaders");
println!(" │ run id : {run_id}"); println!(" │ run id : {run_id}");
println!(" │ target : {}", cfg.target); println!(" │ target : {}", cfg.target);
println!(" │ models : {}", cfg.models.join(", ")); println!(" │ models : {}", cfg.models.join(", "));
@@ -896,6 +910,21 @@ pub(crate) fn print_findings(out: &RunOutput) {
} }
} }
/// Parse repeated `--only` values into a clean agent allowlist. Accepts repeats
/// and comma/semicolon-separated lists (`--only sqli,xss` == `--only sqli --only xss`).
fn parse_only(vals: &[String]) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for v in vals {
for part in v.split([',', ';']) {
let name = part.trim();
if !name.is_empty() && !out.iter().any(|x| x == name) {
out.push(name.to_string());
}
}
}
out
}
fn sanitize(s: &str) -> String { fn sanitize(s: &str) -> String {
let s = s.replace("https://", "").replace("http://", ""); let s = s.replace("https://", "").replace("http://", "");
let mut o: String = s.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect(); let mut o: String = s.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect();
+2 -2
View File
@@ -1,4 +1,4 @@
//! NeuroSploit v3.6.6 — interactive session (Claude-Code / Codex / Cursor-CLI style). //! NeuroSploit v3.6.7 — interactive session (Claude-Code / Codex / Cursor-CLI style).
//! //!
//! Launched when `neurosploit` runs with no subcommand. A persistent REPL with //! Launched when `neurosploit` runs with no subcommand. A persistent REPL with
//! real line editing (arrow-key history recall, Ctrl-A/E/K, paste), model //! real line editing (arrow-key history recall, Ctrl-A/E/K, paste), model
@@ -370,7 +370,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
let backends = harness::installed_cli_backends(); let backends = harness::installed_cli_backends();
println!("\x1b[1m"); println!("\x1b[1m");
println!(" ███╗ ██╗███████╗██╗ ██╗██████╗ ██████╗"); println!(" ███╗ ██╗███████╗██╗ ██╗██████╗ ██████╗");
println!(" ████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗ NeuroSploit v3.6.6"); println!(" ████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗ NeuroSploit v3.6.7");
println!(" ██╔██╗ ██║█████╗ ██║ ██║██████╔╝██║ ██║ interactive harness"); println!(" ██╔██╗ ██║█████╗ ██║ ██║██████╔╝██║ ██║ interactive harness");
println!(" ██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗██║ ██║ by Joas A Santos"); println!(" ██║╚██╗██║██╔══╝ ██║ ██║██╔══██╗██║ ██║ by Joas A Santos");
println!(" ██║ ╚████║███████╗╚██████╔╝██║ ██║╚██████╔╝ & Red Team Leaders"); println!(" ██║ ╚████║███████╗╚██████╔╝██║ ██║╚██████╔╝ & Red Team Leaders");
+1 -1
View File
@@ -1,4 +1,4 @@
//! NeuroSploit v3.6.6 — TUI "Mission Control" mode. //! NeuroSploit v3.6.7 — TUI "Mission Control" mode.
//! //!
//! Concurrent panels that update live while the engagement runs in the //! Concurrent panels that update live while the engagement runs in the
//! background, with a composer input that stays active during execution: //! background, with a composer input that stays active during execution:
+81 -14
View File
@@ -313,6 +313,37 @@ const DECISION_DOCTRINE: &str = "DECIDE WHERE TO ATTACK (analyse, then act):\n\
- Build PoCs when needed: for issues that need an artifact to prove (clickjacking an HTML page that frames the target; CSRF an auto-submitting HTML form; a multi-step or timing exploit a script), WRITE the PoC to the run's PoC dir, run/validate it, and cite the file in the evidence.\n\ - Build PoCs when needed: for issues that need an artifact to prove (clickjacking an HTML page that frames the target; CSRF an auto-submitting HTML form; a multi-step or timing exploit a script), WRITE the PoC to the run's PoC dir, run/validate it, and cite the file in the evidence.\n\
- Test control BYPASSES: when something returns 401/403/redirect or is 'blocked', try to bypass it (verb tampering, path/case/encoding normalization, X-Original-URL / X-Rewrite-URL / X-Forwarded-* headers, missing-vs-invalid token, direct object/API access) and confirm the bypass with the two requests.\n\n"; - Test control BYPASSES: when something returns 401/403/redirect or is 'blocked', try to bypass it (verb tampering, path/case/encoding normalization, X-Original-URL / X-Rewrite-URL / X-Forwarded-* headers, missing-vs-invalid token, direct object/API access) and confirm the bypass with the two requests.\n\n";
/// CHAIN doctrine: turn ANY foothold into the next step. A primitive→next-step
/// playbook (not an exhaustive script) so the agent always has a concrete pivot
/// to reason about, plus a push to chain toward BUSINESS impact — all under the
/// non-destructive SAFETY_DOCTRINE (prove RCE/access with a benign marker, never
/// harm data or state).
const CHAIN_DOCTRINE: &str = "CHAIN THE FOOTHOLD (pivot to deeper, provable impact — any primitive can chain):\n\
- Think in primitives, not labels: reduce the foothold to what it GIVES you (code exec, file read, file write, request forgery, a trusted identity, a leaked secret, arbitrary object access) and pick the next step from that.\n\
- Pivot playbook (attempt the fitting ones, prove each with a benign receipt):\n\
· File upload / write RCE: upload a webshell/handler to an executable path or poison a config/`.htaccess`/cron/serialized file; prove with a benign marker (`id`, unique echo, OOB DNS), not damage.\n\
· SSRF cloud/host takeover: hit `169.254.169.254` (IMDSv1/v2), GCP/Azure metadata, internal admin/actuator, `file://`/`gopher://`; loot temp creds/tokens and REUSE them.\n\
· SQLi RCE/LPE: stacked queries, `INTO OUTFILE`/`COPY TO`, UDF, `xp_cmdshell`, read secrets/creds; then reuse creds to log in and escalate.\n\
· LFI/path traversal RCE: log/session/wrapper poisoning, `/proc/self/environ`, read source & secrets; combine with an upload for exec.\n\
· XXE SSRF/file read creds; deserialization/SSTI RCE via a gadget/template sink; prove exec with a marker.\n\
· IDOR/BOLA/mass-assignment account/tenant takeover or role escalation (`role=admin`); open-redirect/XSS/CORS token/session theft ATO.\n\
· Exposed `.git`/backup/`.env`/secrets reconstruct source & keys auth to internal APIs, cloud, DB; default/leaked creds domain/service compromise.\n\
- Reuse loot relentlessly: every credential/JWT/cookie/API key/host you obtain is input to the next step carry it forward across modules and try it everywhere it might be accepted.\n\
- Understand the BUSINESS & LOGIC: reason about what the app is FOR (payments, orders, tenancy, KYC, entitlements) and chain toward business impact payment/price/coupon abuse, cross-tenant data access, entitlement/limit bypass, workflow/state-machine skips (skip approval/verification steps), race conditions on balance/stock. These compound: each finding updates your model of the app for the next probe.\n\
- Stop at proof: demonstrate the impact with the SMALLEST safe step and report the CHAIN end-to-end; never destroy, overwrite, encrypt, mass-exfiltrate, or DoS to 'prove' it.\n\n";
/// WHITEBOX doctrine: this is a STATIC source review — keep the agent in the code,
/// not on the wire. Prevents whitebox runs from hallucinating black-box network
/// actions (curl/nuclei/live requests) they cannot perform here, and pushes for
/// a symbolic `file:line` receipt plus a runnable repro PoC where it adds value.
const WHITEBOX_DOCTRINE: &str = "MODE: WHITE-BOX STATIC SOURCE REVIEW. You are reading source code, NOT a live target.\n\
- Source-only: reason strictly about the provided code. Do NOT curl, run nuclei, browse, or claim any live/HTTP/network result there is no running app here. Any \"I sent a request / got a response\" claim is a hallucination and will be rejected.\n\
- Symbolic receipt: EVERY finding's evidence is a `file:line` citation plus the exact vulnerable code quoted verbatim. The code citation IS the proof. No `file:line` + code quote do not report it.\n\
- Trace, don't guess: follow tainted input from its SOURCE (request param, env, deserialization, file) to a dangerous SINK (SQL/exec/eval/template/path/SSRF/deserialize). Report only when source reaches sink without effective sanitization; note the path (`entry sink`).\n\
- Version CVE (static): read dependency manifests (package.json, requirements.txt, go.mod, pom.xml, Gemfile.lock, Cargo.lock) and pin exact versions; map to known CVEs and cite the manifest line. Flag reachable, exploitable ones over merely-outdated ones.\n\
- Repro PoC (optional but valued): when a finding warrants it, WRITE a proof/repro script to $NEUROSPLOIT_POCS e.g. the exact malicious input + the request/CLI call that would trigger the sink, or a unit-style harness exercising the vulnerable function with a header comment (file:line it proves, how to run). Cite the PoC path in the evidence. Mark clearly that it demonstrates the code path (static-derived), not a live hit.\n\
- Calibrate: High/Critical only when the sink is reachable and exploitable from untrusted input; guarded/unreachable code is Low or a lead.\n\n";
/// Methodology directions for a modern JS SPA backed by a REST/GraphQL API /// Methodology directions for a modern JS SPA backed by a REST/GraphQL API
/// (Angular/React/Vue front + Node/Express-style API — the shape of OWASP Juice /// (Angular/React/Vue front + Node/Express-style API — the shape of OWASP Juice
/// Shop and many real apps). These are DIRECTIONS on HOW to hunt each vuln class, /// Shop and many real apps). These are DIRECTIONS on HOW to hunt each vuln class,
@@ -456,20 +487,37 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender<Str
// Use the model to pick the agents whose preconditions match the recon — // Use the model to pick the agents whose preconditions match the recon —
// the harness reasons about *which* specialists to run, not all of them. // the harness reasons about *which* specialists to run, not all of them.
// Exception: when the operator pinned an explicit set (--only), run EXACTLY
// those and skip recon-based selection — used to re-test a single vuln.
let focus = cfg.instructions.clone().unwrap_or_default(); let focus = cfg.instructions.clone().unwrap_or_default();
let chosen = select_agents(pool, &recon, &focus, &ranked, &tx).await; let selected: Vec<Agent> = if !cfg.pinned.is_empty() {
let selected: Vec<Agent> = if !chosen.is_empty() {
let sel: Vec<Agent> = let sel: Vec<Agent> =
ranked.iter().filter(|a| chosen.iter().any(|c| c == &a.name)).cloned().collect(); ranked.iter().filter(|a| cfg.pinned.iter().any(|p| p == &a.name)).cloned().collect();
if sel.is_empty() { if sel.is_empty() {
heuristic_select(&ranked, &recon, &focus, cap) let _ = tx.send(format!("--only matched no agent ({}) — falling back to recon selection",
cfg.pinned.join(", "))).await;
let chosen = select_agents(pool, &recon, &focus, &ranked, &tx).await;
ranked.iter().filter(|a| chosen.iter().any(|c| c == &a.name)).take(cap).cloned().collect()
} else { } else {
sel.into_iter().take(cap).collect() let _ = tx.send(format!("--only: running exactly {} pinned agent(s): {}", sel.len(),
sel.iter().map(|a| a.name.clone()).collect::<Vec<_>>().join(", "))).await;
sel
} }
} else { } else {
// LLM selection failed/empty → recon+focus keyword heuristic, not a blind flat list. let chosen = select_agents(pool, &recon, &focus, &ranked, &tx).await;
let _ = tx.send("selection empty — using recon-keyword heuristic".into()).await; if !chosen.is_empty() {
heuristic_select(&ranked, &recon, &focus, cap) let sel: Vec<Agent> =
ranked.iter().filter(|a| chosen.iter().any(|c| c == &a.name)).cloned().collect();
if sel.is_empty() {
heuristic_select(&ranked, &recon, &focus, cap)
} else {
sel.into_iter().take(cap).collect()
}
} else {
// LLM selection failed/empty → recon+focus keyword heuristic, not a blind flat list.
let _ = tx.send("selection empty — using recon-keyword heuristic".into()).await;
heuristic_select(&ranked, &recon, &focus, cap)
}
}; };
// Dedup: never run the same agent twice in one engagement. // Dedup: never run the same agent twice in one engagement.
let mut selected: Vec<Agent> = { let mut selected: Vec<Agent> = {
@@ -479,7 +527,7 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender<Str
// No creds given → always run the registration/form agent FIRST so the run // No creds given → always run the registration/form agent FIRST so the run
// reaches the authenticated surface (and the operator sees it happen). It // reaches the authenticated surface (and the operator sees it happen). It
// self-registers one test account under the anti-flood guardrail. // self-registers one test account under the anti-flood guardrail.
if cfg.auth.as_deref().unwrap_or("").trim().is_empty() { if cfg.pinned.is_empty() && cfg.auth.as_deref().unwrap_or("").trim().is_empty() {
if let Some(reg) = lib.vulns.iter().find(|a| a.name == "account_registration_and_forms") { if let Some(reg) = lib.vulns.iter().find(|a| a.name == "account_registration_and_forms") {
if !selected.iter().any(|a| a.name == reg.name) { if !selected.iter().any(|a| a.name == reg.name) {
let _ = tx.send("no creds set — running account_registration_and_forms first to reach the authenticated surface".into()).await; let _ = tx.send("no creds set — running account_registration_and_forms first to reach the authenticated surface".into()).await;
@@ -597,7 +645,22 @@ pub async fn run_whitebox(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: S
} }
let mut rl = cfg.rl_path.as_ref().map(|p| RlState::load(Path::new(p))).unwrap_or_default(); let mut rl = cfg.rl_path.as_ref().map(|p| RlState::load(Path::new(p))).unwrap_or_default();
let mut ranked: Vec<Agent> = if lib.code.is_empty() { lib.vulns.clone() } else { lib.code.clone() }; let pool_agents: Vec<Agent> = if lib.code.is_empty() { lib.vulns.clone() } else { lib.code.clone() };
let mut ranked: Vec<Agent> = if cfg.pinned.is_empty() {
pool_agents
} else {
// Operator pinned an explicit agent set (--only): re-test exactly those.
let sel: Vec<Agent> = pool_agents.iter()
.filter(|a| cfg.pinned.iter().any(|p| p == &a.name)).cloned().collect();
if sel.is_empty() {
let _ = tx.send(format!("--only matched no code agent ({}) — reviewing with the full set",
cfg.pinned.join(", "))).await;
pool_agents
} else {
let _ = tx.send(format!("--only: reviewing with exactly {} pinned agent(s)", sel.len())).await;
sel
}
};
ranked.sort_by(|a, b| rl.weight(&b.name).partial_cmp(&rl.weight(&a.name)).unwrap_or(std::cmp::Ordering::Equal)); ranked.sort_by(|a, b| rl.weight(&b.name).partial_cmp(&rl.weight(&a.name)).unwrap_or(std::cmp::Ordering::Equal));
let cap = if cfg.max_agents > 0 { cfg.max_agents.min(ranked.len()) } else { ranked.len() }; let cap = if cfg.max_agents > 0 { cfg.max_agents.min(ranked.len()) } else { ranked.len() };
let selected: Vec<Agent> = ranked.into_iter().take(cap).collect(); let selected: Vec<Agent> = ranked.into_iter().take(cap).collect();
@@ -616,11 +679,15 @@ pub async fn run_whitebox(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: S
let user = format!( let user = format!(
"{}\n\nSOURCE CODE TO REVIEW:\n```\n{}\n```\n\nReply ONLY with a JSON array of findings (may be empty []). \ "{}\n\nSOURCE CODE TO REVIEW:\n```\n{}\n```\n\nReply ONLY with a JSON array of findings (may be empty []). \
Each item: {{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}} \ Each item: {{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}} \
where `endpoint` is the file:line and `evidence` quotes the vulnerable code.", where `endpoint` is the file:line and `evidence` quotes the vulnerable code. \
When a finding warrants a runnable proof, write a repro script to $NEUROSPLOIT_POCS and put its path in `payload`.",
ag.user.replace("{target}", "the provided repository").replace("{recon_json}", "{}"), ag.user.replace("{target}", "the provided repository").replace("{recon_json}", "{}"),
ctx ctx
); );
match pool.complete_routed(Task::Exploit, &ag.name, &ag.system, &user).await { // Prepend the white-box doctrine so code agents stay in static
// source-review mode and never hallucinate live/black-box actions.
let sys = format!("{}{}", WHITEBOX_DOCTRINE, ag.system);
match pool.complete_routed(Task::Exploit, &ag.name, &sys, &user).await {
Ok((m, text)) => { Ok((m, text)) => {
let f = extract_findings(&text, &ag.name); let f = extract_findings(&text, &ag.name);
let _ = txc.send(format!("analyze {} via {}{} candidate(s)", ag.name, m.label(), f.len())).await; let _ = txc.send(format!("analyze {} via {}{} candidate(s)", ag.name, m.label(), f.len())).await;
@@ -910,13 +977,13 @@ async fn chain_from_seed(pool: &ModelPool, target: &str, directives: &str, recon
}; };
let short: String = seed.title.chars().take(28).collect(); let short: String = seed.title.chars().take(28).collect();
let user = format!( let user = format!(
"AUTHORIZED engagement on {target}.\n\n{directives}{react}{depth}{decision}{safety}{doctrine}\ "AUTHORIZED engagement on {target}.\n\n{directives}{react}{depth}{decision}{chain}{safety}{doctrine}\
FOOTHOLD TO EXPAND (round {round}/{max}):\n- [{}] {} @ {} ({})\n payload: {}\n evidence: {}\n\n\ FOOTHOLD TO EXPAND (round {round}/{max}):\n- [{}] {} @ {} ({})\n payload: {}\n evidence: {}\n\n\
LOOT GATHERED (reuse it):\n{loot_block}\n\n{recipe_block}RECON:\n{recon_ctx}\n\n\ LOOT GATHERED (reuse it):\n{loot_block}\n\n{recipe_block}RECON:\n{recon_ctx}\n\n\
From THIS foothold, DECIDE the best directions and PROVE new impact post-exploitation (loot creds/keys/config/source), credential reuse, privilege escalation (horizontal & vertical), lateral movement to adjacent services/hosts, data exfiltration, and NEW attack surface it exposes. Every claim needs a real tool receipt.\n\n\ From THIS foothold, DECIDE the best directions and PROVE new impact post-exploitation (loot creds/keys/config/source), credential reuse, privilege escalation (horizontal & vertical), lateral movement to adjacent services/hosts, data exfiltration, and NEW attack surface it exposes. Every claim needs a real tool receipt.\n\n\
Reply ONLY JSON: {{\"findings\":[{{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}}],\"loot\":[\"cred:user:pass@host\",\"token:...\",\"host:10.0.0.5\",\"endpoint:/internal/api\"]}} (empty arrays are fine).", Reply ONLY JSON: {{\"findings\":[{{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}}],\"loot\":[\"cred:user:pass@host\",\"token:...\",\"host:10.0.0.5\",\"endpoint:/internal/api\"]}} (empty arrays are fine).",
seed.severity, seed.title, seed.endpoint, seed.cwe, seed.payload, seed.evidence, seed.severity, seed.title, seed.endpoint, seed.cwe, seed.payload, seed.evidence,
react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, decision = DECISION_DOCTRINE, safety = SAFETY_DOCTRINE, doctrine = tool_doctrine(pool.mcp_config.is_some()), react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, decision = DECISION_DOCTRINE, chain = CHAIN_DOCTRINE, safety = SAFETY_DOCTRINE, doctrine = tool_doctrine(pool.mcp_config.is_some()),
); );
let label = format!("chain:{short}"); let label = format!("chain:{short}");
match pool.complete_routed(Task::Exploit, &label, CHAIN_SYS, &user).await { match pool.complete_routed(Task::Exploit, &label, CHAIN_SYS, &user).await {
+41 -1
View File
@@ -413,11 +413,51 @@ pub fn json_report(target: &str, findings: &[Finding], run_id: &str, meta: &Enga
/// Write the full report bundle: Markdown, JSON, HTML, and the Typst/PDF. /// Write the full report bundle: Markdown, JSON, HTML, and the Typst/PDF.
/// Returns the primary artifact path (PDF if typst present, else the .typ). /// Returns the primary artifact path (PDF if typst present, else the .typ).
/// A "## Reproduction — PoC scripts" section listing the runnable proof-of-concept
/// scripts agents wrote to `<run>/pocs/`. Each is a self-contained artifact the
/// operator can re-run to replicate a finding, so the report ships with a live
/// reproduction kit — not just prose. Empty string when no PoCs were produced.
pub fn pocs_section(dir: &Path) -> String {
let pocs = dir.join("pocs");
let mut entries: Vec<(String, String)> = Vec::new();
if let Ok(rd) = std::fs::read_dir(&pocs) {
for e in rd.flatten() {
let p = e.path();
if !p.is_file() { continue; }
let name = match p.file_name().and_then(|s| s.to_str()) { Some(n) => n.to_string(), None => continue };
// First non-empty comment line doubles as a one-line description.
let desc = std::fs::read_to_string(&p).ok()
.and_then(|t| t.lines()
.map(|l| l.trim())
.find(|l| l.starts_with('#') || l.starts_with("//") || l.starts_with("/*"))
.map(|l| l.trim_start_matches(['#', '/', '*', ' ']).trim().to_string()))
.unwrap_or_default();
entries.push((name, desc));
}
}
if entries.is_empty() { return String::new(); }
entries.sort();
let mut s = String::from("## Reproduction — PoC scripts\n\n");
s.push_str("Runnable proofs written to `pocs/` during the engagement. Re-run any of \
them to replicate the corresponding finding.\n\n");
for (name, desc) in entries {
if desc.is_empty() {
s.push_str(&format!("- `pocs/{name}`\n"));
} else {
s.push_str(&format!("- `pocs/{name}` — {desc}\n"));
}
}
s.push('\n');
s
}
pub fn write_all(target: &str, findings: &[Finding], dir: &Path) -> std::io::Result<PathBuf> { pub fn write_all(target: &str, findings: &[Finding], dir: &Path) -> std::io::Result<PathBuf> {
std::fs::create_dir_all(dir)?; std::fs::create_dir_all(dir)?;
let run_id = dir.file_name().and_then(|s| s.to_str()).unwrap_or("run").to_string(); let run_id = dir.file_name().and_then(|s| s.to_str()).unwrap_or("run").to_string();
let meta = read_meta(dir); let meta = read_meta(dir);
std::fs::write(dir.join("report.md"), markdown(target, findings, &meta))?; let mut md = markdown(target, findings, &meta);
md.push_str(&pocs_section(dir));
std::fs::write(dir.join("report.md"), md)?;
std::fs::write(dir.join("report.json"), json_report(target, findings, &run_id, &meta))?; std::fs::write(dir.join("report.json"), json_report(target, findings, &run_id, &meta))?;
std::fs::write(dir.join("report.html"), html(target, findings, &meta))?; std::fs::write(dir.join("report.html"), html(target, findings, &meta))?;
typst_report(target, findings, dir) typst_report(target, findings, dir)