([\s\S]*?)<\/div>/g;
+ let m;
+ while ((m = findingRegex.exec(html)) !== null) {
+ findings.push({ title: m[1].trim() });
+ }
+
+ // try to extract a summary JSON blob if present
+ const jsonBlobMatch = html.match(/\{[\s\S]*?\}/);
+ let summary = {};
+ if (jsonBlobMatch) {
+ try { summary = JSON.parse(jsonBlobMatch[0]); } catch (e) { summary = {}; }
+ }
+
+ scanInfo = { status: 'completed', result: { scan_info: summary.scan_info || {}, summary: summary.summary || {}, findings: findings } };
+ }
+ }
+ } catch (e) {
+ // ignore and fall through to 404
+ }
+ }
+ if (!scanInfo) {
+ return res.status(404).json({
+ success: false,
+ error: 'Scan ID not found'
+ });
+ }
+
+ res.json({
+ scan_id: scanId,
+ status: scanInfo.status,
+ progress: scanInfo.progress,
+ message: scanInfo.message
+ });
+});
+
+/**
+ * @swagger
+ * /api/scanner/scan/{scanId}/result:
+ * get:
+ * summary: Get scan result
+ * tags: [Vulnerability Scanner]
+ * parameters:
+ * - in: path
+ * name: scanId
+ * required: true
+ * schema:
+ * type: string
+ * description: Scan ID
+ * responses:
+ * 200:
+ * description: Scan result
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/ScanResult'
+ * 202:
+ * description: Scan not completed yet
+ * 404:
+ * description: Scan ID does not exist
+ */
+router.get('/scan/:scanId/result', async (req, res) => {
+ const { scanId } = req.params;
+ const scanInfo = activeScanners.get(scanId);
+
+ if (!scanInfo) {
+ return res.status(404).json({
+ success: false,
+ error: 'Scan ID not found'
+ });
+ }
+
+ if (scanInfo.status !== 'completed') {
+ return res.status(202).json({
+ success: false,
+ error: 'Scan not completed yet',
+ status: scanInfo.status
+ });
+ }
+
+ if (!scanInfo.result) {
+ return res.status(500).json({
+ success: false,
+ error: 'Scan result not available'
+ });
+ }
+
+ // Normalize response: ensure scan_id matches requested scanId and return summary before findings
+ const fullResult = scanInfo.result || {};
+ const summary = fullResult.summary || fullResult.scan_info || {};
+ const findings = fullResult.findings || fullResult.issues || [];
+
+ const responsePayload = {
+ scan_id: scanId,
+ summary: {
+ total_findings: summary.total || summary.total_findings || (Array.isArray(findings) ? findings.length : 0),
+ files_scanned: summary.files_scanned || (summary.stats && summary.stats.files_scanned) || (fullResult.scan_info && fullResult.scan_info.stats && fullResult.scan_info.stats.files_scanned) || null,
+ by_severity: summary.by_severity || summary.severity_summary || fullResult.by_severity || null,
+ by_plugin: summary.by_plugin || fullResult.by_plugin || null
+ },
+ findings: findings
+ };
+
+ res.json(responsePayload);
+});
+
+/**
+ * @swagger
+ * /api/scanner/scan/{scanId}/report:
+ * get:
+ * summary: Download scan report
+ * tags: [Vulnerability Scanner]
+ * parameters:
+ * - in: path
+ * name: scanId
+ * required: true
+ * schema:
+ * type: string
+ * description: Scan ID
+ * - in: query
+ * name: format
+ * schema:
+ * type: string
+ * enum: [html, json]
+ * default: html
+ * description: Report format
+ * responses:
+ * 200:
+ * description: Report file
+ * content:
+ * text/html:
+ * schema:
+ * type: string
+ * application/json:
+ * schema:
+ * type: object
+ * 404:
+ * description: Scan ID does not exist
+ */
+router.get('/scan/:scanId/report', async (req, res) => {
+ const { scanId } = req.params;
+ const { format = 'html' } = req.query;
+ console.log('REPORT request:', { scanId, format, query: req.query });
+ const scanInfo = activeScanners.get(scanId);
+
+ if (!scanInfo) {
+ return res.status(404).json({
+ success: false,
+ error: 'Scan ID not found'
+ });
+ }
+
+ if (scanInfo.status !== 'completed') {
+ return res.status(202).json({
+ success: false,
+ error: 'Scan not completed yet'
+ });
+ }
+
+ if (format === 'html' && scanInfo.result) {
+ // Persist and return HTML report. Prefer the scanner's reports dir for storage.
+ try {
+ const scannerReportsDir = path.join(__dirname, '../Vulnerability_Tool_V2', 'reports');
+ await fs.mkdir(scannerReportsDir, { recursive: true });
+ const htmlPath = path.join(scannerReportsDir, `Vulnerability_Scan_Report_${scanId}.html`);
+
+ // First try to use Python renderer if present
+ const pythonRenderer = path.join(__dirname, '../Vulnerability_Tool_V2/tools/render_from_json.py');
+ const scannerPath = path.join(__dirname, '../Vulnerability_Tool_V2');
+
+ if (await fs.access(pythonRenderer).then(() => true).catch(() => false)) {
+ // write JSON temp file (in scanner reports dir)
+ const tmpJson = path.join(scannerReportsDir, `tmp_${scanId}.json`);
+ await fs.writeFile(tmpJson, JSON.stringify(scanInfo.result, null, 2));
+
+ // Try venv python first, then system python3, then python
+ const pythonCandidates = [
+ path.join(scannerPath, 'venv', 'bin', 'python'),
+ 'python3',
+ 'python'
+ ];
+
+ let spawnRes = null;
+ let usedPython = null;
+ for (const py of pythonCandidates) {
+ try {
+ spawnRes = spawnSync(py, [pythonRenderer, tmpJson, htmlPath], { cwd: scannerPath, encoding: 'utf8' });
+ } catch (e) {
+ spawnRes = { error: e };
+ }
+ if (spawnRes && !spawnRes.error && spawnRes.status === 0) {
+ usedPython = py;
+ break;
+ }
+ }
+
+ // remove tmp
+ try { await fs.unlink(tmpJson); } catch (e) {}
+
+ // If python helper failed or file still missing, fallback to JS renderer
+ const finalHtmlExists = await fs.access(htmlPath).then(() => true).catch(() => false);
+ if (!finalHtmlExists || !usedPython) {
+ const html = generateHTMLReport(scanInfo.result);
+ await fs.writeFile(htmlPath, html);
+ }
+ } else {
+ // No python helper available; use JS renderer
+ const html = generateHTMLReport(scanInfo.result);
+ await fs.writeFile(htmlPath, html);
+ }
+
+ // Prefer scanner reports dir, but as a fallback check project reports dir for any legacy files
+ const projectReportsDir = path.join(__dirname, '../reports');
+
+ // Try to find the actual HTML file which may include an optional tag suffix
+ let finalPath = await findFileWithPrefix(scannerReportsDir, `Vulnerability_Scan_Report_${scanId}`, '.html');
+ if (!finalPath) finalPath = await findFileWithPrefix(projectReportsDir, `Vulnerability_Scan_Report_${scanId}`, '.html');
+ if (!finalPath) finalPath = htmlPath; // fallback to whatever we wrote earlier
+
+ // record chosen path and stream it
+ scanInfo.reportPath = finalPath;
+ const htmlContent = await fs.readFile(finalPath, 'utf-8');
+ res.setHeader('Content-Type', 'text/html');
+ res.setHeader('Content-Disposition', `attachment; filename="${path.basename(finalPath)}"`);
+ res.send(htmlContent);
+ return;
+ } catch (err) {
+ res.status(500).json({ success: false, error: 'Failed to generate HTML report', details: err.message });
+ return;
+ }
+ } else if (format === 'json') {
+ // attempt to find persisted json with optional tag; prefer scanner reports dir
+ const scannerReportsDir = path.join(__dirname, '../Vulnerability_Tool_V2/reports');
+ const projectReportsDir = path.join(__dirname, '../reports');
+ let jsonPath = await findFileWithPrefix(scannerReportsDir, `Vulnerability_Scan_Result_${scanId}`, '.json');
+ if (!jsonPath) jsonPath = await findFileWithPrefix(projectReportsDir, `Vulnerability_Scan_Result_${scanId}`, '.json');
+ if (jsonPath) res.setHeader('Content-Disposition', `attachment; filename="${path.basename(jsonPath)}"`);
+ else res.setHeader('Content-Disposition', `attachment; filename=\"Vulnerability_Scan_Result_${scanId}.json\"`);
+ res.json(scanInfo.result);
+ } else {
+ res.status(400).json({
+ success: false,
+ error: 'Invalid format or report not available'
+ });
+ }
+});
+
+// Debug endpoint: return raw python stdout and JSON candidates for a scan (useful for diagnosing parsing issues)
+router.get('/scan/:scanId/raw', (req, res) => {
+ const { scanId } = req.params;
+ const scanInfo = activeScanners.get(scanId);
+ if (!scanInfo) {
+ return res.status(404).json({ success: false, error: 'Scan ID not found' });
+ }
+
+ const raw = scanInfo.rawOutput || '';
+ const candidates = collectJSONCandidates(raw);
+ res.json({ scan_id: scanId, status: scanInfo.status, progress: scanInfo.progress, raw_preview: raw.slice(0, 4000), candidate_count: candidates.length, candidates: candidates.slice(-3) });
+});
+
+/**
+ * @swagger
+ * /api/scanner/quick-scan:
+ * post:
+ * summary: Quick synchronous scan
+ * tags: [Vulnerability Scanner]
+ * security:
+ * - BearerAuth: []
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/ScanRequest'
+ * responses:
+ * 200:
+ * description: Scan result
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/ScanResult'
+ */
+router.post('/quick-scan', async (req, res) => {
+ try {
+ const { target_path, plugins, output_format = 'json' } = req.body;
+
+ if (!target_path) {
+ return res.status(400).json({
+ success: false,
+ error: 'target_path is required'
+ });
+ }
+ // Validate target path exists (same as the async /scan endpoint)
+ const targetExists = await fs.access(target_path).then(() => true).catch(() => false);
+ if (!targetExists) {
+ return res.status(400).json({
+ success: false,
+ error: `Target path does not exist: ${target_path}`
+ });
+ }
+
+ const scanId = generateScanId();
+ const scanTag = 'quick-scan';
+ const scanIdWithTag = formatScanIdWithTag(scanId, scanTag);
+ const result = await runPythonScanSync(target_path, plugins, output_format);
+
+ // persist into activeScanners so subsequent report/status endpoints can find it
+ const scanInfo = {
+ status: 'completed',
+ progress: 100,
+ message: 'Quick scan completed',
+ result: result,
+ tag: scanTag,
+ scan_time: new Date().toISOString()
+ };
+
+ // write report files into the scanner's own reports dir for later retrieval
+ try {
+ const scannerReportsDir = path.join(__dirname, '../Vulnerability_Tool_V2', 'reports');
+ await fs.mkdir(scannerReportsDir, { recursive: true });
+ const jsonPath = path.join(scannerReportsDir, `Vulnerability_Scan_Result_${scanIdWithTag}.json`);
+ await fs.writeFile(jsonPath, JSON.stringify(result, null, 2));
+
+ // also generate HTML report if requested or default format
+ if (output_format === 'html' || output_format === 'json') {
+ // Prefer the scanner's Python renderer for consistent/identical HTML output
+ const pythonRenderer = path.join(__dirname, '../Vulnerability_Tool_V2/tools/render_from_json.py');
+ const htmlPath = path.join(scannerReportsDir, `Vulnerability_Scan_Report_${scanIdWithTag}.html`);
+ const tmpJson = path.join(scannerReportsDir, `tmp_${scanIdWithTag}.json`);
+ await fs.writeFile(tmpJson, JSON.stringify(result, null, 2));
+ let wroteHtml = false;
+ if (await fs.access(pythonRenderer).then(() => true).catch(() => false)) {
+ // try to run python helper (best-effort without blocking server startup)
+ const { spawnSync } = require('child_process');
+ const scannerPath = path.join(__dirname, '../Vulnerability_Tool_V2');
+ const pythonCandidates = [
+ path.join(scannerPath, 'venv', 'bin', 'python'),
+ 'python3',
+ 'python'
+ ];
+ for (const py of pythonCandidates) {
+ try {
+ const spawnRes = spawnSync(py, [pythonRenderer, tmpJson, htmlPath], { cwd: scannerPath, encoding: 'utf8' });
+ if (!spawnRes.error && spawnRes.status === 0) {
+ wroteHtml = true;
+ break;
+ }
+ } catch (e) {
+ // ignore and try next
+ }
+ }
+ }
+
+ // remove tmp json
+ try { await fs.unlink(tmpJson); } catch (e) {}
+
+ if (!wroteHtml) {
+ // fallback to JS renderer
+ const html = generateHTMLReport(result);
+ await fs.writeFile(htmlPath, html);
+ }
+ scanInfo.reportPath = htmlPath;
+ }
+ } catch (e) {
+ // non-fatal: keep scanInfo in memory but log message
+ scanInfo.message += `; Failed to persist reports: ${e.message}`;
+ }
+
+ activeScanners.set(scanId, scanInfo);
+
+ // Ensure result's own scan_id (if any) doesn't override our generated scanId
+ const responsePayload = Object.assign({}, result || {});
+ responsePayload.scan_id = scanId;
+ responsePayload.target_path = target_path;
+ responsePayload.scan_time = scanInfo.scan_time;
+
+ res.json(responsePayload);
+
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: error.message
+ });
+ }
+});
+
+// Start asynchronous Python scan
+function startPythonScan(scanId, scanTag, targetPath, plugins, outputFormat) {
+ activeScanners.set(scanId, {
+ status: 'running',
+ progress: 0,
+ message: 'Scan initiated',
+ tag: scanTag
+ });
+
+ const scannerPath = path.join(__dirname, '../Vulnerability_Tool_V2');
+ const scriptPath = path.join(scannerPath, 'scanner_v2.py');
+
+ // Resolve a usable python executable: prefer venv, then system python3, then python
+ function resolvePythonExecutableSync(scannerRoot) {
+ const { spawnSync } = require('child_process');
+ const envOverride = process.env.PYTHON_EXECUTABLE && process.env.PYTHON_EXECUTABLE.trim();
+ const candidates = [
+ envOverride,
+ path.join(scannerRoot, 'venv', 'Scripts', 'python.exe'),
+ path.join(scannerRoot, 'venv', 'Scripts', 'python'),
+ path.join(scannerRoot, 'venv', 'bin', 'python'),
+ 'python3',
+ 'python',
+ 'py'
+ ].filter(Boolean);
+ for (const c of candidates) {
+ try {
+ const res = spawnSync(c, ['--version'], { encoding: 'utf8' });
+ if (!res.error && (res.status === 0 || (res.stdout || res.stderr))) {
+ return c;
+ }
+ } catch (e) {
+ // ignore and try next
+ }
+ }
+ return null;
+ }
+
+ const args = ['--target', targetPath, '--format', outputFormat];
+
+ const pythonExec = resolvePythonExecutableSync(scannerPath);
+ if (!pythonExec) {
+ const scanInfo = activeScanners.get(scanId);
+ if (scanInfo) {
+ scanInfo.status = 'failed';
+ scanInfo.progress = 0;
+ scanInfo.message = 'No Python executable found. Expected either Vulnerability_Tool_V2/venv/bin/python or system python3. Please create a venv and install dependencies: `python3 -m venv Vulnerability_Tool_V2/venv && source Vulnerability_Tool_V2/venv/bin/activate && pip install -r Vulnerability_Tool_V2/requirements.txt`';
+ scanInfo.rawOutput = (scanInfo.rawOutput || '') + '\n\nSPAWN_ERROR: No python executable found';
+ }
+ return;
+ }
+
+ let pythonProcess;
+ try {
+ // Enforce UTF-8 so emoji / unicode characters don't break on Windows consoles (keep emoji output intact)
+ const childEnv = Object.assign({}, process.env, {
+ PYTHONUTF8: '1',
+ PYTHONIOENCODING: 'utf-8',
+ SCANNER_PROGRESS: '1' // signal Python side to emit incremental progress lines
+ });
+ pythonProcess = spawn(pythonExec, [scriptPath, ...args], {
+ cwd: scannerPath,
+ env: childEnv
+ });
+ } catch (spawnErr) {
+ const scanInfo = activeScanners.get(scanId);
+ if (scanInfo) {
+ scanInfo.status = 'failed';
+ scanInfo.progress = 0;
+ scanInfo.message = `Failed to start python scanner using '${pythonExec}': ${spawnErr.message || String(spawnErr)}. Tried candidates: venv/bin/python -> python3 -> python`;
+ scanInfo.rawOutput = (scanInfo.rawOutput || '') + '\n\nSPAWN_ERROR:\n' + (spawnErr.stack || String(spawnErr));
+ }
+ return;
+ }
+
+ // handle runtime errors from the child process (e.g., exec failures)
+ pythonProcess.on('error', (err) => {
+ const scanInfo = activeScanners.get(scanId);
+ if (scanInfo) {
+ scanInfo.status = 'failed';
+ scanInfo.progress = 0;
+ scanInfo.message = `Python process error: ${err.message || String(err)}`;
+ scanInfo.rawOutput = (scanInfo.rawOutput || '') + '\n\nPROCESS_ERROR:\n' + (err.stack || String(err));
+ // persist raw output for post-mortem
+ (async () => {
+ try {
+ const reportsDir = path.join(__dirname, '../reports');
+ await fs.mkdir(reportsDir, { recursive: true });
+ const rawPath = path.join(reportsDir, `raw_${scanId}.log`);
+ await fs.writeFile(rawPath, scanInfo.rawOutput || (err.stack || String(err)));
+ scanInfo.rawOutputPath = rawPath;
+ } catch (e) {
+ // nothing else to do
+ }
+ })();
+ }
+ });
+
+ let outputData = '';
+ let errorData = '';
+ let lineBuffer = '';
+ // save raw output for debugging
+
+ pythonProcess.stdout.on('data', (data) => {
+ const chunk = data.toString();
+ outputData += chunk;
+ const scanInfo = activeScanners.get(scanId);
+ lineBuffer += chunk;
+ // Process complete lines for progress markers
+ let lines = lineBuffer.split(/\r?\n/);
+ lineBuffer = lines.pop(); // keep last partial line
+ for (const line of lines) {
+ // Progress sentinel format: PROGRESS|
|
+ const m = line.match(/^PROGRESS\|(\d{1,3})(?:\|(.*))?$/);
+ if (m && scanInfo && scanInfo.status === 'running') {
+ const pct = Math.max(0, Math.min(100, parseInt(m[1], 10)));
+ scanInfo.progress = pct;
+ if (m[2]) {
+ const msg = m[2].trim();
+ // 只在仍处于 running 阶段时更新 message,完成后的成功文案保持原样
+ if (msg) scanInfo.message = msg;
+ }
+ }
+ }
+ });
+
+ pythonProcess.stderr.on('data', (data) => {
+ errorData += data.toString();
+ });
+
+ pythonProcess.on('close', (code) => {
+ console.log('Full Python output:', outputData);
+
+ const scanInfo = activeScanners.get(scanId);
+ if (scanInfo) scanInfo.rawOutput = outputData;
+ if (!scanInfo) return;
+
+ if (code === 0) {
+ try {
+ const result = parseBestJSON(outputData);
+ scanInfo.status = 'completed';
+ scanInfo.progress = 100;
+ scanInfo.message = 'Scan completed successfully';
+ scanInfo.result = result;
+
+ // If there is HTML output, save it as well
+ if (outputFormat === 'html') {
+ scanInfo.htmlReport = generateHTMLReport(result);
+ // persist into project reports dir for easy discovery (async IIFE)
+ (async () => {
+ try {
+ const reportsDir = path.join(__dirname, '../reports');
+ await fs.mkdir(reportsDir, { recursive: true });
+ const idWithTag = formatScanIdWithTag(scanId, scanTag);
+ const htmlPath = path.join(reportsDir, `Vulnerability_Scan_Report_${idWithTag}.html`);
+ await fs.writeFile(htmlPath, scanInfo.htmlReport);
+ scanInfo.reportPath = htmlPath;
+ } catch (e) {
+ // if writing to project reports fails, leave as-is and record message
+ scanInfo.message = (scanInfo.message || '') + `; Failed to persist html report: ${e.message}`;
+ }
+ })();
+ }
+ } catch (error) {
+ // Persist raw output to disk for post-mortem analysis
+ (async () => {
+ try {
+ const reportsDir = path.join(__dirname, '../reports');
+ await fs.mkdir(reportsDir, { recursive: true });
+ const rawPath = path.join(reportsDir, `raw_${scanId}.log`);
+ await fs.writeFile(rawPath, outputData);
+ scanInfo.rawOutputPath = rawPath;
+ scanInfo.status = 'failed';
+ scanInfo.message = `Failed to parse scan result: ${error.message}. Raw output saved to: ${rawPath}`;
+ } catch (fsErr) {
+ scanInfo.status = 'failed';
+ scanInfo.message = `Failed to parse scan result: ${error.message}. Also failed to write raw output: ${fsErr.message}`;
+ }
+ })();
+ }
+ } else {
+ // Try to salvage a result if the python process printed JSON despite non-zero exit
+ try {
+ const maybeResult = parseBestJSON(outputData);
+ scanInfo.status = 'completed';
+ scanInfo.progress = 100;
+ // Keep user-facing message consistent with successful scans
+ const detailMsg = `Non-zero exit code ${code} but output parsed successfully`;
+ scanInfo.message = 'Scan completed successfully';
+ // Preserve diagnostic detail separately (not exposed unless you add to response)
+ scanInfo.diagnostic = detailMsg;
+ scanInfo.result = maybeResult;
+ } catch (parseErr) {
+ // Save raw output for non-zero exit as well
+ (async () => {
+ try {
+ const reportsDir = path.join(__dirname, '../reports');
+ await fs.mkdir(reportsDir, { recursive: true });
+ const rawPath = path.join(reportsDir, `raw_${scanId}.log`);
+ await fs.writeFile(rawPath, outputData + '\n\nSTDERR:\n' + errorData);
+ scanInfo.rawOutputPath = rawPath;
+ scanInfo.status = 'failed';
+ scanInfo.message = `Scan failed with code ${code}. Raw output saved to: ${rawPath}`;
+ } catch (fsErr) {
+ scanInfo.status = 'failed';
+ scanInfo.message = `Scan failed with code ${code}: ${errorData}. Also failed to write raw output: ${fsErr.message}`;
+ }
+ })();
+ }
+ }
+ });
+}
+
+// Run Python scan synchronously
+function runPythonScanSync(targetPath, plugins, outputFormat) {
+ return new Promise((resolve, reject) => {
+ const scannerPath = path.join(__dirname, '../Vulnerability_Tool_V2');
+ const scriptPath = path.join(scannerPath, 'scanner_v2.py');
+
+ // Use the requested output format (was hard-coded to 'json')
+ const args = ['--target', targetPath, '--format', outputFormat || 'json'];
+ (async () => {
+ const pythonExec = await resolvePythonExecutable(scannerPath);
+ if (!pythonExec) {
+ return reject(new Error('No usable Python executable found. Please create Vulnerability_Tool_V2/venv or ensure python3 is available and scanner dependencies are installed.'));
+ }
+
+ const childEnv = Object.assign({}, process.env, {
+ PYTHONUTF8: '1',
+ PYTHONIOENCODING: 'utf-8'
+ });
+ const pythonProcess = spawn(pythonExec, [scriptPath, ...args], {
+ cwd: scannerPath,
+ env: childEnv
+ });
+
+ let outputData = '';
+ let errorData = '';
+
+ pythonProcess.stdout.on('data', (data) => {
+ outputData += data.toString();
+ });
+
+ pythonProcess.stderr.on('data', (data) => {
+ errorData += data.toString();
+ });
+
+ pythonProcess.on('close', (code) => {
+ const attemptParse = () => {
+ try { return parseBestJSON(outputData); } catch (e) { return null; }
+ };
+ const resultObj = attemptParse();
+ if (resultObj) {
+ // 任何情况下(包括非零退出)只要成功解析就返回结果
+ resolve(resultObj);
+ return;
+ }
+ // 若第一次解析失败,再尝试剪掉 stderr 附加的尾部(常见编码错误行)
+ let trimmed = outputData.replace(/Unexpected error:[\s\S]*$/i, '').trim();
+ if (!resultObj && trimmed !== outputData) {
+ try {
+ const salvage = parseBestJSON(trimmed);
+ return resolve(salvage);
+ } catch (_) {}
+ }
+ // 仍失败,写 raw 输出
+ (async () => {
+ try {
+ const reportsDir = path.join(__dirname, '../reports');
+ await fs.mkdir(reportsDir, { recursive: true });
+ const rawPath = path.join(reportsDir, `raw_sync_${Date.now()}.log`);
+ await fs.writeFile(rawPath, outputData + '\n\nSTDERR:\n' + errorData);
+ reject(new Error(`Scan failed with code ${code}. Raw output saved to: ${rawPath}`));
+ } catch (fsErr) {
+ reject(new Error(`Scan failed with code ${code}: ${errorData}. Also failed to write raw output: ${fsErr.message}`));
+ }
+ })();
+ });
+ })();
+ });
+}
+
+// Collect JSON candidates from text by tracking balanced braces/brackets
+function collectJSONCandidates(text) {
+ if (!text || typeof text !== 'string') return [];
+
+ const candidates = [];
+ const len = text.length;
+ let inString = false;
+ let escape = false;
+ let depth = 0;
+ let start = -1;
+
+ for (let i = 0; i < len; i++) {
+ const ch = text[i];
+ if (inString) {
+ if (escape) { escape = false; }
+ else if (ch === '\\') { escape = true; }
+ else if (ch === '"') { inString = false; }
+ continue;
+ }
+ if (ch === '"') { inString = true; continue; }
+
+ if ((ch === '{' || ch === '[') && start === -1) {
+ start = i;
+ depth = 1;
+ continue;
+ }
+
+ if (start !== -1) {
+ if (ch === '{' || ch === '[') depth++;
+ else if (ch === '}' || ch === ']') {
+ depth--;
+ if (depth === 0) {
+ candidates.push(text.substring(start, i + 1).trim());
+ start = -1;
+ }
+ }
+ }
+ }
+ return candidates;
+}
+
+// Attempt to parse the best JSON candidate from text, with progressive trimming if needed
+function parseBestJSON(text) {
+ const candidates = collectJSONCandidates(text);
+ if (!candidates || candidates.length === 0) throw new Error('No JSON object or array found in output');
+
+ const maxTrimAttempts = 200; // bounded attempts to trim tail
+ for (let ci = candidates.length - 1; ci >= 0; ci--) {
+ let cand = candidates[ci];
+ // try direct parse
+ try {
+ return JSON.parse(cand);
+ } catch (err) {
+ // if parse failed, try trimming tail progressively (but bounded)
+ for (let t = 0; t < maxTrimAttempts && cand.length > 2; t++) {
+ // remove up to t+1 chars from end
+ const newLen = Math.max(0, cand.length - (t + 1));
+ const substr = cand.substring(0, newLen).trim();
+ try {
+ return JSON.parse(substr);
+ } catch (e2) {
+ // continue trimming
+ }
+ }
+ }
+ }
+
+ throw new Error('Failed to parse any JSON candidate from output');
+}
+
+// Generate HTML report
+function generateHTMLReport(scanResult) {
+ const { summary, findings } = scanResult;
+
+ return `
+
+
+
+ NutriHelp Vulnerability Scan Report
+
+
+
+
+
+
+
+
${summary.files_scanned}
+
Files Scanned
+
+
+
${findings.length}
+
Total Issues
+
+
+
${summary.by_severity.CRITICAL || 0}
+
Critical
+
+
+
${summary.by_severity.HIGH || 0}
+
High
+
+
+
+ 📋 Detailed Findings
+ ${findings.map(finding => `
+
+
${finding.title} (${finding.severity})
+
File: ${finding.file_path}
+
Description: ${finding.description}
+
Plugin: ${finding.plugin_name}
+
+ `).join('')}
+
+`;
+}
+
+module.exports = router;
\ No newline at end of file
diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js
new file mode 100644
index 0000000..939d477
--- /dev/null
+++ b/scripts/bootstrap.js
@@ -0,0 +1,74 @@
+#!/usr/bin/env node
+/**
+ * bootstrap.js
+ * One-shot developer setup script: Node deps, scanner venv deps, env template, validation.
+ * Modes:
+ * full (default) - Used by "npm run setup" (hard fail on validation errors)
+ * postinstall - Used automatically after npm install (soft fail: warns only)
+ */
+const { spawnSync } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+
+const modeArg = process.argv.find(a => a.startsWith('--mode='));
+const mode = modeArg ? modeArg.split('=')[1] : 'full';
+const soft = mode === 'postinstall';
+
+function log(msg){ console.log(`[bootstrap] ${msg}`); }
+function warn(msg){ console.warn(`[bootstrap] WARN: ${msg}`); }
+function run(cmd,args,opts){
+ const r = spawnSync(cmd,args,Object.assign({stdio:'inherit'},opts));
+ if (r.status !== 0) {
+ console.error(`Command failed: ${cmd} ${args.join(' ')}`);
+ if (!soft) process.exit(r.status || 1);
+ else warn(`Continuing despite failure (mode=${mode})`);
+ }
+}
+
+// 1. Install Node dependencies if node_modules missing
+if (!fs.existsSync(path.join(__dirname,'..','node_modules'))) {
+ log('Installing Node dependencies (npm ci fallback to npm install)...');
+ let res = spawnSync('npm',['ci'],{stdio:'inherit'});
+ if (res.status !== 0) {
+ log('npm ci failed, trying npm install');
+ res = spawnSync('npm',['install'],{stdio:'inherit'});
+ if (res.status !== 0) {
+ if (!soft) process.exit(res.status);
+ else warn('Node dependency installation failed during postinstall mode. Project may be unusable.');
+ }
+ }
+} else {
+ log('node_modules present, skipping npm install');
+}
+
+// 2. Ensure .env (create from example if available)
+const envPath = path.join(__dirname,'..','.env');
+const examplePath = path.join(__dirname,'..','.env.example');
+if (!fs.existsSync(envPath)) {
+ if (fs.existsSync(examplePath)) {
+ fs.copyFileSync(examplePath, envPath);
+ log('Created .env from .env.example');
+ } else {
+ fs.writeFileSync(envPath, '# Auto-generated minimal env (edit with real internal secrets)\nJWT_SECRET=change_me_replace_before_prod\nSUPABASE_URL=your_supabase_url\nSUPABASE_ANON_KEY=your_public_anon_key\nPORT=3000\n');
+ log('Generated minimal .env (placeholders).');
+ }
+} else {
+ log('.env already exists, not touching');
+}
+
+// 3. Prepare scanner (venv + deps)
+log('Preparing vulnerability scanner environment...');
+run(process.execPath, [path.join(__dirname,'prepareScanner.js')]);
+
+// 4. Validate environment
+log('Validating environment variables...');
+const val = spawnSync('npm',['run','validate-env'],{stdio:'inherit'});
+if (val.status !== 0) {
+ if (soft) {
+ warn('Environment validation reported issues (non-fatal in postinstall mode).');
+ } else {
+ process.exit(val.status);
+ }
+}
+
+console.log(`\n✅ Bootstrap complete (mode=${mode}). You can now run: npm start`);
diff --git a/scripts/ci_check_vuln.py b/scripts/ci_check_vuln.py
new file mode 100644
index 0000000..891ec53
--- /dev/null
+++ b/scripts/ci_check_vuln.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+"""CI helper: check vulnerability_report.json and fail if CRITICAL findings exist.
+
+Exit codes:
+ 0 - no critical findings
+ 1 - error reading file or file missing
+ 2 - critical findings found
+"""
+import json
+import sys
+from pathlib import Path
+
+
+def main():
+ fn = Path('vulnerability_report.json')
+ if not fn.exists():
+ print('vulnerability_report.json not found', file=sys.stderr)
+ return 1
+
+ try:
+ data = json.loads(fn.read_text(encoding='utf-8'))
+ except Exception as e:
+ print('Failed to read vulnerability_report.json:', e, file=sys.stderr)
+ return 1
+
+ try:
+ bysev = data.get('summary', {}).get('by_severity', {})
+ crit = int(bysev.get('CRITICAL', 0))
+ except Exception:
+ crit = 0
+
+ if crit > 0:
+ print(f'🚨 Found {crit} CRITICAL vulnerability(ies). Failing job as requested.')
+ findings = data.get('findings', []) or []
+ topcrit = [f for f in findings if str(f.get('severity', '')).upper() == 'CRITICAL']
+ for i, f in enumerate(topcrit[:5], 1):
+ title = f.get('title') or f.get('rule_name') or 'No title'
+ path = f.get('file_path') or f.get('file') or ''
+ print(f'{i}. {title} — {path}')
+ return 2
+
+ print('No critical findings. Proceeding.')
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/scripts/ensureScannerReady.js b/scripts/ensureScannerReady.js
new file mode 100644
index 0000000..d64be4f
--- /dev/null
+++ b/scripts/ensureScannerReady.js
@@ -0,0 +1,43 @@
+#!/usr/bin/env node
+/**
+ * ensureScannerReady.js
+ * Lightweight check to confirm scanner venv & core dependencies exist; if not, call prepareScanner.
+ */
+const fs = require('fs');
+const path = require('path');
+const { spawnSync } = require('child_process');
+
+const scannerRoot = path.join(__dirname, '..', 'Vulnerability_Tool_V2');
+const venvDir = path.join(scannerRoot, 'venv');
+const markerPip = process.platform === 'win32' ? path.join(venvDir,'Scripts','pip.exe') : path.join(venvDir,'bin','pip');
+
+function log(m){ console.log(`[ensure-scanner] ${m}`); }
+function run(cmd, args, opts={}){ return spawnSync(cmd,args,Object.assign({encoding:'utf8'},opts)); }
+
+if (!fs.existsSync(scannerRoot)) {
+ log('Scanner root not found, nothing to ensure.');
+ process.exit(0);
+}
+
+let needPrepare = false;
+if (!fs.existsSync(venvDir)) needPrepare = true;
+if (!fs.existsSync(markerPip)) needPrepare = true;
+
+// quick module import probe (yaml, jinja2) using venv python if present
+if (!needPrepare) {
+ const pyExe = process.platform === 'win32' ? path.join(venvDir,'Scripts','python.exe') : path.join(venvDir,'bin','python');
+ if (fs.existsSync(pyExe)) {
+ const probe = run(pyExe, ['-c','import yaml,jinja2']);
+ if (probe.status !== 0) needPrepare = true;
+ } else {
+ needPrepare = true;
+ }
+}
+
+if (needPrepare) {
+ log('Scanner environment incomplete; running prepare-scanner');
+ const prep = run(process.execPath, [path.join(__dirname,'prepareScanner.js')], { stdio:'inherit' });
+ process.exit(prep.status || 0);
+} else {
+ log('Scanner environment is ready.');
+}
diff --git a/scripts/gen_swagger.js b/scripts/gen_swagger.js
new file mode 100644
index 0000000..b0dc115
--- /dev/null
+++ b/scripts/gen_swagger.js
@@ -0,0 +1,26 @@
+const yaml = require('yamljs');
+const swaggerJSDoc = require('swagger-jsdoc');
+const path = require('path');
+const fs = require('fs');
+
+const base = yaml.load(path.join(process.cwd(), 'index.yaml'));
+const opts = {
+ swaggerDefinition: {
+ openapi: base.openapi || base.swagger || '3.0.0',
+ info: base.info || { title: 'temp', version: '1.0.0' },
+ servers: base.servers || [{ url: 'http://localhost' }]
+ },
+ apis: [path.join(process.cwd(), 'routes', '**', '*.js'), path.join(process.cwd(), 'routes', '*.js')]
+};
+
+try {
+ const gen = swaggerJSDoc(opts);
+ const merged = JSON.parse(JSON.stringify(base));
+ merged.paths = Object.assign({}, merged.paths || {}, gen.paths || {});
+ merged.components = Object.assign({}, merged.components || {}, gen.components || {});
+ const p = merged.paths['/api/scanner/scan'];
+ console.log(JSON.stringify(p, null, 2));
+} catch (e) {
+ console.error('ERROR', e && e.stack ? e.stack : e);
+ process.exit(1);
+}
diff --git a/scripts/prepareScanner.js b/scripts/prepareScanner.js
new file mode 100644
index 0000000..b901403
--- /dev/null
+++ b/scripts/prepareScanner.js
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+/**
+ * prepareScanner.js
+ * Recreates Python virtual environment for Vulnerability_Tool_V2 (idempotent) and installs dependencies.
+ * Safe to run multiple times. Skips work if already up to date. Gracefully degrades if Python is missing.
+ */
+const { spawnSync } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+
+const scannerRoot = path.join(__dirname, '..', 'Vulnerability_Tool_V2');
+const reqFile = path.join(scannerRoot, 'requirements.txt');
+const venvDir = path.join(scannerRoot, 'venv');
+
+function log(msg){ console.log(`[prepare-scanner] ${msg}`); }
+function warn(msg){ console.warn(`[prepare-scanner] WARN: ${msg}`); }
+function err(msg){ console.error(`[prepare-scanner] ERROR: ${msg}`); }
+
+if (!fs.existsSync(scannerRoot)) {
+ warn(`Scanner directory not found at ${scannerRoot}, skipping.`);
+ process.exit(0);
+}
+
+// Determine python executable candidates (prefer explicit override and local project .venv before global)
+const localProjectVenv = process.platform === 'win32'
+ ? path.join(__dirname,'..','.venv','Scripts','python.exe')
+ : path.join(__dirname,'..','.venv','bin','python');
+const envOverride = process.env.PYTHON_EXECUTABLE && process.env.PYTHON_EXECUTABLE.trim();
+const pythonCandidates = [envOverride, localProjectVenv, 'python3', 'python', 'py'].filter(Boolean);
+let pythonExe = null;
+for (const c of pythonCandidates) {
+ try {
+ const res = spawnSync(c, ['--version'], { encoding: 'utf8' });
+ if (!res.error && res.status === 0) { pythonExe = c; break; }
+ } catch (_) {}
+}
+if (!pythonExe) {
+ warn('No usable python interpreter (exit status 0) found. Skipping scanner setup.');
+ warn('API will run; scanner endpoints will be unavailable until Python is installed.');
+ process.exit(0);
+}
+
+// Create venv if missing
+if (!fs.existsSync(venvDir)) {
+ log(`Creating virtual environment: ${pythonExe} -m venv venv`);
+ const create = spawnSync(pythonExe, ['-m','venv','venv'], { cwd: scannerRoot, stdio:'inherit' });
+ if (create.status !== 0) {
+ err('Failed to create scanner venv. You may create it manually then rerun this script.');
+ process.exit(0); // degrade gracefully
+ }
+} else {
+ log('Scanner venv already exists, skipping creation');
+}
+
+// Locate pip
+const pipPath = process.platform === 'win32'
+ ? path.join(venvDir,'Scripts','pip.exe')
+ : path.join(venvDir,'bin','pip');
+if (!fs.existsSync(pipPath)) {
+ err(`pip not found at ${pipPath}`);
+ process.exit(1);
+}
+
+if (!fs.existsSync(reqFile)) {
+ warn('requirements.txt not found, skipping dependency install');
+ process.exit(0);
+}
+
+// Dependency change detection marker
+const marker = path.join(venvDir, '.deps_hash');
+let needInstall = true;
+try {
+ const reqStat = fs.statSync(reqFile).mtimeMs;
+ const markerData = fs.existsSync(marker) ? fs.readFileSync(marker,'utf8') : '';
+ if (markerData.trim() === String(reqStat)) needInstall = false; else fs.writeFileSync(marker, String(reqStat));
+} catch { /* ignore */ }
+
+if (!needInstall) {
+ log('Dependencies already up to date, skipping pip install');
+ process.exit(0);
+}
+
+log('Installing Python scanner dependencies...');
+const install = spawnSync(pipPath, ['install','-r','requirements.txt'], { cwd: scannerRoot, stdio:'inherit' });
+if (install.status !== 0) {
+ err('pip install failed');
+ process.exit(1);
+}
+log('Scanner dependencies installed successfully.');
+
diff --git a/scripts/rename_reports_security_to_vulnerability.py b/scripts/rename_reports_security_to_vulnerability.py
new file mode 100644
index 0000000..5e1a60c
--- /dev/null
+++ b/scripts/rename_reports_security_to_vulnerability.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python3
+"""
+Safe batch rename script:
+- Moves original files matching security_report_* and security_result_* into reports/legacy_backup/
+- Generates new filenames using timestamp extracted from filename (YYYYMMDD_HHMMSS) or file mtime
+- Writes a CSV mapping original -> new in reports/legacy_backup/rename_map.csv
+- Dry-run mode lists planned actions; use --apply to perform
+"""
+import argparse
+import csv
+import os
+import re
+import shutil
+from datetime import datetime
+
+ROOT = os.path.dirname(os.path.dirname(__file__))
+REPORTS_DIR = os.path.join(ROOT, 'reports')
+BACKUP_DIR = os.path.join(REPORTS_DIR, 'legacy_backup')
+
+PAT_REPORT = re.compile(r'security_report_(?P\d{8}_\d{6})(?:_(?P.+?))?\.html$')
+PAT_RESULT = re.compile(r'security_result_(?P\d{8}_\d{6})(?:_(?P.+?))?\.json$')
+
+
+def find_candidates():
+ files = os.listdir(REPORTS_DIR)
+ candidates = []
+ for fn in files:
+ if PAT_REPORT.match(fn) or PAT_RESULT.match(fn):
+ candidates.append(fn)
+ return sorted(candidates)
+
+
+def extract_timestamp(fn):
+ m = PAT_REPORT.match(fn) or PAT_RESULT.match(fn)
+ if m:
+ ts = m.group('ts')
+ tag = m.group('tag')
+ return ts, tag
+ # fallback: use mtime
+ p = os.path.join(REPORTS_DIR, fn)
+ st = os.path.getmtime(p)
+ ts = datetime.fromtimestamp(st).strftime('%Y%m%d_%H%M%S')
+ return ts, None
+
+
+def plan_rename(fn):
+ ts, tag = extract_timestamp(fn)
+ if fn.startswith('security_report_'):
+ newbase = f'Vulnerability_Scan_Report_{ts}'
+ ext = '.html'
+ else:
+ newbase = f'Vulnerability_Scan_Result_{ts}'
+ ext = '.json'
+ if tag:
+ newbase = newbase + '_' + tag
+ newfn = newbase + ext
+ return newfn
+
+
+def ensure_backup_dir():
+ os.makedirs(BACKUP_DIR, exist_ok=True)
+
+
+def perform(dry_run=True):
+ candidates = find_candidates()
+ if not candidates:
+ print('No candidate files found.')
+ return 0
+ plan = []
+ for fn in candidates:
+ src = os.path.join(REPORTS_DIR, fn)
+ newfn = plan_rename(fn)
+ dst = os.path.join(REPORTS_DIR, newfn)
+ # avoid overwriting existing target: if exists, append a counter
+ if os.path.exists(dst):
+ base, ext = os.path.splitext(newfn)
+ i = 1
+ while True:
+ alt = f"{base}._{i}{ext}"
+ altpath = os.path.join(REPORTS_DIR, alt)
+ if not os.path.exists(altpath):
+ dst = altpath
+ newfn = alt
+ break
+ i += 1
+ plan.append((fn, newfn, src, dst))
+
+ print(f'Planned renames: {len(plan)}')
+ for old, newfn, src, dst in plan[:20]:
+ print(f'{old} -> {newfn}')
+ if len(plan) > 20:
+ print('...')
+
+ if dry_run:
+ print('\nDry-run mode; no files moved. Use --apply to execute the moves.')
+ return 0
+
+ # perform
+ ensure_backup_dir()
+ map_csv = os.path.join(BACKUP_DIR, 'rename_map.csv')
+ with open(map_csv, 'w', newline='') as fh:
+ writer = csv.writer(fh)
+ writer.writerow(['original', 'new', 'backup_path'])
+ for old, newfn, src, dst in plan:
+ # move original to backup
+ bak = os.path.join(BACKUP_DIR, old)
+ if not os.path.exists(bak):
+ shutil.move(src, bak)
+ else:
+ # if backup exists, append counter
+ base, ext = os.path.splitext(old)
+ i = 1
+ while True:
+ bak_alt = os.path.join(BACKUP_DIR, f"{base}._{i}{ext}")
+ if not os.path.exists(bak_alt):
+ shutil.move(src, bak_alt)
+ bak = bak_alt
+ break
+ i += 1
+ # copy backup to new name in reports dir
+ shutil.copy(bak, dst)
+ writer.writerow([old, newfn, bak])
+ print(f'Moved originals to {BACKUP_DIR} and wrote mapping to {map_csv}')
+ return 0
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--apply', action='store_true', help='Actually perform moves')
+ args = parser.parse_args()
+ perform(dry_run=not args.apply)
diff --git a/server.js b/server.js
index 129f9c7..cdd6c54 100644
--- a/server.js
+++ b/server.js
@@ -15,6 +15,14 @@ const helmet = require('helmet');
const cors = require("cors");
const swaggerUi = require("swagger-ui-express");
const yaml = require("yamljs");
+// swagger-jsdoc will be used at runtime to include JSDoc @swagger comments from route files
+let swaggerJSDoc;
+try {
+ swaggerJSDoc = require('swagger-jsdoc');
+} catch (e) {
+ // swagger-jsdoc may not be installed in some environments; we will fall back to static index.yaml
+ swaggerJSDoc = null;
+}
const { exec } = require("child_process");
const rateLimit = require('express-rate-limit');
const uploadRoutes = require('./routes/uploadRoutes');
@@ -100,7 +108,36 @@ const limiter = rateLimit({
app.use(limiter);
// Swagger
-const swaggerDocument = yaml.load("./index.yaml");
+let swaggerDocument = yaml.load("./index.yaml");
+// Remove externalDocs if present to avoid CORS issues
+if (swaggerDocument && swaggerDocument.externalDocs) {
+ delete swaggerDocument.externalDocs;
+}
+
+// If swagger-jsdoc is available, attempt to generate docs from JSDoc comments and merge paths
+if (swaggerJSDoc) {
+ try {
+ const options = {
+ definition: {
+ openapi: '3.0.0',
+ },
+ // Scan route files and controllers for @swagger JSDoc comments
+ apis: ["./routes/**/*.js", "./controller/**/*.js"],
+ };
+ const generated = swaggerJSDoc(options);
+ // Merge generated.paths into the static swaggerDocument.paths (generated takes precedence)
+ swaggerDocument.paths = Object.assign({}, swaggerDocument.paths || {}, generated.paths || {});
+ // Merge components.schemas if present
+ if (generated.components && generated.components.schemas) {
+ swaggerDocument.components = swaggerDocument.components || {};
+ swaggerDocument.components.schemas = Object.assign({}, swaggerDocument.components.schemas || {}, generated.components.schemas);
+ }
+ } catch (e) {
+ console.error('Failed to generate swagger from JSDoc:', e && e.message ? e.message : e);
+ // keep swaggerDocument as loaded from index.yaml
+ }
+}
+
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
// Response time monitoring
app.use(responseTimeLogger);
diff --git a/test-Supabse.js b/test-Supabse.js
new file mode 100644
index 0000000..d6362b3
--- /dev/null
+++ b/test-Supabse.js
@@ -0,0 +1,57 @@
+// testSupabase.js
+const { createClient } = require('@supabase/supabase-js');
+require('dotenv').config();
+
+const supabaseUrl = process.env.SUPABASE_URL;
+const supabaseKey = process.env.SUPABASE_ANON_KEY;
+
+console.log('SUPABASE_URL:', supabaseUrl ? 'SET' : 'MISSING');
+console.log('SUPABASE_ANON_KEY:', supabaseKey ? 'SET' : 'MISSING');
+
+const supabase = createClient(supabaseUrl, supabaseKey);
+
+async function testSecurityAssessmentsTable() {
+ console.log('Testing security_assessments table...');
+
+ try {
+ // 1. 测试查询权限
+ console.log('\n1. Testing SELECT...');
+ let { data: queryData, error: queryError } = await supabase
+ .from('security_assessments')
+ .select('*')
+ .limit(1);
+
+ if (queryError) {
+ console.error('Query Error:', queryError);
+ } else {
+ console.log('Query successful, records found:', queryData.length);
+ }
+
+ // 2. 测试插入权限
+ console.log('\n2. Testing INSERT...');
+ let { data: insertData, error: insertError } = await supabase
+ .from('security_assessments')
+ .insert([{
+ timestamp: new Date().toISOString(),
+ overall_score: 75,
+ total_checks: 8,
+ passed_checks: 6,
+ failed_checks: 1,
+ warnings: 1,
+ critical_issues: 0,
+ risk_level: 'low',
+ detailed_results: { test: 'connection_test' }
+ }]);
+
+ if (insertError) {
+ console.error('Insert Error:', insertError);
+ } else {
+ console.log('Insert successful:', insertData);
+ }
+
+ } catch (err) {
+ console.error('Connection failed:', err.message);
+ }
+}
+
+testSecurityAssessmentsTable();
\ No newline at end of file
diff --git a/vulnerability_report.json b/vulnerability_report.json
new file mode 100644
index 0000000..fc77778
--- /dev/null
+++ b/vulnerability_report.json
@@ -0,0 +1,710 @@
+{
+ "scan_id": "fdb7026f-d7c3-414f-87fb-9b3a31c383aa",
+ "target": ".",
+ "timestamp": "2025-09-19T05:39:04.909528",
+ "findings": [
+ {
+ "title": "Missing JWT Protection: GET /",
+ "severity": "MEDIUM",
+ "file_path": "jwt server.js",
+ "line_number": 11,
+ "description": "API endpoint GET / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: POST /",
+ "severity": "MEDIUM",
+ "file_path": "routes/imageClassification.js",
+ "line_number": 19,
+ "description": "API endpoint POST / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: POST /",
+ "severity": "MEDIUM",
+ "file_path": "routes/upload.js",
+ "line_number": 5,
+ "description": "API endpoint POST / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: POST /",
+ "severity": "MEDIUM",
+ "file_path": "routes/waterIntake.js",
+ "line_number": 5,
+ "description": "API endpoint POST / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: POST /",
+ "severity": "MEDIUM",
+ "file_path": "routes/signup.js",
+ "line_number": 11,
+ "description": "API endpoint POST / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: GET /ping",
+ "severity": "MEDIUM",
+ "file_path": "routes/loginDashboard.js",
+ "line_number": 11,
+ "description": "API endpoint GET /ping lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET /ping endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/ping', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/ping', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/ping",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: GET /kpi",
+ "severity": "MEDIUM",
+ "file_path": "routes/loginDashboard.js",
+ "line_number": 22,
+ "description": "API endpoint GET /kpi lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET /kpi endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/kpi', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/kpi', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/kpi",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: GET /daily",
+ "severity": "MEDIUM",
+ "file_path": "routes/loginDashboard.js",
+ "line_number": 29,
+ "description": "API endpoint GET /daily lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET /daily endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/daily', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/daily', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/daily",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: GET /dau",
+ "severity": "MEDIUM",
+ "file_path": "routes/loginDashboard.js",
+ "line_number": 37,
+ "description": "API endpoint GET /dau lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET /dau endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/dau', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/dau', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/dau",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: GET /top-failing-ips",
+ "severity": "MEDIUM",
+ "file_path": "routes/loginDashboard.js",
+ "line_number": 45,
+ "description": "API endpoint GET /top-failing-ips lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET /top-failing-ips endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/top-failing-ips', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/top-failing-ips', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/top-failing-ips",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: GET /fail-by-domain",
+ "severity": "MEDIUM",
+ "file_path": "routes/loginDashboard.js",
+ "line_number": 52,
+ "description": "API endpoint GET /fail-by-domain lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET /fail-by-domain endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/fail-by-domain', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/fail-by-domain', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/fail-by-domain",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: POST /",
+ "severity": "MEDIUM",
+ "file_path": "routes/login.js",
+ "line_number": 11,
+ "description": "API endpoint POST / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: POST /createRecipe",
+ "severity": "MEDIUM",
+ "file_path": "routes/recipe.js",
+ "line_number": 8,
+ "description": "API endpoint POST /createRecipe lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST /createRecipe endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/createRecipe', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/createRecipe', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/createRecipe",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: POST /",
+ "severity": "MEDIUM",
+ "file_path": "routes/recipe.js",
+ "line_number": 10,
+ "description": "API endpoint POST / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: DELETE /",
+ "severity": "MEDIUM",
+ "file_path": "routes/recipe.js",
+ "line_number": 11,
+ "description": "API endpoint DELETE / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the DELETE / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.delete('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.delete('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "DELETE"
+ },
+ {
+ "title": "Missing JWT Protection: GET /",
+ "severity": "MEDIUM",
+ "file_path": "routes/recipeNutritionlog.js",
+ "line_number": 27,
+ "description": "API endpoint GET / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: POST /",
+ "severity": "MEDIUM",
+ "file_path": "routes/userfeedback.js",
+ "line_number": 8,
+ "description": "API endpoint POST / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: GET /",
+ "severity": "MEDIUM",
+ "file_path": "routes/healthNews.js",
+ "line_number": 44,
+ "description": "API endpoint GET / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: POST /",
+ "severity": "MEDIUM",
+ "file_path": "routes/healthNews.js",
+ "line_number": 156,
+ "description": "API endpoint POST / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: PUT /",
+ "severity": "MEDIUM",
+ "file_path": "routes/healthNews.js",
+ "line_number": 214,
+ "description": "API endpoint PUT / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the PUT / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.put('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.put('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "PUT"
+ },
+ {
+ "title": "Missing JWT Protection: DELETE /",
+ "severity": "MEDIUM",
+ "file_path": "routes/healthNews.js",
+ "line_number": 238,
+ "description": "API endpoint DELETE / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the DELETE / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.delete('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.delete('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "DELETE"
+ },
+ {
+ "title": "Missing JWT Protection: POST /generate-baseline",
+ "severity": "MEDIUM",
+ "file_path": "routes/systemRoutes.js",
+ "line_number": 50,
+ "description": "API endpoint POST /generate-baseline lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST /generate-baseline endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/generate-baseline', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/generate-baseline', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/generate-baseline",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: GET /common",
+ "severity": "MEDIUM",
+ "file_path": "routes/allergyRoutes.js",
+ "line_number": 99,
+ "description": "API endpoint GET /common lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET /common endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/common', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/common', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/common",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: POST /check",
+ "severity": "MEDIUM",
+ "file_path": "routes/allergyRoutes.js",
+ "line_number": 127,
+ "description": "API endpoint POST /check lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST /check endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/check', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/check', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/check",
+ "http_method": "POST"
+ },
+ {
+ "title": "Missing JWT Protection: GET /",
+ "severity": "MEDIUM",
+ "file_path": "routes/filter.js",
+ "line_number": 7,
+ "description": "API endpoint GET / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: GET /",
+ "severity": "MEDIUM",
+ "file_path": "routes/articles.js",
+ "line_number": 5,
+ "description": "API endpoint GET / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the GET / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.get('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.get('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "GET"
+ },
+ {
+ "title": "Missing JWT Protection: POST /",
+ "severity": "MEDIUM",
+ "file_path": "routes/contactus.js",
+ "line_number": 14,
+ "description": "API endpoint POST / lacks JWT authentication middleware",
+ "plugin_name": "JWTMissingProtectionPlugin",
+ "recommendation": {
+ "summary": "Protect the POST / endpoint with authentication middleware.",
+ "steps": [
+ "Import authentication middleware: const authenticateToken = require('../middleware/authenticateToken');",
+ "Add middleware to route: router.post('/', authenticateToken, (req, res) => { /* handler */ });",
+ "Ensure JWT configuration is secure: use strong secrets, set appropriate expiration, and handle errors properly."
+ ],
+ "code": "router.post('/', authenticateToken, (req, res) => {\n // Your route handler\n});"
+ },
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": "/",
+ "http_method": "POST"
+ },
+ {
+ "title": "Low Entropy JWT Secret",
+ "severity": "MEDIUM",
+ "file_path": ".env",
+ "line_number": 8,
+ "description": "JWT secret appears to have low entropy (predictable patterns).",
+ "plugin_name": "JWTConfigurationPlugin",
+ "recommendation": "Improve JWT secret security:\n1. Generate a strong secret using crypto:\n const crypto = require('crypto');\n const secret = crypto.randomBytes(64).toString('hex');\n\n2. Use environment-specific secrets\n3. Implement secret rotation\n4. Consider using asymmetric keys for larger systems",
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": null,
+ "http_method": null
+ },
+ {
+ "title": "Low Entropy JWT Secret",
+ "severity": "MEDIUM",
+ "file_path": ".env",
+ "line_number": 10,
+ "description": "JWT secret appears to have low entropy (predictable patterns).",
+ "plugin_name": "JWTConfigurationPlugin",
+ "recommendation": "Improve JWT secret security:\n1. Generate a strong secret using crypto:\n const crypto = require('crypto');\n const secret = crypto.randomBytes(64).toString('hex');\n\n2. Use environment-specific secrets\n3. Implement secret rotation\n4. Consider using asymmetric keys for larger systems",
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": null,
+ "http_method": null
+ },
+ {
+ "title": "Direct JWT Usage Instead of AuthService",
+ "severity": "MEDIUM",
+ "file_path": "middleware.js",
+ "line_number": null,
+ "description": "Direct jwt.verify() usage detected instead of centralized authService.",
+ "plugin_name": "JWTConfigurationPlugin",
+ "recommendation": "Centralize JWT verification:\n1. Create AuthService class\n2. Move all JWT operations to AuthService\n3. Use AuthService.verifyToken() in middleware\n4. Add comprehensive error handling",
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": null,
+ "http_method": null
+ },
+ {
+ "title": "Incomplete JWT Error Handling",
+ "severity": "LOW",
+ "file_path": "middleware.js",
+ "line_number": null,
+ "description": "JWT verification lacks comprehensive error handling.",
+ "plugin_name": "JWTConfigurationPlugin",
+ "recommendation": "Implement proper JWT error handling:\n1. Handle TokenExpiredError\n2. Handle JsonWebTokenError\n3. Handle NotBeforeError\n4. Add logging for security events\n5. Return appropriate status codes",
+ "rule_id": null,
+ "rule_name": null,
+ "rule_mode": null,
+ "confidence": null,
+ "route": null,
+ "http_method": null
+ }
+ ],
+ "summary": {
+ "total": 31,
+ "files_scanned": 147,
+ "by_severity": {
+ "MEDIUM": 30,
+ "LOW": 1
+ },
+ "by_plugin": {
+ "JWTMissingProtectionPlugin": 27,
+ "JWTConfigurationPlugin": 4
+ }
+ },
+ "scan_info": {
+ "target_path": ".",
+ "timestamp": "2025-09-19T05:39:04.909528",
+ "scanner_version": "2.0.0",
+ "stats": {
+ "files_scanned": 147,
+ "plugins_loaded": 3,
+ "total_findings": 31
+ }
+ }
+}
\ No newline at end of file