From 2512388b6bbe9b6cf041f92054efe941b3a2d038 Mon Sep 17 00:00:00 2001 From: CyberSecurityUP Date: Thu, 15 Oct 2026 20:30:31 -0300 Subject: [PATCH] feat(web): import an API collection from the console Add an API-collection field to the launch form (Creds panel): upload a Postman/OpenAPI/Swagger/HAR file or point at a path on disk. Uploaded content is written to a temp file and passed to the CLI via --collection (one-shot modes) or /collection (REPL-backed run/greybox/whitebox), so the console imports exactly the surface the CLI does. The picker previews the endpoint count client-side. Co-Authored-By: Claude Opus 4.8 --- web/public/app.js | 27 +++++++++++++++++++++++++++ web/public/index.html | 6 ++++++ web/server.js | 22 ++++++++++++++++++++-- 3 files changed, 53 insertions(+), 2 deletions(-) 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 || '');