feat: engagement objective + out-of-scope context for prompts (#34)

Add two operator inputs that give agents more test context, both
funneled through operator_directives() so they reach every recon/
exploit prompt (web, host, ai, skills):

- objective: WHY the test runs and WHAT counts as impact — rendered
  as high-priority ENGAGEMENT OBJECTIVE context.
- out_of_scope: hosts/paths/techniques to exclude — rendered as a
  HARD CONSTRAINT the agents must skip and never report against.

REPL: /objective and /scope-out commands (accumulating), optional
onboarding prompts, /show + /help + Tab-complete, session.json
persistence (serde default for back-compat).
CLI: neurosploit run --objective --out-of-scope.

Version unchanged (3.6.5).


Claude-Session: https://claude.ai/code/session_018BGLy4j5qsqqid6CoovowC

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Joas A Santos
2026-07-31 15:26:17 -03:00
committed by GitHub
co-authored by Claude Opus 4.8
parent f3da46886f
commit e267afb7b6
4 changed files with 87 additions and 2 deletions
@@ -35,9 +35,17 @@ Base everything on real observed responses — never assume. Reply with a COMPAC
/// recon/exploit prompts so the engagement is steered as the user asked.
fn operator_directives(cfg: &RunConfig) -> String {
let mut s = String::new();
if let Some(obj) = cfg.objective.as_deref().filter(|x| !x.trim().is_empty()) {
s.push_str(&format!("ENGAGEMENT OBJECTIVE — the goal and context of this test; let it shape what you prioritise and what counts as impact: {obj}\n"));
}
if let Some(focus) = cfg.instructions.as_deref().filter(|x| !x.trim().is_empty()) {
s.push_str(&format!("OPERATOR FOCUS — prioritise this: {focus}\n"));
}
if let Some(oos) = cfg.out_of_scope.as_deref().filter(|x| !x.trim().is_empty()) {
s.push_str(&format!(
"OUT OF SCOPE — HARD CONSTRAINT, do NOT test, probe, or interact with any of the following; \
skip them entirely even if reachable, and never report findings against them: {oos}\n"));
}
if let Some(auth) = cfg.auth.as_deref().filter(|x| !x.trim().is_empty()) {
s.push_str(&format!("AUTHENTICATION — test as an authenticated user; send this with each request: {auth}\n"));
}
@@ -140,6 +140,17 @@ pub struct RunConfig {
/// execution (e.g. "focus on injection and broken access control").
#[serde(default)]
pub instructions: Option<String>,
/// Engagement objective / rules-of-engagement context: WHY this test is run
/// and WHAT matters (e.g. "pre-launch review of the checkout flow; prove any
/// path to unauthorized order access"). Prepended to prompts as high-priority
/// context so agents understand the goal, not just the surface.
#[serde(default)]
pub objective: Option<String>,
/// Explicit out-of-scope exclusions the agents MUST NOT touch (e.g. hosts,
/// paths, techniques, or actions). Rendered as a hard constraint in every
/// recon/exploit prompt. Optional; empty means "nothing excluded".
#[serde(default)]
pub out_of_scope: Option<String>,
/// Authentication material to use against the target so agents test as an
/// authenticated user (e.g. "Authorization: Bearer <jwt>" or "Cookie: session=...").
#[serde(default)]
@@ -209,6 +220,8 @@ impl RunConfig {
rl_path: None,
verbose: false,
instructions: None,
objective: None,
out_of_scope: None,
auth: None,
repo: None,
pinned: Vec::new(),