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 <noreply@anthropic.com>
This commit is contained in:
CyberSecurityUPandClaude Opus 4.8 committed 2026-10-15 20:30:31 -03:00
1 parent 7a0b2cb244
commit 2512388b6b
3 files changed
+53 -2

No files matched your search

+27
View File
@@ -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();
+6
View File
@@ -637,6 +637,12 @@
<input id="credsPath" type="text" placeholder="creds.yaml" />
<div class="field-help">An explicit file on disk — see neurosploit-rs/creds.example.yaml for the schema (jwt/header/cookie/login/roles/ssh/windows/cloud).</div>
</div>
<div class="field-group">
<label class="field-label" for="collectionFile">API collection (Postman / OpenAPI / Swagger / HAR)</label>
<input id="collectionFile" type="file" accept=".json,application/json,.har" />
<input id="collectionPath" type="text" placeholder="…or a path on disk: api.postman_collection.json" />
<div class="field-help" id="collectionHelp">Imports the endpoints as known surface — every route is tested directly, alongside recon. JSON only (convert YAML specs first).</div>
</div>
</div>
</div>
</div>
+20 -2
View File
@@ -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 || '');