init commit. main app + frontend/backend

This commit is contained in:
Brendan McDevitt 2025-07-08 08:34:28 -05:00
commit 967886ef49
13 changed files with 1512 additions and 0 deletions

12
.env.example Normal file
View file

@ -0,0 +1,12 @@
# NVD API Configuration
# Get your free API key at: https://nvd.nist.gov/developers/request-an-api-key
NVD_API_KEY=your_nvd_api_key_here
# Database Configuration (Docker Compose will use defaults)
# DATABASE_URL=postgresql://cve_user:cve_password@localhost:5432/cve_sigma_db
# Frontend Configuration
# REACT_APP_API_URL=http://localhost:8000
# Optional: Redis Configuration
# REDIS_URL=redis://localhost:6379

231
README.md Executable file
View file

@ -0,0 +1,231 @@
# CVE-SIGMA Auto Generator
An automated platform that fetches CVE data and automatically generates SIGMA rules for threat detection.
## Features
- **Automated CVE Fetching**: Regularly polls the NVD (National Vulnerability Database) for new CVEs
- **Intelligent SIGMA Rule Generation**: Automatically creates SIGMA rules based on CVE characteristics
- **Modern Web Interface**: React-based UI for browsing CVEs and managing SIGMA rules
- **Real-time Updates**: Background tasks keep CVE data current
- **Rule Templates**: Configurable templates for different types of vulnerabilities
- **Docker Compose**: Easy deployment and orchestration
## Architecture
- **Backend**: FastAPI with SQLAlchemy ORM
- **Frontend**: React with Tailwind CSS
- **Database**: PostgreSQL
- **Cache**: Redis (optional)
- **Containerization**: Docker & Docker Compose
## Quick Start
### Prerequisites
- Docker and Docker Compose
- (Optional) NVD API Key for increased rate limits
### Setup
1. Clone the repository:
```bash
git clone <repository-url>
cd cve-sigma-generator
```
2. (Optional) Set your NVD API Key:
```bash
export NVD_API_KEY="your-api-key-here"
```
3. Start the application:
```bash
docker-compose up -d
```
4. Wait for services to initialize (about 30-60 seconds)
5. Access the application:
- Frontend: http://localhost:3000
- Backend API: http://localhost:8000
- API Documentation: http://localhost:8000/docs
### First Run
The application will automatically:
1. Initialize the database with rule templates
2. Start fetching recent CVEs from NVD
3. Generate SIGMA rules for each CVE
4. Continue polling for new CVEs every hour
## Usage
### Web Interface
The web interface provides three main sections:
1. **Dashboard**: Overview statistics and recent CVEs
2. **CVEs**: Complete list of all fetched CVEs with details
3. **SIGMA Rules**: Generated detection rules organized by CVE
### Manual CVE Fetch
You can trigger a manual CVE fetch using the "Fetch New CVEs" button in the dashboard or via API:
```bash
curl -X POST http://localhost:8000/api/fetch-cves
```
### API Endpoints
- `GET /api/cves` - List all CVEs
- `GET /api/cves/{cve_id}` - Get specific CVE details
- `GET /api/sigma-rules` - List all SIGMA rules
- `GET /api/sigma-rules/{cve_id}` - Get SIGMA rules for specific CVE
- `POST /api/fetch-cves` - Manually trigger CVE fetch
- `GET /api/stats` - Get application statistics
## Configuration
### Environment Variables
- `DATABASE_URL`: PostgreSQL connection string
- `NVD_API_KEY`: Optional NVD API key for higher rate limits
- `REACT_APP_API_URL`: Backend API URL for frontend
### Rule Templates
The application includes pre-configured rule templates for:
- Windows Process Execution
- Network Connections
- File Modifications
Additional templates can be added to the database via the `rule_templates` table.
## SIGMA Rule Generation Logic
The rule generation process:
1. **CVE Analysis**: Analyzes CVE description and affected products
2. **Template Selection**: Chooses appropriate SIGMA rule template
3. **Indicator Extraction**: Extracts suspicious processes, ports, or file patterns
4. **Rule Population**: Fills template with CVE-specific data
5. **Confidence Scoring**: Assigns confidence level based on CVSS score
### Template Matching
- **Process Execution**: Keywords like "process", "execution", "command"
- **Network Connection**: Keywords like "network", "remote", "connection"
- **File Modification**: Keywords like "file", "write", "filesystem"
## Development
### Local Development
1. Start the database:
```bash
docker-compose up -d db redis
```
2. Run the backend:
```bash
cd backend
pip install -r requirements.txt
uvicorn main:app --reload
```
3. Run the frontend:
```bash
cd frontend
npm install
npm start
```
### Database Migration
The application automatically creates tables on startup. For manual schema changes:
```bash
# Connect to database
docker-compose exec db psql -U cve_user -d cve_sigma_db
# Run custom SQL
\i /path/to/migration.sql
```
## SIGMA Rule Quality
Generated rules are marked as "experimental" and should be:
- Reviewed by security analysts
- Tested in a lab environment
- Tuned to reduce false positives
- Validated against real attack scenarios
## Monitoring
### Logs
View application logs:
```bash
# All services
docker-compose logs -f
# Specific service
docker-compose logs -f backend
```
### Health Checks
The application includes health checks for database connectivity. Monitor with:
```bash
docker-compose ps
```
## Troubleshooting
### Common Issues
1. **CVE Fetch Failing**: Check NVD API rate limits or network connectivity
2. **Database Connection Error**: Ensure PostgreSQL is running and accessible
3. **Frontend Not Loading**: Verify backend is running and CORS is configured
4. **Rule Generation Issues**: Check CVE description quality and template matching
### Rate Limits
Without an API key, NVD limits requests to 5 per 30 seconds. With an API key, the limit increases to 50 per 30 seconds.
## Security Considerations
- **API Keys**: Store NVD API keys securely using environment variables
- **Database Access**: Use strong passwords and restrict database access
- **Network Security**: Deploy behind a reverse proxy in production
- **Rule Validation**: Always validate generated SIGMA rules before deployment
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make changes and add tests
4. Submit a pull request
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Support
For issues and questions:
1. Check the troubleshooting section
2. Review application logs
3. Open an issue on GitHub
## Roadmap
Planned features:
- [ ] Custom rule template editor
- [ ] MITRE ATT&CK mapping
- [ ] Rule effectiveness scoring
- [ ] Export to SIEM platforms
- [ ] Advanced threat intelligence integration
- [ ] Machine learning-based rule optimization

26
backend/Dockerfile Normal file
View file

@ -0,0 +1,26 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first for better caching
COPY requirements.txt .
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

394
backend/main.py Normal file
View file

@ -0,0 +1,394 @@
from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from sqlalchemy import create_engine, Column, String, Text, DECIMAL, TIMESTAMP, Boolean, ARRAY
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.dialects.postgresql import UUID
import uuid
from datetime import datetime, timedelta
import requests
import json
import re
import os
from typing import List, Optional
from pydantic import BaseModel
import asyncio
from contextlib import asynccontextmanager
# Database setup
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://cve_user:cve_password@localhost:5432/cve_sigma_db")
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Database Models
class CVE(Base):
__tablename__ = "cves"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
cve_id = Column(String(20), unique=True, nullable=False)
description = Column(Text)
cvss_score = Column(DECIMAL(3, 1))
severity = Column(String(20))
published_date = Column(TIMESTAMP)
modified_date = Column(TIMESTAMP)
affected_products = Column(ARRAY(String))
references = Column(ARRAY(String))
created_at = Column(TIMESTAMP, default=datetime.utcnow)
updated_at = Column(TIMESTAMP, default=datetime.utcnow)
class SigmaRule(Base):
__tablename__ = "sigma_rules"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
cve_id = Column(String(20))
rule_name = Column(String(255), nullable=False)
rule_content = Column(Text, nullable=False)
detection_type = Column(String(50))
log_source = Column(String(100))
confidence_level = Column(String(20))
auto_generated = Column(Boolean, default=True)
created_at = Column(TIMESTAMP, default=datetime.utcnow)
updated_at = Column(TIMESTAMP, default=datetime.utcnow)
class RuleTemplate(Base):
__tablename__ = "rule_templates"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
template_name = Column(String(255), nullable=False)
template_content = Column(Text, nullable=False)
applicable_product_patterns = Column(ARRAY(String))
description = Column(Text)
created_at = Column(TIMESTAMP, default=datetime.utcnow)
# Pydantic models
class CVEResponse(BaseModel):
id: str
cve_id: str
description: Optional[str]
cvss_score: Optional[float]
severity: Optional[str]
published_date: Optional[datetime]
affected_products: Optional[List[str]]
class Config:
from_attributes = True
class SigmaRuleResponse(BaseModel):
id: str
cve_id: str
rule_name: str
rule_content: str
detection_type: Optional[str]
log_source: Optional[str]
confidence_level: Optional[str]
auto_generated: bool
created_at: datetime
class Config:
from_attributes = True
# CVE and SIGMA Rule Generator Service
class CVESigmaService:
def __init__(self, db: Session):
self.db = db
self.nvd_api_key = os.getenv("NVD_API_KEY")
async def fetch_recent_cves(self, days_back: int = 7):
"""Fetch recent CVEs from NVD API"""
end_date = datetime.utcnow()
start_date = end_date - timedelta(days=days_back)
url = "https://services.nvd.nist.gov/rest/json/cves/2.0"
params = {
"pubStartDate": start_date.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
"pubEndDate": end_date.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
"resultsPerPage": 100
}
headers = {}
if self.nvd_api_key:
headers["apiKey"] = self.nvd_api_key
try:
response = requests.get(url, params=params, headers=headers, timeout=30)
response.raise_for_status()
data = response.json()
new_cves = []
for vuln in data.get("vulnerabilities", []):
cve_data = vuln.get("cve", {})
cve_id = cve_data.get("id")
# Check if CVE already exists
existing = self.db.query(CVE).filter(CVE.cve_id == cve_id).first()
if existing:
continue
# Extract CVE information
description = ""
if cve_data.get("descriptions"):
description = cve_data["descriptions"][0].get("value", "")
cvss_score = None
severity = None
if cve_data.get("metrics", {}).get("cvssMetricV31"):
cvss_data = cve_data["metrics"]["cvssMetricV31"][0]
cvss_score = cvss_data.get("cvssData", {}).get("baseScore")
severity = cvss_data.get("cvssData", {}).get("baseSeverity")
affected_products = []
if cve_data.get("configurations"):
for config in cve_data["configurations"]:
for node in config.get("nodes", []):
for cpe_match in node.get("cpeMatch", []):
if cpe_match.get("vulnerable"):
affected_products.append(cpe_match.get("criteria", ""))
references = []
if cve_data.get("references"):
references = [ref.get("url", "") for ref in cve_data["references"]]
cve_obj = CVE(
cve_id=cve_id,
description=description,
cvss_score=cvss_score,
severity=severity,
published_date=datetime.fromisoformat(cve_data.get("published", "").replace("Z", "+00:00")),
modified_date=datetime.fromisoformat(cve_data.get("lastModified", "").replace("Z", "+00:00")),
affected_products=affected_products,
references=references
)
self.db.add(cve_obj)
new_cves.append(cve_obj)
self.db.commit()
return new_cves
except Exception as e:
print(f"Error fetching CVEs: {str(e)}")
return []
def generate_sigma_rule(self, cve: CVE) -> Optional[SigmaRule]:
"""Generate SIGMA rule based on CVE data"""
if not cve.description:
return None
# Analyze CVE to determine appropriate template
description_lower = cve.description.lower()
affected_products = [p.lower() for p in (cve.affected_products or [])]
template = self._select_template(description_lower, affected_products)
if not template:
return None
# Generate rule content
rule_content = self._populate_template(cve, template)
if not rule_content:
return None
# Determine detection type and confidence
detection_type = self._determine_detection_type(description_lower)
confidence_level = self._calculate_confidence(cve)
sigma_rule = SigmaRule(
cve_id=cve.cve_id,
rule_name=f"CVE-{cve.cve_id.split('-')[1]}-{cve.cve_id.split('-')[2]} Detection",
rule_content=rule_content,
detection_type=detection_type,
log_source=template.template_name.lower().replace(" ", "_"),
confidence_level=confidence_level,
auto_generated=True
)
self.db.add(sigma_rule)
return sigma_rule
def _select_template(self, description: str, affected_products: List[str]):
"""Select appropriate SIGMA rule template"""
templates = self.db.query(RuleTemplate).all()
# Simple template selection logic
if any("windows" in p or "microsoft" in p for p in affected_products):
if "process" in description or "execution" in description:
return next((t for t in templates if "Process Execution" in t.template_name), None)
elif "network" in description or "remote" in description:
return next((t for t in templates if "Network Connection" in t.template_name), None)
elif "file" in description or "write" in description:
return next((t for t in templates if "File Modification" in t.template_name), None)
# Default to process execution template
return next((t for t in templates if "Process Execution" in t.template_name), None)
def _populate_template(self, cve: CVE, template: RuleTemplate) -> str:
"""Populate template with CVE-specific data"""
try:
# Extract suspicious indicators from description
suspicious_processes = self._extract_suspicious_indicators(cve.description, "process")
suspicious_ports = self._extract_suspicious_indicators(cve.description, "port")
file_patterns = self._extract_suspicious_indicators(cve.description, "file")
# Determine severity level
level = "high" if cve.cvss_score and cve.cvss_score >= 7.0 else "medium"
rule_content = template.template_content.format(
title=f"CVE-{cve.cve_id} Exploitation Attempt",
description=cve.description[:200] + "..." if len(cve.description) > 200 else cve.description,
rule_id=str(uuid.uuid4()),
date=datetime.utcnow().strftime("%Y/%m/%d"),
cve_url=f"https://nvd.nist.gov/vuln/detail/{cve.cve_id}",
cve_id=cve.cve_id.lower(),
suspicious_processes=suspicious_processes or ["suspicious.exe", "malware.exe"],
suspicious_ports=suspicious_ports or [4444, 8080, 9999],
file_patterns=file_patterns or ["temp", "malware", "exploit"],
level=level
)
return rule_content
except Exception as e:
print(f"Error populating template: {str(e)}")
return None
def _extract_suspicious_indicators(self, description: str, indicator_type: str) -> List:
"""Extract suspicious indicators from CVE description"""
if indicator_type == "process":
# Look for executable names or process patterns
exe_pattern = re.findall(r'(\w+\.exe)', description, re.IGNORECASE)
return exe_pattern[:5] if exe_pattern else None
elif indicator_type == "port":
# Look for port numbers
port_pattern = re.findall(r'port\s+(\d+)', description, re.IGNORECASE)
return [int(p) for p in port_pattern[:3]] if port_pattern else None
elif indicator_type == "file":
# Look for file extensions or paths
file_pattern = re.findall(r'(\w+\.\w{3,4})', description, re.IGNORECASE)
return file_pattern[:5] if file_pattern else None
return None
def _determine_detection_type(self, description: str) -> str:
"""Determine detection type based on CVE description"""
if "remote" in description or "network" in description:
return "network"
elif "process" in description or "execution" in description:
return "process"
elif "file" in description or "filesystem" in description:
return "file"
else:
return "general"
def _calculate_confidence(self, cve: CVE) -> str:
"""Calculate confidence level for the generated rule"""
if cve.cvss_score and cve.cvss_score >= 9.0:
return "high"
elif cve.cvss_score and cve.cvss_score >= 7.0:
return "medium"
else:
return "low"
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Background task to fetch CVEs and generate rules
async def background_cve_fetch():
while True:
try:
db = SessionLocal()
service = CVESigmaService(db)
print("Fetching recent CVEs...")
new_cves = await service.fetch_recent_cves()
print(f"Found {len(new_cves)} new CVEs")
for cve in new_cves:
print(f"Generating SIGMA rule for {cve.cve_id}")
sigma_rule = service.generate_sigma_rule(cve)
if sigma_rule:
print(f"Generated rule: {sigma_rule.rule_name}")
db.commit()
db.close()
except Exception as e:
print(f"Background task error: {str(e)}")
# Wait 1 hour before next fetch
await asyncio.sleep(3600)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Start background task
task = asyncio.create_task(background_cve_fetch())
yield
# Clean up
task.cancel()
# FastAPI app
app = FastAPI(title="CVE-SIGMA Auto Generator", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/api/cves", response_model=List[CVEResponse])
async def get_cves(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)):
cves = db.query(CVE).order_by(CVE.published_date.desc()).offset(skip).limit(limit).all()
return cves
@app.get("/api/cves/{cve_id}", response_model=CVEResponse)
async def get_cve(cve_id: str, db: Session = Depends(get_db)):
cve = db.query(CVE).filter(CVE.cve_id == cve_id).first()
if not cve:
raise HTTPException(status_code=404, detail="CVE not found")
return cve
@app.get("/api/sigma-rules", response_model=List[SigmaRuleResponse])
async def get_sigma_rules(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)):
rules = db.query(SigmaRule).order_by(SigmaRule.created_at.desc()).offset(skip).limit(limit).all()
return rules
@app.get("/api/sigma-rules/{cve_id}", response_model=List[SigmaRuleResponse])
async def get_sigma_rules_by_cve(cve_id: str, db: Session = Depends(get_db)):
rules = db.query(SigmaRule).filter(SigmaRule.cve_id == cve_id).all()
return rules
@app.post("/api/fetch-cves")
async def manual_fetch_cves(background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
async def fetch_task():
service = CVESigmaService(db)
new_cves = await service.fetch_recent_cves()
for cve in new_cves:
service.generate_sigma_rule(cve)
db.commit()
background_tasks.add_task(fetch_task)
return {"message": "CVE fetch initiated"}
@app.get("/api/stats")
async def get_stats(db: Session = Depends(get_db)):
total_cves = db.query(CVE).count()
total_rules = db.query(SigmaRule).count()
recent_cves = db.query(CVE).filter(CVE.published_date >= datetime.utcnow() - timedelta(days=7)).count()
return {
"total_cves": total_cves,
"total_sigma_rules": total_rules,
"recent_cves_7_days": recent_cves
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

10
backend/requirements.txt Normal file
View file

@ -0,0 +1,10 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
pydantic==2.5.0
requests==2.31.0
python-multipart==0.0.6
redis==5.0.1
alembic==1.13.1
asyncpg==0.29.0

56
docker-compose.yml Normal file
View file

@ -0,0 +1,56 @@
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_DB: cve_sigma_db
POSTGRES_USER: cve_user
POSTGRES_PASSWORD: cve_password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U cve_user -d cve_sigma_db"]
interval: 30s
timeout: 10s
retries: 3
backend:
build: ./backend
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://cve_user:cve_password@db:5432/cve_sigma_db
NVD_API_KEY: ${NVD_API_KEY:-}
depends_on:
db:
condition: service_healthy
volumes:
- ./backend:/app
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
REACT_APP_API_URL: http://localhost:8000
volumes:
- ./frontend:/app
- /app/node_modules
command: npm start
redis:
image: redis:7-alpine
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:

24
frontend/Dockerfile Normal file
View file

@ -0,0 +1,24 @@
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy source code
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Change ownership
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]

47
frontend/package.json Normal file
View file

@ -0,0 +1,47 @@
{
"name": "cve-sigma-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"axios": "^1.6.0",
"react-router-dom": "^6.8.0",
"tailwindcss": "^3.3.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.24",
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
"react-syntax-highlighter": "^15.5.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://backend:8000"
}

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="CVE-SIGMA Auto Generator - Automatically generate SIGMA rules from CVE data"
/>
<title>CVE-SIGMA Auto Generator</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

126
frontend/src/App.css Normal file
View file

@ -0,0 +1,126 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.animate-spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* Custom scrollbar for syntax highlighter */
.react-syntax-highlighter-line-number {
color: #6b7280 !important;
}
/* Responsive table improvements */
@media (max-width: 768px) {
.overflow-x-auto {
-webkit-overflow-scrolling: touch;
}
table {
font-size: 0.875rem;
}
.px-6 {
padding-left: 1rem;
padding-right: 1rem;
}
}
/* Modal backdrop blur effect */
.fixed.inset-0.bg-gray-600 {
backdrop-filter: blur(4px);
}
/* Syntax highlighter theme overrides */
.language-yaml {
border-radius: 0.375rem;
max-height: 400px;
overflow-y: auto;
}
/* Loading spinner improvements */
.animate-spin {
border-top-color: transparent;
}
/* Badge hover effects */
.inline-flex.px-2.py-1 {
transition: all 0.2s ease-in-out;
}
.inline-flex.px-2.py-1:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* Button hover effects */
button {
transition: all 0.2s ease-in-out;
}
button:hover {
transform: translateY(-1px);
}
/* Card hover effects */
.hover\:bg-gray-50:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease-in-out;
}
/* Smooth transitions for tab switching */
.border-b-2 {
transition: border-color 0.2s ease-in-out;
}
/* Custom focus styles for accessibility */
button:focus,
.focus\:outline-none:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Table row hover effects */
tbody tr:hover {
background-color: #f9fafb;
transition: background-color 0.15s ease-in-out;
}
/* Responsive grid improvements */
@media (max-width: 640px) {
.grid-cols-1.md\:grid-cols-3 {
gap: 1rem;
}
}
/* Loading state styles */
.loading-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: .5;
}
}

425
frontend/src/App.js Normal file
View file

@ -0,0 +1,425 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism';
import './App.css';
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
function App() {
const [cves, setCves] = useState([]);
const [sigmaRules, setSigmaRules] = useState([]);
const [selectedCve, setSelectedCve] = useState(null);
const [stats, setStats] = useState({});
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState('dashboard');
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
setLoading(true);
const [cvesRes, rulesRes, statsRes] = await Promise.all([
axios.get(`${API_BASE_URL}/api/cves`),
axios.get(`${API_BASE_URL}/api/sigma-rules`),
axios.get(`${API_BASE_URL}/api/stats`)
]);
setCves(cvesRes.data);
setSigmaRules(rulesRes.data);
setStats(statsRes.data);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
};
const handleFetchCves = async () => {
try {
await axios.post(`${API_BASE_URL}/api/fetch-cves`);
setTimeout(fetchData, 2000); // Refresh after 2 seconds
} catch (error) {
console.error('Error fetching CVEs:', error);
}
};
const getSeverityColor = (severity) => {
switch (severity?.toLowerCase()) {
case 'critical': return 'bg-red-100 text-red-800';
case 'high': return 'bg-orange-100 text-orange-800';
case 'medium': return 'bg-yellow-100 text-yellow-800';
case 'low': return 'bg-green-100 text-green-800';
default: return 'bg-gray-100 text-gray-800';
}
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
};
const Dashboard = () => (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-lg font-medium text-gray-900">Total CVEs</h3>
<p className="text-3xl font-bold text-blue-600">{stats.total_cves || 0}</p>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-lg font-medium text-gray-900">SIGMA Rules</h3>
<p className="text-3xl font-bold text-green-600">{stats.total_sigma_rules || 0}</p>
</div>
<div className="bg-white p-6 rounded-lg shadow">
<h3 className="text-lg font-medium text-gray-900">Recent CVEs (7d)</h3>
<p className="text-3xl font-bold text-purple-600">{stats.recent_cves_7_days || 0}</p>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold text-gray-900">Recent CVEs</h2>
<button
onClick={handleFetchCves}
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md"
>
Fetch New CVEs
</button>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
CVE ID
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Severity
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
CVSS Score
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Published
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{cves.slice(0, 10).map((cve) => (
<tr key={cve.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{cve.cve_id}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(cve.severity)}`}>
{cve.severity || 'N/A'}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{cve.cvss_score || 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{cve.published_date ? formatDate(cve.published_date) : 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button
onClick={() => setSelectedCve(cve)}
className="text-blue-600 hover:text-blue-900"
>
View Details
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
const CVEList = () => (
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">All CVEs</h2>
</div>
<div className="divide-y divide-gray-200">
{cves.map((cve) => (
<div key={cve.id} className="p-6 hover:bg-gray-50">
<div className="flex items-center justify-between">
<div className="flex-1">
<h3 className="text-lg font-medium text-gray-900">{cve.cve_id}</h3>
<p className="text-sm text-gray-600 mt-1 line-clamp-2">
{cve.description}
</p>
<div className="flex items-center mt-2 space-x-4">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(cve.severity)}`}>
{cve.severity || 'N/A'}
</span>
<span className="text-sm text-gray-500">
CVSS: {cve.cvss_score || 'N/A'}
</span>
<span className="text-sm text-gray-500">
{cve.published_date ? formatDate(cve.published_date) : 'N/A'}
</span>
</div>
</div>
<button
onClick={() => setSelectedCve(cve)}
className="ml-4 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md"
>
View Details
</button>
</div>
</div>
))}
</div>
</div>
);
const SigmaRulesList = () => (
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-bold text-gray-900">Generated SIGMA Rules</h2>
</div>
<div className="divide-y divide-gray-200">
{sigmaRules.map((rule) => (
<div key={rule.id} className="p-6">
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-medium text-gray-900">{rule.rule_name}</h3>
<p className="text-sm text-gray-600">CVE: {rule.cve_id}</p>
<div className="flex items-center mt-2 space-x-4">
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
{rule.detection_type}
</span>
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
{rule.confidence_level}
</span>
{rule.auto_generated && (
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">
Auto-generated
</span>
)}
</div>
</div>
<span className="text-sm text-gray-500">
{formatDate(rule.created_at)}
</span>
</div>
<div className="mt-4">
<SyntaxHighlighter
language="yaml"
style={tomorrow}
className="rounded-md"
customStyle={{ fontSize: '12px' }}
>
{rule.rule_content}
</SyntaxHighlighter>
</div>
</div>
))}
</div>
</div>
);
const CVEDetail = ({ cve, onClose }) => {
const [cveRules, setCveRules] = useState([]);
useEffect(() => {
if (cve) {
fetchCveRules(cve.cve_id);
}
}, [cve]);
const fetchCveRules = async (cveId) => {
try {
const response = await axios.get(`${API_BASE_URL}/api/sigma-rules/${cveId}`);
setCveRules(response.data);
} catch (error) {
console.error('Error fetching CVE rules:', error);
}
};
return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div className="relative top-20 mx-auto p-5 border w-11/12 max-w-4xl shadow-lg rounded-md bg-white">
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-bold text-gray-900">{cve.cve_id}</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Details</h3>
<div className="bg-gray-50 p-4 rounded-md">
<div className="grid grid-cols-2 gap-4">
<div>
<span className="text-sm font-medium text-gray-500">Severity:</span>
<span className={`ml-2 inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(cve.severity)}`}>
{cve.severity || 'N/A'}
</span>
</div>
<div>
<span className="text-sm font-medium text-gray-500">CVSS Score:</span>
<span className="ml-2 text-sm text-gray-900">{cve.cvss_score || 'N/A'}</span>
</div>
<div>
<span className="text-sm font-medium text-gray-500">Published:</span>
<span className="ml-2 text-sm text-gray-900">
{cve.published_date ? formatDate(cve.published_date) : 'N/A'}
</span>
</div>
</div>
</div>
</div>
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Description</h3>
<p className="text-sm text-gray-700 bg-gray-50 p-4 rounded-md">
{cve.description}
</p>
</div>
{cve.affected_products && cve.affected_products.length > 0 && (
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Affected Products</h3>
<div className="bg-gray-50 p-4 rounded-md">
<ul className="list-disc list-inside space-y-1">
{cve.affected_products.slice(0, 5).map((product, index) => (
<li key={index} className="text-sm text-gray-700">{product}</li>
))}
{cve.affected_products.length > 5 && (
<li className="text-sm text-gray-500">... and {cve.affected_products.length - 5} more</li>
)}
</ul>
</div>
</div>
)}
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Generated SIGMA Rules ({cveRules.length})</h3>
{cveRules.length > 0 ? (
<div className="space-y-4">
{cveRules.map((rule) => (
<div key={rule.id} className="border border-gray-200 rounded-md p-4">
<div className="flex justify-between items-center mb-2">
<h4 className="font-medium text-gray-900">{rule.rule_name}</h4>
<div className="flex space-x-2">
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
{rule.detection_type}
</span>
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
{rule.confidence_level}
</span>
</div>
</div>
<SyntaxHighlighter
language="yaml"
style={tomorrow}
className="rounded-md"
customStyle={{ fontSize: '12px' }}
>
{rule.rule_content}
</SyntaxHighlighter>
</div>
))}
</div>
) : (
<p className="text-gray-500 bg-gray-50 p-4 rounded-md">No SIGMA rules generated for this CVE yet.</p>
)}
</div>
</div>
</div>
</div>
);
};
if (loading) {
return (
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div>
<p className="mt-4 text-gray-600">Loading...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-100">
<nav className="bg-white shadow">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<div className="flex-shrink-0 flex items-center">
<h1 className="text-xl font-bold text-gray-900">CVE-SIGMA Auto Generator</h1>
</div>
<div className="ml-6 flex space-x-8">
<button
onClick={() => setActiveTab('dashboard')}
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
activeTab === 'dashboard'
? 'border-blue-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
Dashboard
</button>
<button
onClick={() => setActiveTab('cves')}
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
activeTab === 'cves'
? 'border-blue-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
CVEs
</button>
<button
onClick={() => setActiveTab('rules')}
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
activeTab === 'rules'
? 'border-blue-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
SIGMA Rules
</button>
</div>
</div>
</div>
</div>
</nav>
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
{activeTab === 'dashboard' && <Dashboard />}
{activeTab === 'cves' && <CVEList />}
{activeTab === 'rules' && <SigmaRulesList />}
</div>
</main>
{selectedCve && (
<CVEDetail cve={selectedCve} onClose={() => setSelectedCve(null)} />
)}
</div>
);
}
export default App;

11
frontend/src/index.js Normal file
View file

@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './App.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

132
init.sql Normal file
View file

@ -0,0 +1,132 @@
-- Database initialization script
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- CVEs table
CREATE TABLE cves (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cve_id VARCHAR(20) UNIQUE NOT NULL,
description TEXT,
cvss_score DECIMAL(3,1),
severity VARCHAR(20),
published_date TIMESTAMP,
modified_date TIMESTAMP,
affected_products TEXT[],
references TEXT[],
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- SIGMA rules table
CREATE TABLE sigma_rules (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
cve_id VARCHAR(20) REFERENCES cves(cve_id),
rule_name VARCHAR(255) NOT NULL,
rule_content TEXT NOT NULL,
detection_type VARCHAR(50),
log_source VARCHAR(100),
confidence_level VARCHAR(20),
auto_generated BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Rule templates table
CREATE TABLE rule_templates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
template_name VARCHAR(255) NOT NULL,
template_content TEXT NOT NULL,
applicable_product_patterns TEXT[],
description TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Insert some basic rule templates
INSERT INTO rule_templates (template_name, template_content, applicable_product_patterns, description) VALUES
(
'Windows Process Execution',
'title: {title}
description: {description}
id: {rule_id}
status: experimental
author: CVE-SIGMA Auto Generator
date: {date}
references:
- {cve_url}
tags:
- attack.execution
- {cve_id}
logsource:
category: process_creation
product: windows
detection:
selection:
Image|contains: {suspicious_processes}
condition: selection
falsepositives:
- Legitimate use of the software
level: {level}',
ARRAY['windows', 'microsoft'],
'Template for Windows process execution detection'
),
(
'Network Connection',
'title: {title}
description: {description}
id: {rule_id}
status: experimental
author: CVE-SIGMA Auto Generator
date: {date}
references:
- {cve_url}
tags:
- attack.command_and_control
- {cve_id}
logsource:
category: network_connection
product: windows
detection:
selection:
Initiated: true
DestinationPort: {suspicious_ports}
condition: selection
falsepositives:
- Legitimate network connections
level: {level}',
ARRAY['network', 'connection', 'remote'],
'Template for network connection detection'
),
(
'File Modification',
'title: {title}
description: {description}
id: {rule_id}
status: experimental
author: CVE-SIGMA Auto Generator
date: {date}
references:
- {cve_url}
tags:
- attack.defense_evasion
- {cve_id}
logsource:
category: file_event
product: windows
detection:
selection:
EventType: creation
TargetFilename|contains: {file_patterns}
condition: selection
falsepositives:
- Legitimate file operations
level: {level}',
ARRAY['file', 'filesystem', 'modification'],
'Template for file modification detection'
);
-- Create indexes
CREATE INDEX idx_cves_cve_id ON cves(cve_id);
CREATE INDEX idx_cves_published_date ON cves(published_date);
CREATE INDEX idx_cves_severity ON cves(severity);
CREATE INDEX idx_sigma_rules_cve_id ON sigma_rules(cve_id);
CREATE INDEX idx_sigma_rules_detection_type ON sigma_rules(detection_type);