commit 80f32b041240bc39248a1d59391b2b100016e288 Author: jramos Date: Tue Jan 27 04:06:03 2026 +0000 Initial commit: CVE Dashboard v1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbb3c9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Node modules +node_modules/ +package-lock.json + +# Database +backend/cve_database.db +backend/*.db + +# Logs +*.log +backend/backend.log +frontend/frontend.log + +# Uploads (contain sensitive files) +uploads/ + +# Environment files +.env +*.env + +# Build files +frontend/build/ +frontend/.eslintcache + +# IDE +.vscode/ +.idea/ + +# OS files +.DS_Store +Thumbs.db + +# Process IDs +*.pid +backend.pid +frontend.pid + +# Temporary files +backend/uploads/temp/ diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..22f8448 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,312 @@ +// CVE Management Backend API +// Install: npm install express sqlite3 multer cors + +const express = require('express'); +const sqlite3 = require('sqlite3').verbose(); +const multer = require('multer'); +const cors = require('cors'); +const path = require('path'); +const fs = require('fs'); + +const app = express(); +const PORT = 3001; + +// Middleware +app.use(cors({ + origin: ['http://localhost:3000', 'http://192.168.2.117:3000'], + credentials: true +})); +app.use(express.json()); +app.use('/uploads', express.static('uploads')); + +// Database connection +const db = new sqlite3.Database('./cve_database.db', (err) => { + if (err) console.error('Database connection error:', err); + else console.log('Connected to CVE database'); +}); + +// Simple storage - upload to temp directory first +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + const tempDir = 'uploads/temp'; + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); + } + cb(null, tempDir); + }, + filename: (req, file, cb) => { + const timestamp = Date.now(); + cb(null, `${timestamp}-${file.originalname}`); + } +}); + +const upload = multer({ + storage: storage, + limits: { fileSize: 10 * 1024 * 1024 } // 10MB limit +}); + +// ========== CVE ENDPOINTS ========== + +// Get all CVEs with optional filters +app.get('/api/cves', (req, res) => { + const { search, vendor, severity, status } = req.query; + + let query = ` + SELECT c.*, + COUNT(d.id) as document_count, + CASE + WHEN COUNT(CASE WHEN d.type = 'advisory' THEN 1 END) > 0 + THEN 'Complete' + ELSE 'Incomplete' + END as doc_status + FROM cves c + LEFT JOIN documents d ON c.cve_id = d.cve_id + WHERE 1=1 + `; + + const params = []; + + if (search) { + query += ` AND (c.cve_id LIKE ? OR c.description LIKE ?)`; + params.push(`%${search}%`, `%${search}%`); + } + if (vendor && vendor !== 'All Vendors') { + query += ` AND c.vendor = ?`; + params.push(vendor); + } + if (severity && severity !== 'All Severities') { + query += ` AND c.severity = ?`; + params.push(severity); + } + if (status) { + query += ` AND c.status = ?`; + params.push(status); + } + + query += ` GROUP BY c.id ORDER BY c.published_date DESC`; + + db.all(query, params, (err, rows) => { + if (err) { + console.error('Error fetching CVEs:', err); + return res.status(500).json({ error: err.message }); + } + res.json(rows); + }); +}); + +// Check if CVE exists and get its status +app.get('/api/cves/check/:cveId', (req, res) => { + const { cveId } = req.params; + + const query = ` + SELECT c.*, + COUNT(d.id) as total_documents, + COUNT(CASE WHEN d.type = 'advisory' THEN 1 END) as has_advisory, + COUNT(CASE WHEN d.type = 'email' THEN 1 END) as has_email, + COUNT(CASE WHEN d.type = 'screenshot' THEN 1 END) as has_screenshot + FROM cves c + LEFT JOIN documents d ON c.cve_id = d.cve_id + WHERE c.cve_id = ? + GROUP BY c.id + `; + + db.get(query, [cveId], (err, row) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + if (!row) { + return res.json({ + exists: false, + message: 'CVE not found - not yet addressed' + }); + } + + res.json({ + exists: true, + cve: row, + addressed: true, + has_required_docs: row.has_advisory > 0, + compliance: { + advisory: row.has_advisory > 0, + email: row.has_email > 0, + screenshot: row.has_screenshot > 0 + } + }); + }); +}); + +// Create new CVE entry +app.post('/api/cves', (req, res) => { + const { cve_id, vendor, severity, description, published_date } = req.body; + + const query = ` + INSERT INTO cves (cve_id, vendor, severity, description, published_date) + VALUES (?, ?, ?, ?, ?) + `; + + db.run(query, [cve_id, vendor, severity, description, published_date], function(err) { + if (err) { + return res.status(500).json({ error: err.message }); + } + res.json({ + id: this.lastID, + cve_id, + message: 'CVE created successfully' + }); + }); +}); + +// Update CVE status +app.patch('/api/cves/:cveId/status', (req, res) => { + const { cveId } = req.params; + const { status } = req.body; + + const query = `UPDATE cves SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE cve_id = ?`; + + db.run(query, [status, cveId], function(err) { + if (err) { + return res.status(500).json({ error: err.message }); + } + res.json({ message: 'Status updated successfully', changes: this.changes }); + }); +}); + +// ========== DOCUMENT ENDPOINTS ========== + +// Get documents for a CVE +app.get('/api/cves/:cveId/documents', (req, res) => { + const { cveId } = req.params; + + const query = `SELECT * FROM documents WHERE cve_id = ? ORDER BY uploaded_at DESC`; + + db.all(query, [cveId], (err, rows) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + res.json(rows); + }); +}); + +// Upload document +app.post('/api/cves/:cveId/documents', upload.single('file'), (req, res) => { + const { cveId } = req.params; + const { type, notes, vendor } = req.body; + const file = req.file; + + if (!file) { + return res.status(400).json({ error: 'No file uploaded' }); + } + + if (!vendor) { + return res.status(400).json({ error: 'Vendor is required' }); + } + + // Move file from temp to proper location + const finalDir = path.join('uploads', cveId, vendor); + if (!fs.existsSync(finalDir)) { + fs.mkdirSync(finalDir, { recursive: true }); + } + + const finalPath = path.join(finalDir, file.filename); + + // Move file from temp to final location + fs.renameSync(file.path, finalPath); + + const query = ` + INSERT INTO documents (cve_id, name, type, file_path, file_size, mime_type, notes) + VALUES (?, ?, ?, ?, ?, ?, ?) + `; + + const fileSizeKB = (file.size / 1024).toFixed(2) + ' KB'; + + db.run(query, [ + cveId, + file.originalname, + type, + finalPath, + fileSizeKB, + file.mimetype, + notes + ], function(err) { + if (err) { + // If database insert fails, delete the file + if (fs.existsSync(finalPath)) { + fs.unlinkSync(finalPath); + } + return res.status(500).json({ error: err.message }); + } + res.json({ + id: this.lastID, + message: 'Document uploaded successfully', + file: { + name: file.originalname, + path: finalPath, + size: fileSizeKB + } + }); + }); +}); + +// Delete document +app.delete('/api/documents/:id', (req, res) => { + const { id } = req.params; + + // First get the file path to delete the actual file + db.get('SELECT file_path FROM documents WHERE id = ?', [id], (err, row) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + + if (row && fs.existsSync(row.file_path)) { + fs.unlinkSync(row.file_path); + } + + db.run('DELETE FROM documents WHERE id = ?', [id], function(err) { + if (err) { + return res.status(500).json({ error: err.message }); + } + res.json({ message: 'Document deleted successfully' }); + }); + }); +}); + +// ========== UTILITY ENDPOINTS ========== + +// Get all vendors +app.get('/api/vendors', (req, res) => { + const query = `SELECT DISTINCT vendor FROM cves ORDER BY vendor`; + + db.all(query, [], (err, rows) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + res.json(rows.map(r => r.vendor)); + }); +}); + +// Get statistics +app.get('/api/stats', (req, res) => { + const query = ` + SELECT + COUNT(DISTINCT c.id) as total_cves, + COUNT(DISTINCT CASE WHEN c.severity = 'Critical' THEN c.id END) as critical_count, + COUNT(DISTINCT CASE WHEN c.status = 'Addressed' THEN c.id END) as addressed_count, + COUNT(DISTINCT d.id) as total_documents, + COUNT(DISTINCT CASE WHEN cd.compliance_status = 'Complete' THEN c.id END) as compliant_count + FROM cves c + LEFT JOIN documents d ON c.cve_id = d.cve_id + LEFT JOIN cve_document_status cd ON c.cve_id = cd.cve_id + `; + + db.get(query, [], (err, row) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + res.json(row); + }); +}); + +// Start server +app.listen(PORT, () => { + console.log(`CVE API server running on http://localhost:${PORT}`); +}); diff --git a/backend/setup.js b/backend/setup.js new file mode 100644 index 0000000..7b62398 --- /dev/null +++ b/backend/setup.js @@ -0,0 +1,209 @@ +// Setup Script for CVE Database +// This creates a fresh database ready for new CVE entries + +const sqlite3 = require('sqlite3').verbose(); +const fs = require('fs'); +const path = require('path'); + +const DB_FILE = './cve_database.db'; +const UPLOADS_DIR = './uploads'; + +// Initialize database with schema +function initializeDatabase() { + return new Promise((resolve, reject) => { + const db = new sqlite3.Database(DB_FILE, (err) => { + if (err) reject(err); + }); + + const schema = ` + CREATE TABLE IF NOT EXISTS cves ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cve_id VARCHAR(20) UNIQUE NOT NULL, + vendor VARCHAR(100) NOT NULL, + severity VARCHAR(20) NOT NULL, + description TEXT, + published_date DATE, + status VARCHAR(50) DEFAULT 'Open', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS documents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + cve_id VARCHAR(20) NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(50) NOT NULL, + file_path VARCHAR(500) NOT NULL, + file_size VARCHAR(20), + mime_type VARCHAR(100), + uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + notes TEXT, + FOREIGN KEY (cve_id) REFERENCES cves(cve_id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS required_documents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + vendor VARCHAR(100) NOT NULL, + document_type VARCHAR(50) NOT NULL, + is_mandatory BOOLEAN DEFAULT 1, + description TEXT + ); + + CREATE INDEX IF NOT EXISTS idx_cve_id ON cves(cve_id); + CREATE INDEX IF NOT EXISTS idx_vendor ON cves(vendor); + CREATE INDEX IF NOT EXISTS idx_severity ON cves(severity); + CREATE INDEX IF NOT EXISTS idx_status ON cves(status); + CREATE INDEX IF NOT EXISTS idx_doc_cve_id ON documents(cve_id); + CREATE INDEX IF NOT EXISTS idx_doc_type ON documents(type); + + INSERT OR IGNORE INTO required_documents (vendor, document_type, is_mandatory, description) VALUES + ('Microsoft', 'advisory', 1, 'Official Microsoft Security Advisory'), + ('Microsoft', 'screenshot', 0, 'Proof of patch application'), + ('Cisco', 'advisory', 1, 'Cisco Security Advisory'), + ('Oracle', 'advisory', 1, 'Oracle Security Alert'), + ('VMware', 'advisory', 1, 'VMware Security Advisory'), + ('Adobe', 'advisory', 1, 'Adobe Security Bulletin'); + + CREATE VIEW IF NOT EXISTS cve_document_status AS + SELECT + c.cve_id, + c.vendor, + c.severity, + c.status, + COUNT(DISTINCT d.id) as total_documents, + COUNT(DISTINCT CASE WHEN d.type = 'advisory' THEN d.id END) as advisory_count, + COUNT(DISTINCT CASE WHEN d.type = 'email' THEN d.id END) as email_count, + COUNT(DISTINCT CASE WHEN d.type = 'screenshot' THEN d.id END) as screenshot_count, + CASE + WHEN COUNT(DISTINCT CASE WHEN d.type = 'advisory' THEN d.id END) > 0 + THEN 'Complete' + ELSE 'Missing Required Docs' + END as compliance_status + FROM cves c + LEFT JOIN documents d ON c.cve_id = d.cve_id + GROUP BY c.cve_id, c.vendor, c.severity, c.status; + `; + + db.exec(schema, (err) => { + if (err) { + reject(err); + } else { + console.log('āœ“ Database initialized successfully'); + resolve(db); + } + }); + }); +} + +// Create uploads directory structure +function createUploadsDirectory() { + if (!fs.existsSync(UPLOADS_DIR)) { + fs.mkdirSync(UPLOADS_DIR, { recursive: true }); + console.log('āœ“ Created uploads directory'); + } else { + console.log('āœ“ Uploads directory already exists'); + } +} + +// Add sample CVE data (optional - for testing) +async function addSampleData(db) { + console.log('\nšŸ“ Would you like to add sample CVE data for testing? (y/n)'); + + // For automated setup, we'll skip this. Uncomment the code below if you want samples. + + /* + const sampleCVEs = [ + { + cve_id: 'CVE-2024-SAMPLE-1', + vendor: 'Microsoft', + severity: 'Critical', + description: 'Sample vulnerability for testing', + published_date: '2024-01-15' + } + ]; + + for (const cve of sampleCVEs) { + await new Promise((resolve, reject) => { + db.run( + `INSERT OR IGNORE INTO cves (cve_id, vendor, severity, description, published_date) + VALUES (?, ?, ?, ?, ?)`, + [cve.cve_id, cve.vendor, cve.severity, cve.description, cve.published_date], + (err) => { + if (err) reject(err); + else { + console.log(` āœ“ Added sample CVE: ${cve.cve_id}`); + resolve(); + } + } + ); + }); + } + */ + + console.log('ā„¹ļø Skipping sample data - you can add CVEs through the API or dashboard'); +} + +// Display setup summary +function displaySummary() { + console.log('\n╔════════════════════════════════════════════════════════╗'); + console.log('ā•‘ CVE DATABASE SETUP COMPLETE! ā•‘'); + console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•'); + console.log('\nšŸ“Š What was created:'); + console.log(' āœ“ SQLite database (cve_database.db)'); + console.log(' āœ“ Tables: cves, documents, required_documents'); + console.log(' āœ“ Indexes for fast queries'); + console.log(' āœ“ Document compliance view'); + console.log(' āœ“ Uploads directory for file storage'); + console.log('\nšŸ“ File structure will be:'); + console.log(' uploads/'); + console.log(' └── CVE-XXXX-XXXX/'); + console.log(' └── VendorName/'); + console.log(' ā”œā”€ā”€ advisory.pdf'); + console.log(' ā”œā”€ā”€ email.pdf'); + console.log(' └── screenshot.png'); + console.log('\nšŸš€ Next steps:'); + console.log(' 1. Start the backend API:'); + console.log(' → cd backend && node server.js'); + console.log(' 2. Start the frontend:'); + console.log(' → cd frontend && npm start'); + console.log(' 3. Open http://localhost:3000'); + console.log(' 4. Start adding CVEs and uploading documents!'); + console.log('\nšŸ’” Tips:'); + console.log(' • Use the Quick Check to verify CVE status'); + console.log(' • Upload documents through the dashboard'); + console.log(' • Documents are auto-organized by CVE ID → Vendor'); + console.log(' • Required docs: Advisory (mandatory for most vendors)\n'); +} + +// Main execution +async function main() { + console.log('šŸš€ CVE Database Setup\n'); + console.log('════════════════════════════════════════\n'); + + try { + // Create uploads directory + createUploadsDirectory(); + + // Initialize database + const db = await initializeDatabase(); + + // Optionally add sample data + await addSampleData(db); + + // Close database connection + db.close((err) => { + if (err) console.error('Error closing database:', err); + else console.log('āœ“ Database connection closed'); + + // Display summary + displaySummary(); + }); + + } catch (error) { + console.error('āŒ Setup Error:', error); + process.exit(1); + } +} + +// Run the setup +main(); \ No newline at end of file diff --git a/frontend b/frontend new file mode 160000 index 0000000..4f0cb0a --- /dev/null +++ b/frontend @@ -0,0 +1 @@ +Subproject commit 4f0cb0a6cc1500720cc24916bcffd305170fcf66 diff --git a/package.json b/package.json new file mode 100644 index 0000000..3c4a60f --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "cve-dashboard", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "cors": "^2.8.6", + "express": "^5.2.1", + "multer": "^2.0.2", + "sqlite3": "^5.1.7" + } +}