mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-09-30 13:09:36 +02:00
feat(budget,provenance): reasoning budget modes and JOASNSCOPE provenance
Budget (opt-in, unlimited by default so an un-budgeted run is unchanged):
- crates/harness/src/budget.rs — modes, phase shares, Token Governor
- CLI: --budget/--token-limit/--deep-test-limit/--coverage-first/
--depth-first/--sample-per-route; same controls in the web wizard
- pipeline honours it: vote_n narrows, evidence rounds are capped
Run control parity in the web console:
- /pause in the REPL, backed by a pause gate in the model pool: in-flight
agents finish, then the run holds with every finding kept
- POST /api/exploit/:id/{pause,continue,report} + GET .../log
Provenance (crates/harness/src/provenance.rs):
- JOASNSCOPE sigil leads every canary, so a marker found in a response,
a log or someone else's report extracts whole and names its build
- per-build fingerprint, per-run id, optional per-customer build id
- findings.json stamped with _engine; signed provenance.json manifest
- structural signature survives rewording but not a changed result set
- prompts watermarked at the single pool chokepoint
- `neurosploit provenance show|scan|verify`
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
40b047b9e7
commit
408350539f
16 files changed
+1464
-26
No files matched your search
@@ -442,6 +442,19 @@ state.authMode = 'api';
|
||||
// review (step 5)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// What the budget controls add up to, in the operator's words. "unlimited"
|
||||
/// is spelled out rather than left blank, because the absence of a cap is the
|
||||
/// thing worth confirming before launching.
|
||||
function budgetSummary() {
|
||||
const mode = $('#fieldBudget').value;
|
||||
const limit = Number($('#fieldTokenLimit').value) || 0;
|
||||
if (mode === 'unlimited' && !limit) return 'unlimited — full run, no cap';
|
||||
const parts = [mode, $('#fieldOrder').value];
|
||||
if (limit) parts.push(`${limit.toLocaleString()} tokens max`);
|
||||
parts.push(`${$('#fieldSampleRoute').value}/route`);
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
function renderReview() {
|
||||
const target = $('#fieldTarget').value.trim();
|
||||
const repo = $('#fieldRepo').value.trim();
|
||||
@@ -457,6 +470,7 @@ function renderReview() {
|
||||
{ k: 'Leads selected', v: `${state.selected.size} of ${allAgents().length}${state.selected.size === 0 ? ' — auto (recon-driven)' : ''}` },
|
||||
{ k: 'Custom leads', v: String(state.customLeads.length) },
|
||||
{ k: 'Votes / chain / recon', v: `${$('#fieldVotes').value} / ${$('#fieldChain').value} / ${$('#fieldRecon').value}` },
|
||||
{ k: 'Budget', v: budgetSummary() },
|
||||
{ k: 'Target auth', v: state.auth.header ? 'header set' : (state.auth.roles.length ? `${state.auth.roles.length} role(s)` : 'none') },
|
||||
];
|
||||
$('#reviewGrid').innerHTML = items.map((it) => `
|
||||
@@ -495,6 +509,12 @@ async function startExploitation() {
|
||||
focus: focusParts.join('; ') || undefined,
|
||||
objective: $('#fieldObjective').value.trim() || undefined,
|
||||
outOfScope: $('#fieldOutOfScope').value.trim() || undefined,
|
||||
// Budget is opt-in: 'unlimited' sends nothing, so a run nobody budgeted is
|
||||
// the same full run it was before this control existed.
|
||||
budget: $('#fieldBudget').value,
|
||||
tokenLimit: Number($('#fieldTokenLimit').value) || undefined,
|
||||
order: $('#fieldOrder').value,
|
||||
samplePerRoute: Number($('#fieldSampleRoute').value) || undefined,
|
||||
auth: state.auth.header || undefined,
|
||||
roles: state.auth.roles.length ? state.auth.roles : undefined,
|
||||
creds: state.credsPath || undefined,
|
||||
@@ -762,6 +782,7 @@ function applySnapshot(snap) {
|
||||
state.currentJob.pinnedAgents = snap.pinnedAgents;
|
||||
updatePinnedLine();
|
||||
}
|
||||
if (state.currentJob) state.currentJob.phase = snap.phase;
|
||||
$('#progressLabel').textContent = `${snap.agentsDone} / ${snap.agents || '?'} agents`;
|
||||
$('#progressBar').classList.toggle('indeterminate', !snap.agents);
|
||||
if (snap.agents) $('#progressFill').style.width = `${Math.min(100, (snap.agentsDone / snap.agents) * 100)}%`;
|
||||
@@ -769,6 +790,12 @@ function applySnapshot(snap) {
|
||||
$('#btnOpenReport').href = `/api/runs/${snap.runId}/asset/report.html`;
|
||||
show($('#btnOpenReport'), true);
|
||||
}
|
||||
const paused = (snap.phase || '').startsWith('paused');
|
||||
$('#btnPauseRun').textContent = paused ? '▶ Continue' : '⏸ Pause';
|
||||
$('#btnPauseRun').classList.toggle('btn-warn', paused);
|
||||
$('#btnPauseRun').disabled = !!snap.done || !snap.interactive;
|
||||
$('#btnReportNow').disabled = !snap.interactive;
|
||||
$('#btnDownloadLog').href = `/api/exploit/${snap.id}/log`;
|
||||
if (snap.done) $('#phaseDot').classList.add('static');
|
||||
}
|
||||
|
||||
@@ -776,6 +803,32 @@ $('#btnStopRun').addEventListener('click', async () => {
|
||||
if (!state.currentJob) return;
|
||||
await api(`/api/exploit/${state.currentJob.id}/stop`, { method: 'POST' });
|
||||
});
|
||||
|
||||
// Pause is a toggle against the run's own phase, so the button always says
|
||||
// what pressing it will do rather than what the run currently is.
|
||||
$('#btnPauseRun').addEventListener('click', async () => {
|
||||
if (!state.currentJob) return;
|
||||
const paused = (state.currentJob.phase || '').startsWith('paused');
|
||||
const verb = paused ? 'continue' : 'pause';
|
||||
try {
|
||||
await api(`/api/exploit/${state.currentJob.id}/${verb}`, { method: 'POST' });
|
||||
toast(paused ? 'Resuming the run.' : 'Pausing — in-flight agents finish first, findings are kept.', 'ok');
|
||||
} catch (e) {
|
||||
toast(e.message, 'error', 8000);
|
||||
}
|
||||
});
|
||||
|
||||
// Report from where it stopped: the REPL's /report writes from the evidence on
|
||||
// disk, so a run that is paused, stalled or simply long can be read now.
|
||||
$('#btnReportNow').addEventListener('click', async () => {
|
||||
if (!state.currentJob) return;
|
||||
try {
|
||||
await api(`/api/exploit/${state.currentJob.id}/report`, { method: 'POST' });
|
||||
toast('Building a report from what has been found so far — it appears above when written.', 'ok', 7000);
|
||||
} catch (e) {
|
||||
toast(e.message, 'error', 8000);
|
||||
}
|
||||
});
|
||||
function leaveLiveJob() {
|
||||
localStorage.removeItem(ACTIVE_JOB_KEY);
|
||||
clearInterval(state.currentJob?.pocPoll);
|
||||
|
||||
@@ -215,6 +215,31 @@
|
||||
<div class="field-help">Deeper recon finds more surface but costs more tokens.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="section-title">Reasoning budget</div>
|
||||
<div class="section-desc">Optional. Left on <em>unlimited</em>, the run behaves exactly as it always has — full depth, no cap.</div>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<div class="field-group"><label class="field-label" for="fieldBudget">Budget mode</label>
|
||||
<select class="narrow" id="fieldBudget">
|
||||
<option value="unlimited" selected>unlimited · full run (default)</option>
|
||||
<option value="eco">eco · cover ground, reason only on strong signal</option>
|
||||
<option value="balanced">balanced · investigate what looks suspicious</option>
|
||||
<option value="aggressive">aggressive · multiple hypotheses, deep validation</option>
|
||||
</select>
|
||||
<div class="field-help">How compute is spent, not how much there is.</div>
|
||||
</div>
|
||||
<div class="field-group"><label class="field-label" for="fieldTokenLimit">Token limit</label><input class="narrow" id="fieldTokenLimit" type="number" min="0" step="10000" placeholder="none" /><div class="field-help">Hard ceiling for the run. Empty or 0 = none.</div></div>
|
||||
<div class="field-group"><label class="field-label" for="fieldOrder">Order</label>
|
||||
<select class="narrow" id="fieldOrder">
|
||||
<option value="coverage-first" selected>coverage-first</option>
|
||||
<option value="depth-first">depth-first</option>
|
||||
</select>
|
||||
<div class="field-help">Map everything first, or chase a lead as it appears.</div>
|
||||
</div>
|
||||
<div class="field-group"><label class="field-label" for="fieldSampleRoute">Sample / route</label><input class="narrow" id="fieldSampleRoute" type="number" min="1" max="50" value="3" /><div class="field-help">Requests per endpoint family (<code>/api/users/{id}</code> is sampled, not enumerated).</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 5 — Review -->
|
||||
@@ -250,6 +275,9 @@
|
||||
<div class="run-actions">
|
||||
<button class="btn" id="btnOpenTerm3">❭_ Terminal</button>
|
||||
<a class="btn" id="btnOpenReport" target="_blank" hidden>Open report</a>
|
||||
<button class="btn" id="btnPauseRun" title="Hold the run where it is — in-flight agents finish, nothing is lost">⏸ Pause</button>
|
||||
<a class="btn" id="btnDownloadLog" download>Download log</a>
|
||||
<button class="btn" id="btnReportNow" title="Write the report from what has been found so far">Report so far</button>
|
||||
<button class="btn btn-danger" id="btnStopRun">Stop</button>
|
||||
<button class="btn" id="btnBackToBoard">← New engagement</button>
|
||||
</div>
|
||||
|
||||
@@ -274,7 +274,12 @@ a { color: var(--accent); text-decoration: none; }
|
||||
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
||||
.btn-danger { background: transparent; border-color: var(--sev-critical-fg); color: var(--sev-critical-fg); }
|
||||
.btn-danger:hover { background: var(--sev-critical-bg); }
|
||||
/* A paused run is a state to notice, not an error — the resume button wears
|
||||
the warning hue so the run never looks quietly stalled. */
|
||||
.btn-warn { background: var(--sev-medium-bg); border-color: var(--sev-medium-fg); color: var(--sev-medium-fg); }
|
||||
.btn-warn:hover { background: var(--sev-medium-bg); filter: brightness(1.12); }
|
||||
.btn:disabled { opacity: .5; cursor: not-allowed; }
|
||||
a.btn:disabled, a.btn[aria-disabled="true"] { pointer-events: none; }
|
||||
.btn:focus-visible { outline-offset: 2px; }
|
||||
|
||||
/* ============================================================ Wizard */
|
||||
|
||||
Reference in new issue
Block a user