mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-10-21 23:11:50 +02:00
release: v4.3.2 — bulk collection import, full MCP server, web console overhaul
Collection import: - parse_path() auto-detects file vs directory; parse_dir() walks a folder of .json collections (Postman/OpenAPI/Swagger/HAR), merges and deduplicates endpoints across all files. walkdir (already a dep) now used in collection.rs. - CLI --collection, REPL /collection, pipeline injection, and web console all accept a directory path transparently. MCP server (neurosploit mcp): - Expanded from 8 to 24 tools: added whitebox, greybox, host, aitest, mobile, skills, pr, graph, audit, poc, traffic, assurance, agents, models, and import_collection. neurosploit_run gained --collection and --objective params. - Clients: Claude Code, Codex CLI, Cursor, Windsurf, VS Code — all documented in tool descriptions. Web console: - Full sidebar navigation: Dashboard, Pentests, PR Reviews, Issues, Repositories, Domains, Networks, Integrations, Settings. Chat removed. - switchView() navigation system with lazy-loading per page. - Pentests page: tabular list of all engagements with click-to-detail. - PR Reviews page: filtered view of PR review runs. - Issues page: aggregate findings across every run, filterable by severity. - Repositories page: connect/manage source repos (persisted in .neurosploit/). - Domains page: manage target domains for black-box scans. - Networks page: manage network ranges for infra scanning. - Integrations page: GitHub, GitLab, Jira, Slack, MCP status cards. - Settings page: default model, environment, TypeSafe, network, proxy config. - Server: CRUD API endpoints for repos/domains/networks/integrations/settings with JSON file persistence in .neurosploit/ directory. - New CSS: nav items, config cards, data tables, stat cards, integration cards, form modals, accent buttons. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqW6BbQgqGkEDn3RYuCVoM
This commit is contained in:
1 parent
e8b7f61890
commit
71339a9ddb
11 files changed
+1072
-93
No files matched your search
+110
-1
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
/**
|
||||
* NeuroSploit v4.3.1 — web console backend.
|
||||
* NeuroSploit v4.3.2 — web console backend.
|
||||
*
|
||||
* Zero-dependency Node HTTP server that:
|
||||
* - serves the static SPA in ./public
|
||||
@@ -57,6 +57,22 @@ function saveEngagementName(runId, name) {
|
||||
// interrupted run/whitebox/greybox job, RESUME it (relaunch the REPL, which
|
||||
// auto-recovers the harness's own on-disk checkpoint and carries findings
|
||||
// forward). Secrets (API keys, creds-file contents) are never written here.
|
||||
const CONFIG_DIR = path.join(ROOT, '.neurosploit');
|
||||
function configFile(name) { return path.join(CONFIG_DIR, `${name}.json`); }
|
||||
async function ensureConfigDir() { await fsp.mkdir(CONFIG_DIR, { recursive: true }); }
|
||||
async function readConfig(name) {
|
||||
try { return JSON.parse(await fsp.readFile(configFile(name), 'utf8')); }
|
||||
catch { return []; }
|
||||
}
|
||||
async function readConfigObj(name) {
|
||||
try { return JSON.parse(await fsp.readFile(configFile(name), 'utf8')); }
|
||||
catch { return {}; }
|
||||
}
|
||||
async function writeConfig(name, data) {
|
||||
await ensureConfigDir();
|
||||
await fsp.writeFile(configFile(name), JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
const JOBS_DIR = path.join(ROOT, '.neurosploit', 'web-jobs');
|
||||
function jobFile(id) { return path.join(JOBS_DIR, `${id}.json`); }
|
||||
function persistJob(job) {
|
||||
@@ -1942,6 +1958,99 @@ const server = http.createServer(async (req, res) => {
|
||||
return sendJson(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
// ---- Issues (aggregate findings across all runs) ----
|
||||
if (req.method === 'GET' && p === '/api/issues') {
|
||||
const dirs = await fsp.readdir(RUNS_DIR).catch(() => []);
|
||||
const all = [];
|
||||
for (const d of dirs) {
|
||||
const fp = path.join(RUNS_DIR, d, 'findings.json');
|
||||
try {
|
||||
const raw = await fsp.readFile(fp, 'utf8');
|
||||
const findings = JSON.parse(raw);
|
||||
for (const f of findings) all.push({ ...f, run: d, target: f.target || d });
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
const sRank = (s) => { s = (s || '').toLowerCase(); return s.includes('crit') ? 0 : s.includes('high') ? 1 : s.includes('med') ? 2 : s.includes('low') ? 3 : 4; };
|
||||
all.sort((a, b) => sRank(a.severity) - sRank(b.severity));
|
||||
return sendJson(res, 200, all);
|
||||
}
|
||||
|
||||
// ---- Config CRUD: repos ----
|
||||
if (req.method === 'GET' && p === '/api/config/repos') return sendJson(res, 200, await readConfig('repos'));
|
||||
if (req.method === 'POST' && p === '/api/config/repos') {
|
||||
const body = await readBody(req);
|
||||
const repos = await readConfig('repos');
|
||||
const item = { id: crypto.randomUUID(), ...body, createdAt: new Date().toISOString() };
|
||||
repos.push(item);
|
||||
await writeConfig('repos', repos);
|
||||
return sendJson(res, 200, item);
|
||||
}
|
||||
m = p.match(/^\/api\/config\/repos\/([^/]+)$/);
|
||||
if (req.method === 'DELETE' && m) {
|
||||
let repos = await readConfig('repos');
|
||||
repos = repos.filter(r => r.id !== decodeURIComponent(m[1]));
|
||||
await writeConfig('repos', repos);
|
||||
return sendJson(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
// ---- Config CRUD: domains ----
|
||||
if (req.method === 'GET' && p === '/api/config/domains') return sendJson(res, 200, await readConfig('domains'));
|
||||
if (req.method === 'POST' && p === '/api/config/domains') {
|
||||
const body = await readBody(req);
|
||||
const domains = await readConfig('domains');
|
||||
const item = { id: crypto.randomUUID(), ...body, createdAt: new Date().toISOString() };
|
||||
domains.push(item);
|
||||
await writeConfig('domains', domains);
|
||||
return sendJson(res, 200, item);
|
||||
}
|
||||
m = p.match(/^\/api\/config\/domains\/([^/]+)$/);
|
||||
if (req.method === 'DELETE' && m) {
|
||||
let domains = await readConfig('domains');
|
||||
domains = domains.filter(d => d.id !== decodeURIComponent(m[1]));
|
||||
await writeConfig('domains', domains);
|
||||
return sendJson(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
// ---- Config CRUD: networks ----
|
||||
if (req.method === 'GET' && p === '/api/config/networks') return sendJson(res, 200, await readConfig('networks'));
|
||||
if (req.method === 'POST' && p === '/api/config/networks') {
|
||||
const body = await readBody(req);
|
||||
const networks = await readConfig('networks');
|
||||
const item = { id: crypto.randomUUID(), ...body, createdAt: new Date().toISOString() };
|
||||
networks.push(item);
|
||||
await writeConfig('networks', networks);
|
||||
return sendJson(res, 200, item);
|
||||
}
|
||||
m = p.match(/^\/api\/config\/networks\/([^/]+)$/);
|
||||
if (req.method === 'DELETE' && m) {
|
||||
let networks = await readConfig('networks');
|
||||
networks = networks.filter(n => n.id !== decodeURIComponent(m[1]));
|
||||
await writeConfig('networks', networks);
|
||||
return sendJson(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
// ---- Config CRUD: integrations ----
|
||||
if (req.method === 'GET' && p === '/api/config/integrations') return sendJson(res, 200, await readConfig('integrations'));
|
||||
m = p.match(/^\/api\/config\/integrations\/([^/]+)$/);
|
||||
if (req.method === 'PUT' && m) {
|
||||
const body = await readBody(req);
|
||||
let integs = await readConfig('integrations');
|
||||
const id = decodeURIComponent(m[1]);
|
||||
const idx = integs.findIndex(i => i.id === id);
|
||||
if (idx >= 0) integs[idx] = { ...integs[idx], ...body };
|
||||
else integs.push({ id, ...body });
|
||||
await writeConfig('integrations', integs);
|
||||
return sendJson(res, 200, integs.find(i => i.id === id));
|
||||
}
|
||||
|
||||
// ---- Config: settings ----
|
||||
if (req.method === 'GET' && p === '/api/config/settings') return sendJson(res, 200, await readConfigObj('settings'));
|
||||
if (req.method === 'PUT' && p === '/api/config/settings') {
|
||||
const body = await readBody(req);
|
||||
await writeConfig('settings', body);
|
||||
return sendJson(res, 200, body);
|
||||
}
|
||||
|
||||
sendJson(res, 404, { error: 'not found' });
|
||||
} catch (err) {
|
||||
sendJson(res, 500, { error: err.message });
|
||||
|
||||
Reference in new issue
Block a user