mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-07-25 20:20:52 +02:00
v3.6.2: stream Codex tool-by-tool + capture agent commands in /logs & /status
- Drive `codex exec --json` and parse its JSONL event stream into the same categorized live feed as Claude Code (exec/edit/tool/net/tokens), so recon and exploitation are visible as each command runs instead of a silent black box. - Fix the activity feed to keep per-agent tool events (commands, network, files, findings) and only filter model reasoning + token telemetry, so /logs shows the real command trail and /status 'last:' is a true sign-of-life. - Surface failed internal commands as 'exec: (exit N)'; keep Codex auth/rate detection from stderr.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<h1 align="center">🧠 NeuroSploit v3.6.1</h1>
|
<h1 align="center">🧠 NeuroSploit v3.6.2</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://trendshift.io/repositories/22624?utm_source=trendshift-badge&utm_medium=badge&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&utm_medium=badge&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,7 +12,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/Version-3.6.1-blue?style=flat-square">
|
<img src="https://img.shields.io/badge/Version-3.6.2-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-417-red?style=flat-square">
|
<img src="https://img.shields.io/badge/MD%20Agents-417-red?style=flat-square">
|
||||||
@@ -28,14 +28,15 @@
|
|||||||
>
|
>
|
||||||
> 📖 **New here? Read the [full Tutorial & User Guide →](TUTORIAL.md)** — every mode, flag, config and example explained.
|
> 📖 **New here? Read the [full Tutorial & User Guide →](TUTORIAL.md)** — every mode, flag, config and example explained.
|
||||||
|
|
||||||
> 🆕 **New in v3.6.1 — Cloud testing + REPL navigation + deeper recon:**
|
> 🆕 **New in v3.6.2 — live Codex streaming + full activity feed:**
|
||||||
> **AWS/GCP/Azure** agents (+17 → **375** total) with credentials wired through
|
> Codex runs now **stream tool-by-tool** (`codex exec --json`) exactly like
|
||||||
> `creds.yaml`; a more navigable **REPL** — **`/timeout`** idle guardrail,
|
> Claude Code — every command, file edit, MCP call and token count appears live
|
||||||
> **multi-target** `/target a,b,c` (sequential), an interactive **`/results`**
|
> instead of a silent black box, so a long intense recon never looks frozen. The
|
||||||
> browser (target → vuln → detail, Esc to go back) and **`/report`** picker; and
|
> **`/logs`** feed and **`/status`** sign-of-line now capture the *actual
|
||||||
> **deeper recon** that downloads & analyzes JavaScript (endpoints, secrets,
|
> commands each agent runs* (subfinder, httpx, nmap, curl…), not just pipeline
|
||||||
> source maps) and does request/response differential analysis. Interactive
|
> phases. *(v3.6.1 added GPT-5.6 sol/terra/luna, the Codex exit-1 fix, `/logs`
|
||||||
> line-editing prompt bug fixed.
|
> and richer `/status`; v3.6.0 added AI-agent/LLM/MCP/Skills/n8n testing +
|
||||||
|
> onboarding wizard + Cloud scope.)*
|
||||||
> *(v3.5.4 added robust attack chaining + false-positive reduction; v3.5.3
|
> *(v3.5.4 added robust attack chaining + false-positive reduction; v3.5.3
|
||||||
> GitHub/GitLab/Jira **[integrations](TUTORIAL-INTEGRATION.md)**; v3.5.2 the DEPTH
|
> GitHub/GitLab/Jira **[integrations](TUTORIAL-INTEGRATION.md)**; v3.5.2 the DEPTH
|
||||||
> doctrine + report-hygiene — see [RELEASE.md](RELEASE.md).)*
|
> doctrine + report-hygiene — see [RELEASE.md](RELEASE.md).)*
|
||||||
|
|||||||
+20
-2
@@ -1,7 +1,7 @@
|
|||||||
# NeuroSploit v3.6.1 — Release Notes
|
# NeuroSploit v3.6.2 — Release Notes
|
||||||
|
|
||||||
**Release Date:** July 2026
|
**Release Date:** July 2026
|
||||||
**Codename:** GPT-5.6 models
|
**Codename:** Live Codex
|
||||||
**License:** MIT
|
**License:** MIT
|
||||||
**Credits:** Joas A Santos & Red Team Leaders
|
**Credits:** Joas A Santos & Red Team Leaders
|
||||||
|
|
||||||
@@ -9,6 +9,24 @@
|
|||||||
|
|
||||||
## Highlights
|
## Highlights
|
||||||
|
|
||||||
|
- **Codex now streams live, tool-by-tool.** `codex exec` is driven with `--json`
|
||||||
|
and its JSONL event stream is parsed into the same categorized activity feed
|
||||||
|
as Claude Code: every shell command it runs (`exec:`), file edit (`edit:`),
|
||||||
|
MCP tool call (`tool:`), web search (`net:`) and token count appears the moment
|
||||||
|
it happens. A long, intense recon (subfinder → httpx → katana → nmap …) is no
|
||||||
|
longer a silent black box — you watch each tool execute.
|
||||||
|
- **`/logs` and `/status` now capture what each agent actually runs.** The
|
||||||
|
activity feed previously dropped the per-agent tool events; it now keeps the
|
||||||
|
actionable ones (commands, network, files, findings) and only filters long
|
||||||
|
model reasoning and token telemetry. `/logs` shows the real command trail;
|
||||||
|
`/status` `last:` shows a true sign-of-life.
|
||||||
|
- Failed internal commands surface as `exec: (exit N) <cmd>` instead of
|
||||||
|
silently vanishing, and Codex auth/rate errors are still detected from stderr.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Previously in v3.6.1
|
||||||
|
|
||||||
- **Added the GPT-5.6 model line** (OpenAI / ChatGPT): `openai:gpt-5.6-sol`
|
- **Added the GPT-5.6 model line** (OpenAI / ChatGPT): `openai:gpt-5.6-sol`
|
||||||
(frontier / default), `openai:gpt-5.6-terra` (balanced), and
|
(frontier / default), `openai:gpt-5.6-terra` (balanced), and
|
||||||
`openai:gpt-5.6-luna` (fast & affordable) — alongside the existing GPT-5.x,
|
`openai:gpt-5.6-luna` (fast & affordable) — alongside the existing GPT-5.x,
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
# NeuroSploit — Tutorial & User Guide (v3.6.1)
|
# NeuroSploit — Tutorial & User Guide (v3.6.2)
|
||||||
|
|
||||||
A complete, hands-on guide to installing, configuring and running NeuroSploit —
|
A complete, hands-on guide to installing, configuring and running NeuroSploit —
|
||||||
the autonomous, multi-model penetration-testing harness.
|
the autonomous, multi-model penetration-testing harness.
|
||||||
@@ -98,7 +98,7 @@ Agents **degrade gracefully**: if `rustscan` is absent they use `nmap`; if neith
|
|||||||
### Verify
|
### Verify
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
neurosploit --version # neurosploit 3.6.1
|
neurosploit --version # neurosploit 3.6.2
|
||||||
neurosploit agents # {"vulns":196,...,"chains":12,"total":417}
|
neurosploit agents # {"vulns":196,...,"chains":12,"total":417}
|
||||||
neurosploit models # all providers & models
|
neurosploit models # all providers & models
|
||||||
```
|
```
|
||||||
|
|||||||
Generated
+2
-2
@@ -871,7 +871,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neurosploit"
|
name = "neurosploit"
|
||||||
version = "3.6.1"
|
version = "3.6.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -888,7 +888,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neurosploit-harness"
|
name = "neurosploit-harness"
|
||||||
version = "3.6.1"
|
version = "3.6.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures",
|
"futures",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ members = ["crates/harness", "app"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "3.6.1"
|
version = "3.6.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/JoasASantos/NeuroSploit"
|
repository = "https://github.com/JoasASantos/NeuroSploit"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! NeuroSploit v3.6.1 — interactive harness + CLI (`run` / `whitebox` / `agents` / `models`).
|
//! NeuroSploit v3.6.2 — 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.1 — multi-model autonomous pentest harness",
|
about = "NeuroSploit v3.6.2 — multi-model autonomous pentest harness",
|
||||||
long_about = "NeuroSploit v3.6.1 — a Rust multi-model harness that drives a pool of LLMs \
|
long_about = "NeuroSploit v3.6.2 — 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\
|
||||||
@@ -721,7 +721,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.1 · by Joas A Santos & Red Team Leaders");
|
println!(" ┌─ NeuroSploit v3.6.2 · 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(", "));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! NeuroSploit v3.6.1 — interactive session (Claude-Code / Codex / Cursor-CLI style).
|
//! NeuroSploit v3.6.2 — 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
|
||||||
@@ -52,9 +52,16 @@ impl RunLive {
|
|||||||
fn ingest(&mut self, line: &str) {
|
fn ingest(&mut self, line: &str) {
|
||||||
let low = line.to_lowercase();
|
let low = line.to_lowercase();
|
||||||
self.lines += 1;
|
self.lines += 1;
|
||||||
// Keep a compact activity trail for /logs and the /status sign-of-life
|
// Keep a compact activity trail for /logs and the /status sign-of-life.
|
||||||
// (skip the machine-only finding_json/ai/tokens noise).
|
// Streamed agent events are tagged "@label <event>": keep the actionable
|
||||||
if !low.starts_with("finding_json:") && !low.starts_with("@") && !low.contains("ai:") && !low.starts_with("tokens:") {
|
// ones (commands, net, tools, file edits, phases) so the operator sees
|
||||||
|
// exactly what each agent is running — drop only long model reasoning
|
||||||
|
// (ai:), token telemetry (tokens:), and machine JSON (finding_json:).
|
||||||
|
let payload = line.strip_prefix('@')
|
||||||
|
.and_then(|r| r.split_once(' ').map(|(_, rest)| rest))
|
||||||
|
.unwrap_or(line);
|
||||||
|
let plow = payload.to_lowercase();
|
||||||
|
if !low.starts_with("finding_json:") && !plow.starts_with("ai:") && !plow.starts_with("tokens:") {
|
||||||
let clean: String = line.chars().take(160).collect();
|
let clean: String = line.chars().take(160).collect();
|
||||||
self.last = clean.clone();
|
self.last = clean.clone();
|
||||||
self.feed.push(clean);
|
self.feed.push(clean);
|
||||||
@@ -346,7 +353,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.1");
|
println!(" ████╗ ██║██╔════╝██║ ██║██╔══██╗██╔═══██╗ NeuroSploit v3.6.2");
|
||||||
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,4 +1,4 @@
|
|||||||
//! NeuroSploit v3.6.1 — TUI "Mission Control" mode.
|
//! NeuroSploit v3.6.2 — 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:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! POMDP belief-state world model (v3.6.1).
|
//! POMDP belief-state world model (v3.6.2).
|
||||||
//!
|
//!
|
||||||
//! The target is only partially observable, so we don't track booleans — we
|
//! The target is only partially observable, so we don't track booleans — we
|
||||||
//! track a **belief**: a property graph whose nodes (host / service / vuln /
|
//! track a **belief**: a property graph whose nodes (host / service / vuln /
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! Verification / grounding engine (v3.6.1).
|
//! Verification / grounding engine (v3.6.2).
|
||||||
//!
|
//!
|
||||||
//! Hard rule: **no claim enters the world model without a tool receipt** — raw
|
//! Hard rule: **no claim enters the world model without a tool receipt** — raw
|
||||||
//! tool output, not the LLM's paraphrase. This is the empirical anti-hallucination
|
//! tool output, not the LLM's paraphrase. This is the empirical anti-hallucination
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! NeuroSploit v3.6.1 harness — a robust multi-model runtime for the
|
//! NeuroSploit v3.6.2 harness — a robust multi-model runtime for the
|
||||||
//! markdown-driven autonomous pentest engine.
|
//! markdown-driven autonomous pentest engine.
|
||||||
//!
|
//!
|
||||||
//! The harness loads the `agents_md/` library, drives a *pool* of LLM models
|
//! The harness loads the `agents_md/` library, drives a *pool* of LLM models
|
||||||
|
|||||||
@@ -194,6 +194,12 @@ impl ChatClient {
|
|||||||
if bin == "claude" {
|
if bin == "claude" {
|
||||||
return self.chat_claude_stream(label, model, &prompt, mcp_config, progress).await;
|
return self.chat_claude_stream(label, model, &prompt, mcp_config, progress).await;
|
||||||
}
|
}
|
||||||
|
// Codex exec streams JSONL events (`--json`): commands it runs, agent
|
||||||
|
// messages, file changes, token usage. Surface them live so recon and
|
||||||
|
// exploitation are visible tool-by-tool instead of a silent black box.
|
||||||
|
if bin == "codex" {
|
||||||
|
return self.chat_codex_stream(label, model, &prompt, mcp_config, progress).await;
|
||||||
|
}
|
||||||
|
|
||||||
let mut cmd = Command::new(bin);
|
let mut cmd = Command::new(bin);
|
||||||
match bin {
|
match bin {
|
||||||
@@ -366,6 +372,136 @@ impl ChatClient {
|
|||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drive `codex exec --json` and surface its JSONL event stream as a live,
|
||||||
|
/// categorized activity feed (commands, agent messages, file changes, MCP
|
||||||
|
/// tool calls, token usage). The final agent message is returned as the
|
||||||
|
/// result. Mirrors `chat_claude_stream` so Codex runs are just as visible.
|
||||||
|
async fn chat_codex_stream(
|
||||||
|
&self,
|
||||||
|
label: &str,
|
||||||
|
model: &str,
|
||||||
|
prompt: &str,
|
||||||
|
mcp_config: Option<&str>,
|
||||||
|
progress: Option<tokio::sync::mpsc::Sender<String>>,
|
||||||
|
) -> Result<String> {
|
||||||
|
let mut cmd = Command::new("codex");
|
||||||
|
cmd.arg("exec").arg("--json").arg("--model").arg(model)
|
||||||
|
.arg("--dangerously-bypass-approvals-and-sandbox");
|
||||||
|
if let Some(mcp) = mcp_config {
|
||||||
|
for (name, cmdline, args) in mcp_servers_from(mcp) {
|
||||||
|
cmd.arg("-c").arg(format!("mcp_servers.{name}.command={cmdline}"));
|
||||||
|
cmd.arg("-c").arg(format!("mcp_servers.{name}.args={args}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.arg("-");
|
||||||
|
cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).kill_on_drop(true);
|
||||||
|
let mut child = cmd.spawn().map_err(|e| anyhow!("spawn codex failed: {e}"))?;
|
||||||
|
if let Some(mut stdin) = child.stdin.take() {
|
||||||
|
stdin.write_all(prompt.as_bytes()).await?;
|
||||||
|
// Drop closes stdin so Codex processes the prompt and exits.
|
||||||
|
}
|
||||||
|
let stdout = child.stdout.take().ok_or_else(|| anyhow!("no stdout"))?;
|
||||||
|
let stderr = child.stderr.take().ok_or_else(|| anyhow!("no stderr"))?;
|
||||||
|
let mut lines = BufReader::new(stdout).lines();
|
||||||
|
let lbl = if label.is_empty() { String::new() } else { format!("@{label} ") };
|
||||||
|
let emit = |s: String| {
|
||||||
|
if let Some(tx) = &progress {
|
||||||
|
let _ = tx.try_send(format!("{lbl}{s}"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Last agent_message is the model's final answer; keep every one so a
|
||||||
|
// run that ends on a tool call still returns the most recent reasoning.
|
||||||
|
let mut result = String::new();
|
||||||
|
let read = async {
|
||||||
|
while let Ok(Some(line)) = lines.next_line().await {
|
||||||
|
let Ok(v) = serde_json::from_str::<serde_json::Value>(&line) else { continue };
|
||||||
|
let ty = v.get("type").and_then(|t| t.as_str()).unwrap_or("");
|
||||||
|
match ty {
|
||||||
|
"item.started" | "item.completed" => {
|
||||||
|
let Some(item) = v.get("item") else { continue };
|
||||||
|
let itype = item.get("type").and_then(|t| t.as_str()).unwrap_or("");
|
||||||
|
match itype {
|
||||||
|
"command_execution" => {
|
||||||
|
// Only announce on start (avoid double lines); note failures on completion.
|
||||||
|
let c = item.get("command").and_then(|x| x.as_str()).unwrap_or("");
|
||||||
|
// Strip the `/bin/sh -lc '...'` wrapper Codex adds.
|
||||||
|
let c = c.strip_prefix("/bin/sh -lc ").map(|s| s.trim_matches('\'')).unwrap_or(c);
|
||||||
|
if ty == "item.started" {
|
||||||
|
let danger = c.contains("rm -rf") || c.contains("mkfs")
|
||||||
|
|| c.contains(":(){") || c.contains("dd if=") || c.contains("> /dev/");
|
||||||
|
emit(format!("{}: {}", if danger { "danger" } else { "exec" }, truncate(c, 200)));
|
||||||
|
} else if let Some(code) = item.get("exit_code").and_then(|x| x.as_i64()) {
|
||||||
|
if code != 0 {
|
||||||
|
emit(format!("exec: (exit {code}) {}", truncate(c, 120)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"agent_message" => {
|
||||||
|
if let Some(t) = item.get("text").and_then(|x| x.as_str()) {
|
||||||
|
let t = t.trim();
|
||||||
|
if !t.is_empty() {
|
||||||
|
if ty == "item.completed" { result = t.to_string(); }
|
||||||
|
emit(format!("ai: {}", truncate(t, 240)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"file_change" | "patch" => {
|
||||||
|
let p = item.get("path").and_then(|x| x.as_str())
|
||||||
|
.or_else(|| item.get("file").and_then(|x| x.as_str())).unwrap_or("file");
|
||||||
|
emit(format!("edit: {p}"));
|
||||||
|
}
|
||||||
|
"mcp_tool_call" => {
|
||||||
|
let name = item.get("tool").and_then(|x| x.as_str())
|
||||||
|
.or_else(|| item.get("name").and_then(|x| x.as_str())).unwrap_or("mcp");
|
||||||
|
emit(format!("tool: {name}"));
|
||||||
|
}
|
||||||
|
"web_search" => {
|
||||||
|
let q = item.get("query").and_then(|x| x.as_str()).unwrap_or("");
|
||||||
|
emit(format!("net: search {}", truncate(q, 100)));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"turn.completed" => {
|
||||||
|
let ti = v.pointer("/usage/input_tokens").and_then(|x| x.as_u64());
|
||||||
|
let to = v.pointer("/usage/output_tokens").and_then(|x| x.as_u64());
|
||||||
|
if ti.is_some() || to.is_some() {
|
||||||
|
emit(format!("tokens: in={} out={}", ti.unwrap_or(0), to.unwrap_or(0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Bound the whole streamed turn (matches the buffered path's cap).
|
||||||
|
if tokio::time::timeout(Duration::from_secs(900), read).await.is_err() {
|
||||||
|
return Err(anyhow!("codex stream timed out after 900s"));
|
||||||
|
}
|
||||||
|
let status = child.wait().await.ok();
|
||||||
|
// Drain stderr for auth/rate diagnostics if we got nothing usable.
|
||||||
|
if result.is_empty() {
|
||||||
|
let mut errbuf = String::new();
|
||||||
|
let mut el = BufReader::new(stderr).lines();
|
||||||
|
while let Ok(Some(l)) = el.next_line().await {
|
||||||
|
if !errbuf.is_empty() { errbuf.push('\n'); }
|
||||||
|
errbuf.push_str(&l);
|
||||||
|
if errbuf.len() > 2000 { break; }
|
||||||
|
}
|
||||||
|
let low = errbuf.to_lowercase();
|
||||||
|
let hard = ["not logged in", "please log in", "please login", "run /login",
|
||||||
|
"unauthorized", "not authenticated", "invalid api key", "no api key",
|
||||||
|
"rate limit", "429", "quota", "credit balance", "usage limit"]
|
||||||
|
.iter().any(|k| low.contains(k));
|
||||||
|
let code = status.and_then(|s| s.code()).map(|c| c.to_string()).unwrap_or_else(|| "signal".into());
|
||||||
|
if hard || !errbuf.trim().is_empty() {
|
||||||
|
return Err(anyhow!("codex subscription CLI exit {}: {}", code, truncate(errbuf.trim(), 240)));
|
||||||
|
}
|
||||||
|
return Err(anyhow!("codex stream produced no result"));
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Categorise a Claude tool_use block into a tagged activity-feed event.
|
/// Categorise a Claude tool_use block into a tagged activity-feed event.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! POMDP decision layer (v3.6.1): value-of-information planning + the
|
//! POMDP decision layer (v3.6.2): value-of-information planning + the
|
||||||
//! anti-hallucination gate.
|
//! anti-hallucination gate.
|
||||||
//!
|
//!
|
||||||
//! The choice "scan more vs exploit now" is **not** a heuristic here — it falls
|
//! The choice "scan more vs exploit now" is **not** a heuristic here — it falls
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! Deterministic HTTP request/response analysis (v3.6.1).
|
//! Deterministic HTTP request/response analysis (v3.6.2).
|
||||||
//!
|
//!
|
||||||
//! Before the LLM recon runs, the harness performs a **real** probe of the
|
//! Before the LLM recon runs, the harness performs a **real** probe of the
|
||||||
//! target and captures observed facts — status, headers, security headers,
|
//! target and captures observed facts — status, headers, security headers,
|
||||||
|
|||||||
@@ -97,9 +97,9 @@ pub fn html(target: &str, findings: &[Finding]) -> String {
|
|||||||
h4{{margin:12px 0 3px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:#8b5cf6}}\
|
h4{{margin:12px 0 3px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:#8b5cf6}}\
|
||||||
.b{{color:#8b5cf6;font-weight:800}}</style></head><body>\
|
.b{{color:#8b5cf6;font-weight:800}}</style></head><body>\
|
||||||
<h1><span class=b>NeuroSploit</span> Penetration Test Report</h1>\
|
<h1><span class=b>NeuroSploit</span> Penetration Test Report</h1>\
|
||||||
<div class=meta>Target: <b>{t}</b> · v3.6.1 Rust harness · multi-model validated</div>\
|
<div class=meta>Target: <b>{t}</b> · v3.6.2 Rust harness · multi-model validated</div>\
|
||||||
<div>{chips}</div>{graph_block}<h2>Findings ({n})</h2>{body}\
|
<div>{chips}</div>{graph_block}<h2>Findings ({n})</h2>{body}\
|
||||||
<p class=meta>Authorized testing only. Findings confirmed by multi-model adversarial voting.<br>NeuroSploit v3.6.1 · by <b>Joas A Santos</b> & <b>Red Team Leaders</b></p></body></html>",
|
<p class=meta>Authorized testing only. Findings confirmed by multi-model adversarial voting.<br>NeuroSploit v3.6.2 · by <b>Joas A Santos</b> & <b>Red Team Leaders</b></p></body></html>",
|
||||||
t = esc(target), chips = chips, n = sorted.len(), body = body, graph_block = graph_block,
|
t = esc(target), chips = chips, n = sorted.len(), body = body, graph_block = graph_block,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ pub fn typst_report(target: &str, findings: &[Finding], dir: &Path) -> std::io::
|
|||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
data.push_str(&format!(
|
data.push_str(&format!(
|
||||||
"#let meta = (target: {}, run_id: {}, generated: {}, model: {})\n",
|
"#let meta = (target: {}, run_id: {}, generated: {}, model: {})\n",
|
||||||
tq(target), tq(&run_id), tq("NeuroSploit v3.6.1"), tq("multi-model")
|
tq(target), tq(&run_id), tq("NeuroSploit v3.6.2"), tq("multi-model")
|
||||||
));
|
));
|
||||||
data.push_str("#let findings = (\n");
|
data.push_str("#let findings = (\n");
|
||||||
for f in sorted_findings(findings) {
|
for f in sorted_findings(findings) {
|
||||||
|
|||||||
Reference in New Issue
Block a user