mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-09-30 13:09:36 +02:00
feat(scope): --scope-file YAML loader + web Scoping/Guardrails UI
Hard scoping was already enforced in code (every request passes
ScopePolicy::check_request; exclude beats allowlist; capability token caps
it; out-of-scope findings withheld + audited). What was missing was a way to
author that boundary from a file or the web form instead of only CLI flags.
- scope.rs: ScopePolicy::from_yaml / from_file — a dependency-free parser for
the friendly string format (app.example.com, *.wildcard, CIDR, url-prefix),
the same strings Pattern::parse already takes, NOT the raw serde {kind,value}
shape. Strict in one direction: an unreadable file errors, an empty hard list
authorizes nothing (a safe failure, but the operator's choice, not a typo).
- CLI: --scope-file <yaml>. Loaded before authorization so --in-scope adds to
it and the capability grant still caps it.
- Web: a full Scoping & Guardrails section in the Authorization tab — hard
scope, exclusions, observe-only, destructive-method + account-creation
toggles, max accounts, rate limit, forbidden payloads, notes. The server
materializes a scope YAML and passes --scope-file; notes stay labelled
"guidance, NOT enforced" so prose is never mistaken for a control.
- examples/scope.example.yaml documents the format.
End-to-end verified: web form -> YAML -> Rust loader -> enforced boundary.
332 tests (+4).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f1fb6b8bc7
commit
8894649ccb
8 files changed
+413
-3
No files matched your search
@@ -455,6 +455,27 @@ function budgetSummary() {
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
/// Gather the Scoping/Guardrails form into the object the server turns into a
|
||||
/// scope YAML. A hard list is what makes it a boundary; without one the server
|
||||
/// sends nothing and the run keeps its target+flags behaviour.
|
||||
function collectScope() {
|
||||
const lines = (id) => ($(`#${id}`)?.value || '').split(/[\n,;]+/).map((x) => x.trim()).filter(Boolean);
|
||||
const hard = lines('scopeHard');
|
||||
const scope = {
|
||||
hard,
|
||||
exclude: lines('scopeExclude'),
|
||||
observeOnly: lines('scopeObserve'),
|
||||
allowDestructive: $('#scopeDestructive')?.checked || false,
|
||||
allowAccountCreation: $('#scopeAccounts') ? $('#scopeAccounts').checked : true,
|
||||
maxAccounts: $('#scopeMaxAccounts')?.value ?? '',
|
||||
rateLimit: $('#scopeRate')?.value ?? '',
|
||||
forbidden: lines('scopeForbidden'),
|
||||
notes: lines('scopeNotes'),
|
||||
};
|
||||
// Only meaningful when a boundary was actually drawn.
|
||||
return hard.length ? scope : undefined;
|
||||
}
|
||||
|
||||
function renderReview() {
|
||||
const target = $('#fieldTarget').value.trim();
|
||||
const repo = $('#fieldRepo').value.trim();
|
||||
@@ -473,6 +494,7 @@ function renderReview() {
|
||||
{ k: 'Budget', v: budgetSummary() },
|
||||
{ k: 'Egress', v: state.authz.transport || 'direct' },
|
||||
{ k: 'Out-of-band', v: state.authz.oobDomain ? `*.${state.authz.oobDomain}` : 'none — blind classes stay leads' },
|
||||
{ k: 'Hard scope', v: (() => { const sc = collectScope(); return sc ? `${sc.hard.length} rule(s), ${sc.exclude.length} excluded, ${sc.rateLimit || '∞'}rpm${sc.allowDestructive ? ', destructive ON' : ''}` : 'target + authorized hosts only'; })() },
|
||||
{ k: 'Intercept', v: $('#fieldIntercept').value === 'off' ? 'direct' : $('#fieldIntercept').value },
|
||||
{ k: 'Sandbox', v: $('#fieldSandbox').value ? 'Kali container' : 'host' },
|
||||
{ k: 'PoC re-validation', v: $('#fieldRevalidatePoc').checked ? 'on' : 'off' },
|
||||
@@ -537,6 +559,7 @@ async function startExploitation() {
|
||||
oobHttp: state.authz.oobHttp || undefined,
|
||||
oobDns: state.authz.oobDns || undefined,
|
||||
sms: state.authz.sms || undefined,
|
||||
scope: collectScope(),
|
||||
};
|
||||
|
||||
$('#btnLaunch').disabled = true;
|
||||
|
||||
Reference in new issue
Block a user