diff --git a/web/public/app.js b/web/public/app.js
index c1fc1a3..08c9870 100644
--- a/web/public/app.js
+++ b/web/public/app.js
@@ -28,6 +28,8 @@ const state = {
providers: [],
auth: { header: '', roles: [] },
credsPath: '',
+ collectionPath: '',
+ collectionData: '',
// Engagement authorization: the grant, plus settings that may only narrow it.
authz: { capability: '', inScope: '', environment: 'production', policyProfile: 'web', transport: '', oobDomain: '', oobHttp: '', oobDns: '', sms: '' },
keys: [],
@@ -627,6 +629,8 @@ async function startExploitation() {
auth: state.auth.header || undefined,
roles: state.auth.roles.length ? state.auth.roles : undefined,
creds: state.credsPath || undefined,
+ collectionPath: state.collectionPath || undefined,
+ collectionData: state.collectionData || undefined,
capability: state.authz.capability || undefined,
inScope: state.authz.inScope.split(/[,;\s]+/).filter(Boolean),
environment: state.authz.environment,
@@ -1725,6 +1729,7 @@ function openAuthModal() {
show($('#authModal'), true);
renderRoleList();
$('#credsPath').value = state.credsPath;
+ $('#collectionPath').value = state.collectionPath;
refreshKeyStatus();
}
['#btnOpenAuth', '#btnOpenAuth2', '#btnOpenAuth3'].forEach((sel) => $(sel)?.addEventListener('click', openAuthModal));
@@ -1740,6 +1745,28 @@ $$('.modal-tab').forEach((tab) => tab.addEventListener('click', () => {
$('#authHeader').addEventListener('input', (e) => { state.auth.header = e.target.value.trim(); });
$('#authHeader').value = state.auth.header;
$('#credsPath').addEventListener('input', (e) => { state.credsPath = e.target.value.trim(); });
+$('#collectionPath').addEventListener('input', (e) => { state.collectionPath = e.target.value.trim(); });
+$('#collectionFile').addEventListener('change', async (e) => {
+ const help = $('#collectionHelp');
+ const file = e.target.files && e.target.files[0];
+ if (!file) { state.collectionData = ''; return; }
+ try {
+ const text = await file.text();
+ let n = 0;
+ try {
+ const j = JSON.parse(text);
+ if (j.log && Array.isArray(j.log.entries)) n = j.log.entries.length;
+ else if (j.paths) n = Object.values(j.paths).reduce((a, o) => a + Object.keys(o || {}).length, 0);
+ else if (Array.isArray(j.item)) n = JSON.stringify(j.item).match(/"request"/g)?.length || 0;
+ } catch { throw new Error('not valid JSON (convert a YAML spec to JSON first)'); }
+ state.collectionData = text;
+ state.collectionPath = ''; // uploaded content wins over a path
+ help.textContent = `Loaded ${file.name}${n ? ` — ~${n} endpoint(s)` : ''}. Tested as known surface.`;
+ } catch (err) {
+ state.collectionData = '';
+ help.textContent = `Could not read collection: ${err.message}`;
+ }
+});
$('#capToken').addEventListener('input', (e) => {
state.authz.capability = e.target.value.trim();
diff --git a/web/public/index.html b/web/public/index.html
index e41fd19..8610162 100644
--- a/web/public/index.html
+++ b/web/public/index.html
@@ -637,6 +637,12 @@
An explicit file on disk — see neurosploit-rs/creds.example.yaml for the schema (jwt/header/cookie/login/roles/ssh/windows/cloud).
+
+
+
+
+
Imports the endpoints as known surface — every route is tested directly, alongside recon. JSON only (convert YAML specs first).
+
diff --git a/web/server.js b/web/server.js
index 3f52758..4346814 100644
--- a/web/server.js
+++ b/web/server.js
@@ -263,6 +263,20 @@ async function materializeCreds(body, jobId) {
return file;
}
+// An imported API collection (Postman/OpenAPI/HAR). The UI either uploads the
+// file content (collectionData) or points at a path already on disk
+// (collectionPath). Uploaded content is written to a temp file so the CLI's
+// --collection flag can read it.
+async function materializeCollection(body, jobId) {
+ if (body.collectionPath) return body.collectionPath; // path on disk wins
+ if (!body.collectionData) return undefined;
+ const dir = path.join(os.tmpdir(), 'neurosploit-web');
+ await fsp.mkdir(dir, { recursive: true });
+ const file = path.join(dir, `${jobId}.collection.json`);
+ await fsp.writeFile(file, body.collectionData);
+ return file;
+}
+
// ---------------------------------------------------------------------------
// Agent library — read agents_md/{vulns,ai,infra,code,chains,recon,meta}/*.md
// and classify each into a lead category the UI can group + toggle.
@@ -1022,6 +1036,7 @@ function buildArgs(body) {
if (body.subscription) args.push('--subscription');
if (body.mcp) args.push('--mcp');
if (body.creds) args.push('--creds', body.creds);
+ if (body.collectionPath) args.push('--collection', body.collectionPath);
if (body.focus) args.push('--focus', body.focus);
if (body.objective) args.push('--objective', body.objective);
if (body.outOfScope) args.push('--out-of-scope', body.outOfScope);
@@ -1060,7 +1075,8 @@ async function startJob(body) {
const id = crypto.randomUUID();
const credsPath = await materializeCreds(body, id);
const scopePath = await materializeScope(body, id);
- const args = buildArgs({ ...body, creds: credsPath, scopePath });
+ const collectionPath = await materializeCollection(body, id);
+ const args = buildArgs({ ...body, creds: credsPath, scopePath, collectionPath });
const job = new Job(id, BIN, args, body.repo || body.target || '', body.name || '');
job.pinnedAgents = body.agents || [];
jobs.set(id, job);
@@ -1170,6 +1186,7 @@ function buildReplScript(body) {
if (body.objective) lines.push(`/objective ${body.objective}`);
if (body.outOfScope) lines.push(`/scope-out ${body.outOfScope}`);
if (body.creds) lines.push(`/creds ${body.creds}`);
+ if (body.collectionPath) lines.push(`/collection ${body.collectionPath}`);
if (body.research) lines.push(`/research on`);
// Settings the wizard collects that are NOT already applied as launch flags
// (authArgs) or the scope file. Emitted via the REPL's own commands so a web
@@ -1200,7 +1217,8 @@ async function startJobViaRepl(body) {
if (!BIN) throw new Error('neurosploit binary not found — run `cargo build --release` in neurosploit-rs/');
const id = crypto.randomUUID();
const credsPath = await materializeCreds(body, id);
- const script = buildReplScript({ ...body, creds: credsPath });
+ const collectionPath = await materializeCollection(body, id);
+ const script = buildReplScript({ ...body, creds: credsPath, collectionPath });
const scopePath = await materializeScope(body, id);
const auth = authArgs({ ...body, scopePath });
const job = new Job(id, BIN, auth, body.repo || body.target || '', body.name || '');