Initial commit: CVE Dashboard v1.0

This commit is contained in:
2026-01-27 04:06:03 +00:00
commit 80f32b0412
5 changed files with 579 additions and 0 deletions

39
.gitignore vendored Normal file
View File

@@ -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/

312
backend/server.js Normal file
View File

@@ -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}`);
});

209
backend/setup.js Normal file
View File

@@ -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();

1
frontend Submodule

Submodule frontend added at 4f0cb0a6cc

18
package.json Normal file
View File

@@ -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"
}
}