From 5836dc3d8d129fce5d1e90edae835dcaa85e5b91 Mon Sep 17 00:00:00 2001 From: CyberSecurityUP Date: Fri, 16 Oct 2026 12:11:40 -0300 Subject: [PATCH] fix(http): plain NeuroSploit/ UA + attribution in X-NeuroSploit-Scan header The verbose default User-Agent ("NeuroSploit/ (authorized security assessment; +https://...)") was getting blocked/fingerprinted by WAFs and CDNs, causing false negatives and breaking scans. Shrink the default UA to a plain `NeuroSploit/` token and move attribution to headers carried on every request: User-Agent: NeuroSploit/ Accept: */* X-NeuroSploit-Scan: New pipeline::default_scan_headers() is applied on every harness HTTP client (probe.rs + the authz-matrix and subdomain-enum clients), so defenders still see the scan via the header even after a browser-UA switch. The web console shells the same binary and sets no UA override, so CLI and Web stay in sync on the new default. Tests cover the plain UA and the attribution header. Co-Authored-By: Claude Opus 4.8 --- neurosploit-rs/crates/harness/src/pipeline.rs | 43 +++++++++++++++++-- neurosploit-rs/crates/harness/src/probe.rs | 1 + 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/neurosploit-rs/crates/harness/src/pipeline.rs b/neurosploit-rs/crates/harness/src/pipeline.rs index 9a0754b..af873f9 100644 --- a/neurosploit-rs/crates/harness/src/pipeline.rs +++ b/neurosploit-rs/crates/harness/src/pipeline.rs @@ -404,9 +404,26 @@ fn tool_doctrine(mcp_on: bool) -> String { /// Default identifying User-Agent so target owners (and the operator) can tell /// traffic came from NeuroSploit — and so authorship of a scan is unambiguous. +/// Deliberately a plain `NeuroSploit/` token: the verbose +/// "authorized security assessment" comment made WAFs/CDNs block or fingerprint +/// the scan outright (false negatives). Attribution still travels in the +/// `X-NeuroSploit-Scan` header (see `default_scan_headers`), which every request +/// carries, so the scan stays identifiable to defenders regardless of the UA. pub fn default_user_agent() -> String { - format!("NeuroSploit/{} (authorized security assessment; +https://github.com/JoasASantos/NeuroSploit)", - env!("CARGO_PKG_VERSION")) + format!("NeuroSploit/{}", env!("CARGO_PKG_VERSION")) +} + +/// Headers stamped on every request the harness makes, so attribution lives in +/// the HEADER (not just the UA) and survives a browser-UA switch: +/// `Accept: */*` and `X-NeuroSploit-Scan: `. +pub fn default_scan_headers() -> reqwest::header::HeaderMap { + use reqwest::header::{HeaderMap, HeaderValue, ACCEPT}; + let mut h = HeaderMap::new(); + h.insert(ACCEPT, HeaderValue::from_static("*/*")); + if let Ok(v) = HeaderValue::from_str(env!("CARGO_PKG_VERSION")) { + h.insert("X-NeuroSploit-Scan", v); + } + h } /// A realistic current-browser User-Agent. Use this when the identifying UA @@ -428,7 +445,7 @@ fn ua_line() -> String { format!( "IDENTIFY & UA STRATEGY (attribution lives in the HEADER, not just the UA): add \ `-H \"X-NeuroSploit-Scan: {ver}\"` to EVERY request — that is what keeps the scan attributable. \ - For the User-Agent, start with `-A \"{ua}\"`. BUT: if the target sits behind a WAF/CDN \ + For the User-Agent, start with the plain `-A \"{ua}\"`. BUT: if the target sits behind a WAF/CDN \ (Cloudflare, Akamai, …) and the scanner UA gets blocked, challenged, rate-limited, or returns \ DIFFERENT content than a browser would, SWITCH to a realistic browser UA `-A \"{browser}\"` — a \ self-declaring scanner UA causes FALSE NEGATIVES, and the X-NeuroSploit-Scan header keeps you \ @@ -922,6 +939,7 @@ async fn authz_matrix_pass(cfg: &RunConfig, recon: &str, tx: &Sender) -> let client = reqwest::Client::builder() .danger_accept_invalid_certs(true) .timeout(std::time::Duration::from_secs(30)) + .default_headers(default_scan_headers()) .user_agent(cfg.user_agent.clone().unwrap_or_else(default_user_agent)) .build() .unwrap_or_default(); @@ -4674,6 +4692,7 @@ async fn enumerate_subdomains(cfg: &RunConfig, tx: &Sender) -> String { let client = reqwest::Client::builder() .danger_accept_invalid_certs(true) .timeout(std::time::Duration::from_secs(30)) + .default_headers(default_scan_headers()) .user_agent(cfg.user_agent.clone().unwrap_or_else(|| default_user_agent())) .build().unwrap_or_default(); @@ -5384,4 +5403,22 @@ mod dedup_tests { assert_eq!(endpoint_key("https://h.test/Account/Login?x=1"), "h.test/account/login"); assert_eq!(endpoint_key("https://www.h.test/a/"), "h.test/a"); } + + /// The default UA is a plain token — no "authorized security assessment" + /// comment that WAFs/CDNs block or fingerprint. Attribution moves to the + /// X-NeuroSploit-Scan header, carried on every request. + #[test] + fn the_default_user_agent_is_a_plain_token() { + let ua = default_user_agent(); + assert!(ua.starts_with("NeuroSploit/"), "{ua}"); + assert!(!ua.contains("authorized"), "verbose comment causes WAF false negatives: {ua}"); + assert!(!ua.contains('('), "no parenthetical comment in the UA: {ua}"); + } + + #[test] + fn every_request_carries_the_scan_attribution_header() { + let h = default_scan_headers(); + assert_eq!(h.get("X-NeuroSploit-Scan").unwrap(), env!("CARGO_PKG_VERSION")); + assert_eq!(h.get(reqwest::header::ACCEPT).unwrap(), "*/*"); + } } diff --git a/neurosploit-rs/crates/harness/src/probe.rs b/neurosploit-rs/crates/harness/src/probe.rs index f00e69e..9457a06 100644 --- a/neurosploit-rs/crates/harness/src/probe.rs +++ b/neurosploit-rs/crates/harness/src/probe.rs @@ -123,6 +123,7 @@ fn client() -> reqwest::Client { .timeout(Duration::from_secs(15)) .danger_accept_invalid_certs(true) .redirect(redirect) + .default_headers(crate::pipeline::default_scan_headers()) .user_agent(ua); if let Ok(p) = std::env::var("NEUROSPLOIT_PROXY") { if !p.trim().is_empty() {