diff --git a/examples/scopes/engagement.example.yaml b/examples/scopes/engagement.example.yaml new file mode 100644 index 0000000..f6805d8 --- /dev/null +++ b/examples/scopes/engagement.example.yaml @@ -0,0 +1,56 @@ +# =========================================================================== +# NeuroSploit scope config — direct engagement (TEMPLATE) +# --------------------------------------------------------------------------- +# For a normal pentest where you have WRITTEN AUTHORIZATION from the asset +# owner (a signed SOW / contract / authorization letter) — no bug-bounty +# program or HackerOne needed. You, the operator, assert you are authorized +# for everything in `hard` below. Fill it from the engagement's authorization. +# +# You usually don't even need this file: in the REPL just run +# /authorize app.client.com *.client.com 10.0.0.0/24 +# or on the CLI +# neurosploit run app.client.com --in-scope "*.client.com" --in-scope 10.0.0.0/24 +# Use this file when the scope is large or you want it version-controlled with +# the engagement notes. +# +# HARD scope is the one thing you MUST set — it is the safety boundary (a host +# not covered here is refused before any request leaves). Everything else +# (rate, accounts, destructive verbs) is YOURS to tune for THIS engagement. +# =========================================================================== + +# --- HARD: everything you are authorized to test. ------------------------- +hard: + - app.client.example # a single host + - "*.client.example" # apex + all subdomains + - 10.0.0.0/24 # an internal range (reach it with --transport) + - https://api.client.example/v2 # or just one URL prefix + +# --- EXCLUDE: anything carved out of the authorization. ------------------- +exclude: + # - billing.client.example + # - "*.prod.client.example" # e.g. test staging only + +# --- SOFT: guardrails — tune these to the engagement's rules. ------------- +soft: + # Look-only hosts (recon, no payloads) — e.g. shared/third-party infra. + observe_only: [] + + # Direct engagements often authorize more than a bounty would. Set these to + # what the SOW allows: + allow_destructive_methods: false # true only if the authorization covers it (e.g. a staging env) + allow_account_creation: true # create test accounts to reach authed surface + max_accounts: 3 + max_requests_per_minute: 240 # raise for a lab / internal test, lower for fragile prod + + # Hard stops regardless of authorization — things that destroy data or DoS. + forbidden_payloads: + - "drop table" + - "truncate table" + - "delete from" + - "rm -rf /" + - "shutdown" + - "while(true)" + + notes: + - "Authorized under ; owner contact: ." + - "Test window: . Notify before any high-impact test." diff --git a/neurosploit-rs/app/src/repl.rs b/neurosploit-rs/app/src/repl.rs index abac0b3..850b2e2 100644 --- a/neurosploit-rs/app/src/repl.rs +++ b/neurosploit-rs/app/src/repl.rs @@ -150,7 +150,7 @@ pub(crate) const ACCEPTED: &[&str] = &[ "/context", "/continue", "/creds", "/diff", "/exclude", "/exit", "/expand", "/feed", "/finding", "/findings", "/focus", "/forget", "/full", "/go", "/goal", "/graph", "/guardrail", "/guardrails", "/help", "/history", "/idle", "/inscope", "/instructions", "/integration", "/integrations", "/key", "/log", - "/scope-file", "/scopefile", "/import-scope", "/authorization", "/authz", "/program", "/logs", "/mcp", "/memory", "/model", "/models", "/objective", "/objectives", "/observe", + "/authorize", "/grant", "/inscope-set", "/scope-file", "/scopefile", "/import-scope", "/authorization", "/authz", "/program", "/logs", "/mcp", "/memory", "/model", "/models", "/objective", "/objectives", "/observe", "/observe-only", "/offline", "/onboard", "/only", "/oos", "/outofscope", "/policy", "/providers", "/proxy", "/class", "/classes", "/focus-class", "/research", "/quick", "/economy", "/eco", "/q", "/quit", "/recon", "/pause", "/repo", "/report", "/results", "/resume", "/retest", "/revalidate", "/run", "/runs", @@ -843,6 +843,44 @@ pub async fn repl(base: &Path, auth: SessionAuth) -> anyhow::Result<()> { s.objective = Some(arg.to_string()); println!(" objective set — steers what agents prioritise and what counts as impact"); } + "/authorize" | "/inscope-set" | "/grant" => { + // Declare the whole authorized scope in ONE line for a direct + // engagement (a client test with written authorization — no + // bug-bounty program or capability token needed). Accepts + // multiple hosts / *.domains / CIDRs / URL-prefixes, sets them + // as the hard grant, and pins it so a later /target doesn't + // re-derive. The operator asserts they are authorized for these. + if arg.trim().is_empty() || arg.trim() == "clear" { + if arg.trim() == "clear" { s.policy.hard.clear(); s.scope_pinned = false; println!(" authorized scope cleared"); continue; } + println!(" declare what you're authorized to test (one line, direct engagement):"); + println!(" /authorize app.client.com *.client.com 10.0.0.0/24 https://api.client.com/v2"); + println!(" current: {}", s.policy.summary()); + continue; + } + let entries: Vec<&str> = arg.split([',', ';', ' ']).map(str::trim).filter(|x| !x.is_empty()).collect(); + let keep_exclude = s.policy.exclude.clone(); + let keep_soft = s.policy.soft.clone(); + let mut np = harness::scope::ScopePolicy::default(); + np.exclude = keep_exclude; + np.soft = keep_soft; + let mut added = 0usize; + for e in &entries { let before = np.hard.len(); np.allow(e); added += np.hard.len().saturating_sub(before); } + if np.hard.is_empty() { + println!(" \x1b[33mnothing valid to authorize in: {}\x1b[0m", arg); + } else { + s.policy = np; + s.scope_pinned = true; + // Seed the target from the first entry if none set, so /run works immediately. + if s.target.is_none() { + let first = entries[0]; + let h = harness::scope::host_of(first); + let seed = h.strip_prefix("*.").map(|a| format!("https://{a}")).unwrap_or_else(|| if first.contains("://") { first.to_string() } else { format!("https://{}", harness::scope::host_of(first)) }); + s.target = Some(seed); + } + println!(" \x1b[1;32m🔓 authorized scope set\x1b[0m ({} entr{}) — {}", added, if added == 1 { "y" } else { "ies" }, s.policy.summary()); + println!(" \x1b[2mdirect engagement — you assert written authorization for these assets. Tune limits with /guardrail · exclude with /scope-out · /run to start.\x1b[0m"); + } + } "/scope-file" | "/scopefile" | "/import-scope" => { let path = arg.trim().trim_start_matches('@'); if path.is_empty() { @@ -2421,6 +2459,7 @@ fn help() { h("/chain ", "attack-chain depth (post-exploitation pivots; 0 = off)"); h("/recon <1-4>", "recon intensity: 1 quick · 2 standard · 3 deep · 4 exhaustive (installs tools)"); h("/class ", "focus a run on vuln classes (idor,sqli,xss,ssrf,…) — pins the matching agents"); + h("/authorize ", "direct engagement: declare the whole authorized scope in one line (hosts/*.domains/CIDRs/URLs) — no program needed"); h("/scope-file ", "import a ready scope config (hard allowlist + exclusions + guardrails) — e.g. examples/scopes/rockstargames.yaml"); h("/authorization ", "declare the program/authorization (e.g. a bug-bounty URL) — recorded; does NOT widen scope"); h("/research", "whitebox/greybox: hunt a NOVEL, CVE-reportable bug (known-CVE dedup + patch-diff variant analysis)");