mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-10-05 07:27:18 +02:00
v4.2.4: full Kali sandbox orchestration for recon
Spin Kali up for the engagement, run tool-recon in it, let the LLM refine on top, tear it down after. - kali_provision_recon_tools(): installs the recon toolbox (subfinder/httpx/ katana/gau/waybackurls/nuclei/naabu/dnsx/assetfinder/gf/qsreplace/anew via go install + apt) in the Kali sandbox on demand, idempotent, once per run. - kali_tool_recon(): deterministic tool-recon phase — gau/waybackurls/katana URL harvest + targeted nuclei (exposures/misconfig/takeovers, high-signal only) + gf-flagged candidate URLs by class — over the live hosts, then folded into the recon context so the LLM works on top of the tool output and confirms each. Runs in the sandbox (--sandbox) or on host tools via recon_tool(). - Engine autostart: if the container engine is installed but not running, start it automatically (colima start / open -a Docker / systemctl start docker / podman machine start) and poll until up — a --sandbox run no longer fails just because the daemon wasn't started. Clear guidance if it can't be started. - Teardown: the Kali container is removed at the end of the run (override with NEUROSPLOIT_KEEP_SANDBOX=1). Version 4.2.4 across CLI/clap/web/README/TUTORIAL. 423 tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
c258299eb6
commit
94404555aa
9 files changed
+190
-16
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
<h1 align="center">🧠 NeuroSploit v4.2.3</h1>
|
<h1 align="center">🧠 NeuroSploit v4.2.4</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/JoasASantos/NeuroSploit/stargazers"><img src="https://img.shields.io/github/stars/JoasASantos/NeuroSploit?style=for-the-badge&logo=github&color=8b5cf6" alt="Stars"></a>
|
<a href="https://github.com/JoasASantos/NeuroSploit/stargazers"><img src="https://img.shields.io/github/stars/JoasASantos/NeuroSploit?style=for-the-badge&logo=github&color=8b5cf6" alt="Stars"></a>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/Version-4.2.3-blue?style=flat-square">
|
<img src="https://img.shields.io/badge/Version-4.2.4-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-479-red?style=flat-square">
|
<img src="https://img.shields.io/badge/MD%20Agents-479-red?style=flat-square">
|
||||||
@@ -52,7 +52,7 @@ Control TUI**.
|
|||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
> **New in v4.2.3** — **free, LLM-directed exploration**: an exploit agent's named
|
> **New in v4.2.4** — **free, LLM-directed exploration**: an exploit agent's named
|
||||||
> class is a starting point, not a cage — it maps what the app actually does and
|
> class is a starting point, not a cage — it maps what the app actually does and
|
||||||
> reports any class it can prove, with **authentication / identity** (login, signup,
|
> reports any class it can prove, with **authentication / identity** (login, signup,
|
||||||
> password reset, MFA, OAuth/OIDC/SAML, JWT, session) as a first-class target and
|
> password reset, MFA, OAuth/OIDC/SAML, JWT, session) as a first-class target and
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
# NeuroSploit — Tutorial & User Guide (v4.2.3)
|
# NeuroSploit — Tutorial & User Guide (v4.2.4)
|
||||||
|
|
||||||
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.
|
||||||
@@ -102,7 +102,7 @@ Agents **degrade gracefully**: if `rustscan` is absent they use `nmap`; if neith
|
|||||||
### Verify
|
### Verify
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
neurosploit --version # neurosploit 4.2.3
|
neurosploit --version # neurosploit 4.2.4
|
||||||
neurosploit agents # {"vulns":255,...,"ai":30,...,"total":480}
|
neurosploit agents # {"vulns":255,...,"ai":30,...,"total":480}
|
||||||
neurosploit models # all providers & models
|
neurosploit models # all providers & models
|
||||||
```
|
```
|
||||||
|
|||||||
Generated
+2
-2
@@ -940,7 +940,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neurosploit"
|
name = "neurosploit"
|
||||||
version = "4.2.3"
|
version = "4.2.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -957,7 +957,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "neurosploit-harness"
|
name = "neurosploit-harness"
|
||||||
version = "4.2.3"
|
version = "4.2.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ members = ["crates/harness", "app"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "4.2.3"
|
version = "4.2.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/JoasASantos/NeuroSploit"
|
repository = "https://github.com/JoasASantos/NeuroSploit"
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use std::path::{Path, PathBuf};
|
|||||||
#[command(
|
#[command(
|
||||||
name = "neurosploit",
|
name = "neurosploit",
|
||||||
version,
|
version,
|
||||||
about = "NeuroSploit v4.2.3 — multi-model autonomous pentest harness",
|
about = "NeuroSploit v4.2.4 — multi-model autonomous pentest harness",
|
||||||
long_about = "NeuroSploit v4.2.3 — a Rust multi-model harness that drives a pool of LLMs \
|
long_about = "NeuroSploit v4.2.4 — a Rust multi-model harness that drives a pool of LLMs \
|
||||||
(API key or local subscription: Claude/Codex/Gemini/Grok/OpenCode/Hermes) to autonomously test a target. \
|
(API key or local subscription: Claude/Codex/Gemini/Grok/OpenCode/Hermes) 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\
|
||||||
|
|||||||
@@ -1057,7 +1057,24 @@ pub async fn run(mut cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender
|
|||||||
// in-scope subdomains and fold them into the surface, so the run tests the
|
// in-scope subdomains and fold them into the surface, so the run tests the
|
||||||
// whole authorized domain, not just the seed host.
|
// whole authorized domain, not just the seed host.
|
||||||
let subdomains = if cfg.offline { String::new() } else { enumerate_subdomains(&cfg, &tx).await };
|
let subdomains = if cfg.offline { String::new() } else { enumerate_subdomains(&cfg, &tx).await };
|
||||||
let probe_facts = if subdomains.is_empty() { probe_facts } else { format!("{probe_facts}{subdomains}") };
|
// Live hosts discovered (parsed from the block) — fed to the tool-recon pass.
|
||||||
|
let live_hosts: Vec<String> = subdomains.lines()
|
||||||
|
.filter_map(|l| l.trim().strip_prefix("- https://").map(|r| r.split_whitespace().next().unwrap_or("").to_string()))
|
||||||
|
.filter(|h| !h.is_empty()).collect();
|
||||||
|
|
||||||
|
// KALI TOOL-RECON: run the real recon pipeline (gau/katana URL harvest +
|
||||||
|
// targeted nuclei) inside the sandbox (or on host tools), then let the LLM
|
||||||
|
// refine on top of the output. Provisions the toolbox in Kali on demand.
|
||||||
|
let tool_recon = if cfg.offline {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
if let Some(sb) = engagement_sandbox(&cfg) {
|
||||||
|
kali_provision_recon_tools(&sb, &tx).await;
|
||||||
|
}
|
||||||
|
kali_tool_recon(&cfg, &live_hosts, &tx).await
|
||||||
|
};
|
||||||
|
|
||||||
|
let probe_facts = format!("{probe_facts}{subdomains}{tool_recon}");
|
||||||
|
|
||||||
let recon = if cfg.offline {
|
let recon = if cfg.offline {
|
||||||
let _ = tx.send("recon: offline mode — skipping model calls".into()).await;
|
let _ = tx.send("recon: offline mode — skipping model calls".into()).await;
|
||||||
@@ -2810,6 +2827,15 @@ async fn finish(cfg: RunConfig, _lib: &Library, pool: &ModelPool, recon: String,
|
|||||||
let _ = tx.send(format!("notify: phase complete — {} validated finding(s) [{}]", findings.len(), sev)).await;
|
let _ = tx.send(format!("notify: phase complete — {} validated finding(s) [{}]", findings.len(), sev)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tear the Kali sandbox down at the end of the engagement (spin up for the
|
||||||
|
// run, kill it after), unless the operator asked to keep it.
|
||||||
|
if cfg.sandbox.is_some() && !std::env::var("NEUROSPLOIT_KEEP_SANDBOX").map(|v| v == "1" || v == "true").unwrap_or(false) {
|
||||||
|
if let Some(sb) = engagement_sandbox(&cfg) {
|
||||||
|
sb.teardown().await;
|
||||||
|
let _ = tx.send("notify: 📦 Kali sandbox torn down".into()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RunOutput {
|
RunOutput {
|
||||||
target: cfg.target.clone(),
|
target: cfg.target.clone(),
|
||||||
workdir: cfg.workdir.clone().unwrap_or_default(),
|
workdir: cfg.workdir.clone().unwrap_or_default(),
|
||||||
@@ -4104,6 +4130,115 @@ async fn recon_tool(cfg: &RunConfig, tool: &str, command: &str) -> Option<String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Sandbox handle for the engagement when `--sandbox` is set (the container
|
||||||
|
/// was ensured up earlier in run()). None when not sandboxed.
|
||||||
|
fn engagement_sandbox(cfg: &RunConfig) -> Option<crate::sandbox::Sandbox> {
|
||||||
|
let image = cfg.sandbox.as_deref()?;
|
||||||
|
let mut sc = crate::sandbox::SandboxConfig::default();
|
||||||
|
if !image.trim().is_empty() { sc = sc.with_image(image); }
|
||||||
|
crate::sandbox::Sandbox::new(sc).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install the recon toolbox in the Kali sandbox on demand (idempotent — skips
|
||||||
|
/// anything already present). Runs once per engagement. Best-effort: a tool that
|
||||||
|
/// fails to install just won't be used.
|
||||||
|
async fn kali_provision_recon_tools(sb: &crate::sandbox::Sandbox, tx: &Sender<String>) {
|
||||||
|
// Which recon tools are missing?
|
||||||
|
let check = sb.exec("for t in subfinder httpx katana gau waybackurls nuclei naabu dnsx assetfinder gf qsreplace anew; do command -v $t >/dev/null 2>&1 || echo $t; done").await;
|
||||||
|
let missing: Vec<String> = check.map(|o| o.stdout.lines().map(|l| l.trim().to_string()).filter(|l| !l.is_empty()).collect()).unwrap_or_default();
|
||||||
|
if missing.is_empty() { return; }
|
||||||
|
let _ = tx.send(format!("📦 provisioning Kali recon tools: {}", missing.join(", "))).await;
|
||||||
|
// ProjectDiscovery + bug-bounty tools install via `go install`; a couple via apt.
|
||||||
|
// Install Go first if needed, then the PD suite in one shot.
|
||||||
|
let _ = sb.exec("command -v go >/dev/null 2>&1 || (apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq golang-go) ; true").await;
|
||||||
|
let go = |pkg: &str| format!("GOBIN=/usr/local/bin go install -v {pkg}@latest 2>/dev/null; true");
|
||||||
|
let pd: &[(&str, &str)] = &[
|
||||||
|
("subfinder", "github.com/projectdiscovery/subfinder/v2/cmd/subfinder"),
|
||||||
|
("httpx", "github.com/projectdiscovery/httpx/cmd/httpx"),
|
||||||
|
("katana", "github.com/projectdiscovery/katana/cmd/katana"),
|
||||||
|
("nuclei", "github.com/projectdiscovery/nuclei/v3/cmd/nuclei"),
|
||||||
|
("naabu", "github.com/projectdiscovery/naabu/v2/cmd/naabu"),
|
||||||
|
("dnsx", "github.com/projectdiscovery/dnsx/cmd/dnsx"),
|
||||||
|
("gau", "github.com/lc/gau/v2/cmd/gau"),
|
||||||
|
("waybackurls", "github.com/tomnomnom/waybackurls"),
|
||||||
|
("assetfinder", "github.com/tomnomnom/assetfinder"),
|
||||||
|
("gf", "github.com/tomnomnom/gf"),
|
||||||
|
("qsreplace", "github.com/tomnomnom/qsreplace"),
|
||||||
|
("anew", "github.com/tomnomnom/anew"),
|
||||||
|
];
|
||||||
|
for (bin, pkg) in pd {
|
||||||
|
if missing.iter().any(|m| m == bin) { let _ = sb.exec(&go(pkg)).await; }
|
||||||
|
}
|
||||||
|
// nuclei templates (quietly).
|
||||||
|
if missing.iter().any(|m| m == "nuclei") { let _ = sb.exec("nuclei -update-templates -silent 2>/dev/null; true").await; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deterministic TOOL-RECON phase: run the real bug-bounty recon pipeline inside
|
||||||
|
/// the Kali sandbox (or on the host) against the live hosts, and return a
|
||||||
|
/// structured block (URL harvest + targeted nuclei quick-wins + gf-flagged
|
||||||
|
/// candidate URLs) to feed the LLM refine phase. This is the "recon by tools,
|
||||||
|
/// then the LLM works on top of the output" the operator asked for.
|
||||||
|
async fn kali_tool_recon(cfg: &RunConfig, hosts: &[String], tx: &Sender<String>) -> String {
|
||||||
|
// Need either a sandbox or host tools; the recon_tool() helper handles both.
|
||||||
|
let seed = crate::scope::host_of(&cfg.target);
|
||||||
|
let mut targets: Vec<String> = Vec::new();
|
||||||
|
if !seed.is_empty() { targets.push(seed); }
|
||||||
|
for h in hosts { if !targets.contains(h) { targets.push(h.clone()); } }
|
||||||
|
targets.truncate(12); // bound the deterministic pass; the LLM covers the rest
|
||||||
|
if targets.is_empty() { return String::new(); }
|
||||||
|
|
||||||
|
let _ = tx.send(format!("🛠 tool-recon over {} host(s) (gau/katana + targeted nuclei)…", targets.len())).await;
|
||||||
|
let mut urls: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
|
||||||
|
let mut nuclei_hits: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
for host in &targets {
|
||||||
|
// URL harvest (historical + crawl).
|
||||||
|
let harvest = format!("( echo {host} | gau --threads 20 2>/dev/null; echo {host} | waybackurls 2>/dev/null; katana -u https://{host} -d 2 -jc -silent 2>/dev/null ) | sort -u | head -400");
|
||||||
|
if let Some(out) = recon_tool(cfg, "gau", &harvest).await {
|
||||||
|
for l in out.lines() { let u = l.trim(); if u.starts_with("http") { urls.insert(u.to_string()); } }
|
||||||
|
}
|
||||||
|
// Targeted nuclei quick-wins (exposures/misconfig/takeover), high signal only.
|
||||||
|
let nuc = format!("nuclei -u https://{host} -t exposures/,misconfiguration/,takeovers/ -severity critical,high,medium -silent -nc 2>/dev/null | head -40");
|
||||||
|
if let Some(out) = recon_tool(cfg, "nuclei", &nuc).await {
|
||||||
|
for l in out.lines() { let t = l.trim(); if !t.is_empty() { nuclei_hits.push(t.to_string()); } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if urls.is_empty() && nuclei_hits.is_empty() {
|
||||||
|
let _ = tx.send("🛠 tool-recon: no tools available or no output (LLM recon continues)".into()).await;
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
// gf-flag candidate URLs by class for the exploitation phase.
|
||||||
|
let url_list = urls.iter().cloned().collect::<Vec<_>>().join("\n");
|
||||||
|
let mut gf_block = String::new();
|
||||||
|
if !url_list.is_empty() {
|
||||||
|
for pat in ["sqli", "xss", "ssrf", "redirect", "lfi", "idor"] {
|
||||||
|
let cmd = format!("printf '%s' {:?} | gf {pat} 2>/dev/null | head -15", url_list.chars().take(8000).collect::<String>());
|
||||||
|
if let Some(out) = recon_tool(cfg, "gf", &cmd).await {
|
||||||
|
let hits: Vec<&str> = out.lines().map(|l| l.trim()).filter(|l| l.starts_with("http")).collect();
|
||||||
|
if !hits.is_empty() { gf_block.push_str(&format!(" {pat}: {}\n", hits.join(" "))); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = tx.send(format!("🛠 tool-recon: {} URL(s) harvested, {} nuclei hit(s){}", urls.len(), nuclei_hits.len(),
|
||||||
|
if gf_block.is_empty() { String::new() } else { " · gf-flagged candidates added".into() })).await;
|
||||||
|
|
||||||
|
let mut block = String::from("\n\nTOOL-RECON OUTPUT (deterministic — work on top of this, verify each before reporting):\n");
|
||||||
|
if !nuclei_hits.is_empty() {
|
||||||
|
block.push_str("Nuclei quick-wins (CONFIRM each with your own request — do not report a template name as a finding):\n");
|
||||||
|
for h in nuclei_hits.iter().take(40) { block.push_str(&format!("- {h}\n")); }
|
||||||
|
}
|
||||||
|
if !gf_block.is_empty() {
|
||||||
|
block.push_str("Candidate URLs by vuln class (gf-flagged — TEST these parameters):\n");
|
||||||
|
block.push_str(&gf_block);
|
||||||
|
}
|
||||||
|
if !urls.is_empty() {
|
||||||
|
block.push_str(&format!("Harvested URLs (sample of {}):\n", urls.len()));
|
||||||
|
for u in urls.iter().take(80) { block.push_str(&format!("- {u}\n")); }
|
||||||
|
}
|
||||||
|
block
|
||||||
|
}
|
||||||
|
|
||||||
async fn enumerate_subdomains(cfg: &RunConfig, tx: &Sender<String>) -> String {
|
async fn enumerate_subdomains(cfg: &RunConfig, tx: &Sender<String>) -> String {
|
||||||
// Which apex(es) are authorized domain-wide?
|
// Which apex(es) are authorized domain-wide?
|
||||||
let apexes: Vec<String> = cfg.scope.hard.iter().filter_map(|p| {
|
let apexes: Vec<String> = cfg.scope.hard.iter().filter_map(|p| {
|
||||||
|
|||||||
@@ -209,7 +209,46 @@ impl Sandbox {
|
|||||||
///
|
///
|
||||||
/// Returns a human-readable status. Idempotent: a second call on an
|
/// Returns a human-readable status. Idempotent: a second call on an
|
||||||
/// already-running container is a no-op, so the pipeline can call it freely.
|
/// already-running container is a no-op, so the pipeline can call it freely.
|
||||||
|
/// Is the container ENGINE (daemon) responsive? `<bin> info` only succeeds
|
||||||
|
/// when the daemon is up — the binary being installed is not enough.
|
||||||
|
async fn engine_up(&self) -> bool {
|
||||||
|
run(self.runtime.bin(), &["info"], Duration::from_secs(10)).await
|
||||||
|
.map(|o| o.code == 0).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the container engine if it's installed but not running — so a run
|
||||||
|
/// with `--sandbox` doesn't fail just because Docker Desktop/Colima/the
|
||||||
|
/// docker daemon/the podman machine wasn't started. Tries the common starts
|
||||||
|
/// for the platform, then polls `<bin> info` until it comes up.
|
||||||
|
async fn start_engine(&self) -> Result<(), String> {
|
||||||
|
if self.engine_up().await { return Ok(()); }
|
||||||
|
let starters: &[(&str, &[&str])] = match self.runtime {
|
||||||
|
Runtime::Docker => &[
|
||||||
|
("colima", &["start"]), // macOS/Linux, common
|
||||||
|
("systemctl", &["start", "docker"]), // Linux systemd
|
||||||
|
("service", &["docker", "start"]), // Linux sysv
|
||||||
|
("open", &["-a", "Docker"]), // macOS Docker Desktop
|
||||||
|
],
|
||||||
|
Runtime::Podman => &[
|
||||||
|
("podman", &["machine", "start"]),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
for (bin, args) in starters {
|
||||||
|
if !which(bin) { continue; }
|
||||||
|
let _ = run(bin, args, Duration::from_secs(120)).await;
|
||||||
|
// Poll for the daemon to come up (engines take a few seconds).
|
||||||
|
for _ in 0..30 {
|
||||||
|
if self.engine_up().await { return Ok(()); }
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.engine_up().await { Ok(()) }
|
||||||
|
else { Err(format!("the {} engine is installed but not running, and could not be started automatically — start it (e.g. `colima start`, `open -a Docker`, or `sudo systemctl start docker`) and retry", self.runtime.bin())) }
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn ensure(&self) -> Result<String, String> {
|
pub async fn ensure(&self) -> Result<String, String> {
|
||||||
|
// Bring the engine up first — installed-but-not-running is the common case.
|
||||||
|
self.start_engine().await?;
|
||||||
if self.is_up().await {
|
if self.is_up().await {
|
||||||
return Ok(format!("{} container `{}` already running", self.runtime.bin(), self.cfg.name));
|
return Ok(format!("{} container `{}` already running", self.runtime.bin(), self.cfg.name));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>NeuroSploit v4.2.3 — Console</title>
|
<title>NeuroSploit v4.2.4 — Console</title>
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧠</text></svg>">
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧠</text></svg>">
|
||||||
<link rel="stylesheet" href="/vendor/xterm.css" />
|
<link rel="stylesheet" href="/vendor/xterm.css" />
|
||||||
<link rel="stylesheet" href="/style.css" />
|
<link rel="stylesheet" href="/style.css" />
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<div class="sb-groups" id="sbGroups"><!-- populated by app.js --></div>
|
<div class="sb-groups" id="sbGroups"><!-- populated by app.js --></div>
|
||||||
|
|
||||||
<div class="sb-bottom">
|
<div class="sb-bottom">
|
||||||
<span class="sb-version" id="sbVersion">v4.2.3</span>
|
<span class="sb-version" id="sbVersion">v4.2.4</span>
|
||||||
<div class="sb-bottom-actions">
|
<div class="sb-bottom-actions">
|
||||||
<button class="icon-btn" id="btnOpenAuth" title="Auth & API keys">🔑</button>
|
<button class="icon-btn" id="btnOpenAuth" title="Auth & API keys">🔑</button>
|
||||||
<button class="icon-btn" id="btnOpenRepl" title="Open terminal (Ctrl+`)">❭_</button>
|
<button class="icon-btn" id="btnOpenRepl" title="Open terminal (Ctrl+`)">❭_</button>
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
'use strict';
|
'use strict';
|
||||||
/**
|
/**
|
||||||
* NeuroSploit v4.2.3 — web console backend.
|
* NeuroSploit v4.2.4 — web console backend.
|
||||||
*
|
*
|
||||||
* Zero-dependency Node HTTP server that:
|
* Zero-dependency Node HTTP server that:
|
||||||
* - serves the static SPA in ./public
|
* - serves the static SPA in ./public
|
||||||
@@ -1487,7 +1487,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'GET' && p === '/api/meta') {
|
if (req.method === 'GET' && p === '/api/meta') {
|
||||||
return sendJson(res, 200, { version: "4.2.3", binary: BIN, root: ROOT });
|
return sendJson(res, 200, { version: "4.2.4", binary: BIN, root: ROOT });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- providers / API keys (in-memory only, never persisted) ----
|
// ---- providers / API keys (in-memory only, never persisted) ----
|
||||||
@@ -1521,7 +1521,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
loadPersistedJobs();
|
loadPersistedJobs();
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log(`NeuroSploit v4.2.3 web console → http://localhost:${PORT}`);
|
console.log(`NeuroSploit v4.2.4 web console → http://localhost:${PORT}`);
|
||||||
console.log(` binary : ${BIN || '(not found — build neurosploit-rs first)'}`);
|
console.log(` binary : ${BIN || '(not found — build neurosploit-rs first)'}`);
|
||||||
console.log(` agents : ${AGENTS_DIR}`);
|
console.log(` agents : ${AGENTS_DIR}`);
|
||||||
console.log(` runs : ${RUNS_DIR}`);
|
console.log(` runs : ${RUNS_DIR}`);
|
||||||
|
|||||||
Reference in new issue
Block a user