mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-08-15 06:00:28 +02:00
feat: whitebox/greybox/repl accept a GitHub URL (auto-clone)
`whitebox <arg>`, `greybox --repo <arg>`, `tui --repo`, and the REPL `/repo` now accept a git URL (https://github.com/owner/repo[.git], git@…, ssh://, *.git) or an `owner/repo` shorthand. A new resolve_source() shallow-clones it into <base>/repos/<name> (cached, .gitignored) and reviews it; existing local paths are used unchanged. Works identically with API-key (--model) and --subscription. Verified: `neurosploit whitebox https://github.com/digininja/DVWA --offline` clones DVWA and runs the 78 code agents over 120KB of source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
489b3abd3f
commit
761d3df444
@@ -104,3 +104,7 @@ data/repl_runs.json
|
|||||||
data/repl_history.txt
|
data/repl_history.txt
|
||||||
.neurosploit/
|
.neurosploit/
|
||||||
/tmp/*
|
/tmp/*
|
||||||
|
|
||||||
|
# Cloned source repos (whitebox/greybox from a git URL)
|
||||||
|
repos/
|
||||||
|
neurosploit-rs/repos/
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ and severity-calibrated).
|
|||||||
- **5 new doctrine meta-agents** (`agents_md/meta/`): `exploit_depth_doctrine`,
|
- **5 new doctrine meta-agents** (`agents_md/meta/`): `exploit_depth_doctrine`,
|
||||||
`finding_chainer`, `artifact_decoder`, `token_auditor`, `report_calibrator`
|
`finding_chainer`, `artifact_decoder`, `token_auditor`, `report_calibrator`
|
||||||
(meta agents 17 → 22; total library 343 → 348).
|
(meta agents 17 → 22; total library 343 → 348).
|
||||||
|
- **Source from a GitHub URL.** `whitebox` / `greybox --repo` (and the REPL
|
||||||
|
`/repo`) now accept a **git URL** (`https://github.com/owner/repo[.git]`) or an
|
||||||
|
`owner/repo` shorthand — the repo is cloned (shallow) into `<base>/repos/` and
|
||||||
|
reviewed automatically, no manual `git clone` needed:
|
||||||
|
```bash
|
||||||
|
neurosploit whitebox https://github.com/digininja/DVWA \
|
||||||
|
--subscription --model anthropic:claude-opus-4-8 -v
|
||||||
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,10 @@ enum Cmd {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
},
|
},
|
||||||
/// White-box: analyse a local repository's source code for vulnerabilities.
|
/// White-box: analyse a repository's source code for vulnerabilities.
|
||||||
Whitebox {
|
Whitebox {
|
||||||
|
/// Local path, a GitHub URL (https://github.com/owner/repo[.git]) or an
|
||||||
|
/// `owner/repo` shorthand — git URLs are cloned automatically.
|
||||||
path: String,
|
path: String,
|
||||||
#[arg(long = "model")]
|
#[arg(long = "model")]
|
||||||
models: Vec<String>,
|
models: Vec<String>,
|
||||||
@@ -83,7 +85,7 @@ enum Cmd {
|
|||||||
},
|
},
|
||||||
/// Greybox: review a repo's source AND exploit the running app together.
|
/// Greybox: review a repo's source AND exploit the running app together.
|
||||||
Greybox {
|
Greybox {
|
||||||
/// Path to the source repository.
|
/// Source repo: local path, a GitHub URL, or `owner/repo` (cloned if a URL).
|
||||||
repo: String,
|
repo: String,
|
||||||
/// URL of the running application.
|
/// URL of the running application.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
@@ -230,6 +232,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
print_findings(&out);
|
print_findings(&out);
|
||||||
}
|
}
|
||||||
Cmd::Whitebox { path, models, max_agents, vote_n, offline, subscription, verbose } => {
|
Cmd::Whitebox { path, models, max_agents, vote_n, offline, subscription, verbose } => {
|
||||||
|
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;
|
||||||
cfg.vote_n = vote_n;
|
cfg.vote_n = vote_n;
|
||||||
@@ -243,6 +246,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
print_findings(&out);
|
print_findings(&out);
|
||||||
}
|
}
|
||||||
Cmd::Greybox { repo, url, models, creds, focus, max_agents, vote_n, offline, subscription, mcp, verbose } => {
|
Cmd::Greybox { repo, url, models, creds, focus, max_agents, vote_n, offline, subscription, mcp, verbose } => {
|
||||||
|
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);
|
||||||
cfg.repo = Some(repo);
|
cfg.repo = Some(repo);
|
||||||
@@ -260,6 +264,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
print_findings(&out);
|
print_findings(&out);
|
||||||
}
|
}
|
||||||
Cmd::Tui { url, models, repo, creds, focus, max_agents, vote_n, subscription, mcp } => {
|
Cmd::Tui { url, models, repo, creds, focus, max_agents, vote_n, subscription, mcp } => {
|
||||||
|
let repo = match repo { Some(r) => Some(resolve_source(&base, &r)?), None => None }; // github URL ok
|
||||||
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;
|
||||||
@@ -532,6 +537,45 @@ fn now_ts() -> u64 {
|
|||||||
SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0)
|
SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a source argument (white-box `path` / grey-box `--repo`) to a local
|
||||||
|
/// directory. A git URL (`https://…`, `git@…`, `ssh://…`, `*.git`) or a GitHub
|
||||||
|
/// `owner/repo` shorthand is **cloned** (shallow) into `<base>/repos/<name>` and
|
||||||
|
/// that path is returned; an existing local path is returned unchanged.
|
||||||
|
pub(crate) fn resolve_source(base: &Path, arg: &str) -> anyhow::Result<String> {
|
||||||
|
let is_url = arg.starts_with("http://") || arg.starts_with("https://")
|
||||||
|
|| arg.starts_with("git@") || arg.starts_with("ssh://") || arg.ends_with(".git");
|
||||||
|
// `owner/repo` GitHub shorthand: no scheme, exactly one slash, not a real path.
|
||||||
|
let is_shorthand = !is_url
|
||||||
|
&& !Path::new(arg).exists()
|
||||||
|
&& arg.matches('/').count() == 1
|
||||||
|
&& !arg.starts_with('.') && !arg.starts_with('/') && !arg.starts_with('~')
|
||||||
|
&& arg.chars().all(|c| c.is_ascii_alphanumeric() || "._-/".contains(c));
|
||||||
|
if !is_url && !is_shorthand {
|
||||||
|
return Ok(arg.to_string()); // already a local path
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = if is_shorthand { format!("https://github.com/{arg}") } else { arg.to_string() };
|
||||||
|
let name = sanitize(url.trim_end_matches('/').trim_end_matches(".git").rsplit('/').next().unwrap_or("repo"));
|
||||||
|
let repos_dir = base.join("repos");
|
||||||
|
std::fs::create_dir_all(&repos_dir).ok();
|
||||||
|
let dest = repos_dir.join(&name);
|
||||||
|
|
||||||
|
if dest.join(".git").is_dir() {
|
||||||
|
println!(" [*] repo cache hit → {} (delete it to re-clone)", dest.display());
|
||||||
|
return Ok(dest.display().to_string());
|
||||||
|
}
|
||||||
|
println!(" [*] cloning {url} → {}", dest.display());
|
||||||
|
let status = std::process::Command::new("git")
|
||||||
|
.args(["clone", "--depth", "1", &url, &dest.display().to_string()])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| anyhow::anyhow!("could not start `git clone` (is git installed?): {e}"))?;
|
||||||
|
if !status.success() {
|
||||||
|
std::fs::remove_dir_all(&dest).ok();
|
||||||
|
anyhow::bail!("git clone failed for {url}");
|
||||||
|
}
|
||||||
|
Ok(dest.display().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// Blocking yes/no prompt (default yes). Used after a graceful Ctrl-C.
|
/// Blocking yes/no prompt (default yes). Used after a graceful Ctrl-C.
|
||||||
fn ask_yes_no(q: &str) -> bool {
|
fn ask_yes_no(q: &str) -> bool {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|||||||
@@ -392,9 +392,15 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
|
|||||||
s.target = Some(t.clone()); println!(" target: {t}"); }
|
s.target = Some(t.clone()); println!(" target: {t}"); }
|
||||||
}
|
}
|
||||||
"/repo" => {
|
"/repo" => {
|
||||||
if arg.is_empty() { println!(" repo: {}", s.repo.clone().unwrap_or_else(|| "(none) — set with /repo <path>, clear with /repo clear".into())); }
|
if arg.is_empty() { println!(" repo: {}", s.repo.clone().unwrap_or_else(|| "(none) — set with /repo <path | github-url | owner/repo>, clear with /repo clear".into())); }
|
||||||
else if arg == "clear" { s.repo = None; println!(" repo cleared"); }
|
else if arg == "clear" { s.repo = None; println!(" repo cleared"); }
|
||||||
else { s.repo = Some(arg.to_string()); println!(" repo: {arg}"); }
|
else {
|
||||||
|
// Accept a local path OR a GitHub URL / owner-repo shorthand (cloned on set).
|
||||||
|
match crate::resolve_source(base, arg) {
|
||||||
|
Ok(p) => { s.repo = Some(p.clone()); println!(" repo: {p}"); }
|
||||||
|
Err(e) => println!(" \x1b[31mcould not resolve repo: {e}\x1b[0m"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"/auth" => {
|
"/auth" => {
|
||||||
if arg.is_empty() { println!(" auth: {}", s.auth.clone().unwrap_or_else(|| "(none) — set with /auth <header>, clear with /auth clear".into())); }
|
if arg.is_empty() { println!(" auth: {}", s.auth.clone().unwrap_or_else(|| "(none) — set with /auth <header>, clear with /auth clear".into())); }
|
||||||
|
|||||||
Reference in New Issue
Block a user