Automated Vulnerability Management & Incident Response Platform
Real-time SOC dashboard for triaging incidents, tracking CVEs, and orchestrating infrastructure scans — built for security teams who ship fast and break nothing.
- Overview
- Features
- Tech Stack
- Architecture
- Getting Started
- Environment Variables
- Scan Engine & Supported Tools
- Background Worker
- API Reference
- Authentication & Security
- Pre-Seeded Accounts
- Testing & Linting
- Deployment
- Project Structure
- Contributing
- License
PwnOps is a full-stack security operations platform that gives SOC teams a unified interface to:
- Detect — Run real vulnerability scans against live infrastructure using industry-standard tools.
- Triage — Manage incidents through a Kanban-style workflow with severity classification and assignment.
- Track — Monitor CVEs across your asset inventory with filtering, status tracking, and CVSS scoring.
- Respond — Receive instant notifications via Slack, Discord, PagerDuty, or custom webhooks when critical events occur.
It's designed to work out of the box with pre-installed scanning tools via Docker, while the web application can be deployed independently to Vercel or any Node.js host.
| Feature | Description |
|---|---|
| Dashboard | Real-time metrics — open incidents, running scans, critical vulnerabilities, and a live threat feed |
| Incident Management | Full CRUD with Kanban board, severity/status tracking, assignee management, and threaded comments |
| Vulnerability Tracking | CVE management with CVSS scoring, affected asset mapping, status workflow (open → in_progress → fixed) |
| Asset Inventory | Track endpoints, servers, networks, and cloud resources with tagging and status monitoring |
| Scan Orchestration | Launch, schedule (cron), and monitor security scans with terminal-style streaming output |
| Integrations | Webhook-based alerting for Slack, Discord, PagerDuty, and generic HTTP endpoints |
| Feature | Description |
|---|---|
| JWT Authentication | Secure HttpOnly, SameSite=Lax cookie-based sessions with 24h expiry |
| Multi-Factor Auth (2FA) | TOTP-based two-factor authentication with QR code provisioning |
| Role-Based Access Control | Three roles — admin, analyst, viewer — enforced at the edge and API layer |
| OAuth Providers | GitHub and Google SSO integration |
| API Keys | Bearer token authentication for programmatic access with SHA-256 hashed storage |
| Edge Security | Rate limiting (Upstash Redis + in-memory fallback), CSRF protection, security headers (HSTS, CSP, X-Frame-Options, Permissions-Policy) |
| DNS Rebinding Protection | FQDN targets are resolved and validated against blocked IP ranges before scan execution |
| Feature | Description |
|---|---|
| Cyber-Themed UI | Dark mode with glassmorphism, neon accents, and scan-line animations |
| Keyboard Shortcuts | Global hotkeys for power users |
| Responsive Layout | Mobile-friendly sidebar with collapsible navigation |
| SEO Optimized | OpenGraph, Twitter cards, robots.txt, dynamic sitemap |
| Layer | Technology |
|---|---|
| Framework | Next.js 16 — App Router, Server Components, Edge Proxy |
| Language | TypeScript 5 |
| Styling | Tailwind CSS 4, custom CSS variables |
| Database | PostgreSQL 15 via Prisma ORM 7.8 (with @prisma/adapter-pg) |
| Auth | jose (JWT), bcryptjs (hashing), otplib + qrcode (2FA) |
| Rate Limiting | Upstash Redis + @upstash/ratelimit (sliding window) |
| Background Jobs | Standalone Node.js worker via tsx, cron-parser, ws |
| Resend API | |
| Containerization | Docker & Docker Compose |
| Reverse Proxy | Caddy (automatic HTTPS) |
| CI/CD | GitHub Actions |
| E2E Testing | Playwright |
| Analytics | Vercel Analytics |
┌─────────────────────────────────────────────────────────────────────┐
│ CLIENTS │
│ (Browser / API Consumers) │
└──────────────────────────┬──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ EDGE PROXY (proxy.ts) │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────────┐ │
│ │ JWT Verify │ │ Rate Limiter │ │ Security Headers + CSRF │ │
│ └──────────────┘ └──────────────┘ └────────────────────────────┘ │
└──────────────────────────┬───────────────────────────────────────────┘
│
┌────────────┴────────────┐
▼ ▼
┌──────────────────────┐ ┌──────────────────────────────────────────┐
│ NEXT.JS APP │ │ REST API (/api/*) │
│ ┌────────────────┐ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Dashboard │ │ │ │ Auth │ │ Scans │ │ Incidents│ │
│ │ Incidents │ │ │ │ Users │ │ Vulns │ │ Assets │ │
│ │ Scans │ │ │ │ Settings │ │ Integr. │ │ Metrics │ │
│ │ Vulnerabilities│ │ │ └──────────┘ └──────────┘ └──────────┘ │
│ │ Assets │ │ └──────────────────┬───────────────────────┘
│ │ Integrations │ │ │
│ │ Users │ │ │
│ └────────────────┘ │ │
└──────────────────────┘ │
▼
┌──────────────────────────┐
│ PostgreSQL (Prisma) │
│ 12 Models │
└─────────────┬────────────┘
│
▼
┌──────────────────────────┐
│ BACKGROUND WORKER │
│ ┌────────────────────┐ │
│ │ Poll for queued │ │
│ │ scans → Execute │ │
│ │ tools → Stream │ │
│ │ results via WS │ │
│ │ → Webhook alerts │ │
│ └────────────────────┘ │
└──────────────────────────┘
- Node.js ≥ 20
- Docker & Docker Compose (for database and scan worker)
- npm ≥ 9
git clone https://github.com/0xshellghost/pwnops.git
cd pwnops
npm installcp .env.example .env.localEdit .env.local with your settings. At minimum, set:
# Required
JWT_SECRET="$(openssl rand -base64 32)"
DATABASE_URL="postgresql://postgres:password123@localhost:5432/pwnops"
# Required for seeding
SEED_ADMIN_PASSWORD="YourStrongAdminPass!"
SEED_ANALYST_PASSWORD="YourStrongAnalystPass!"
SEED_VIEWER_PASSWORD="YourStrongViewerPass!"Spin up PostgreSQL and the background scan worker (pre-installed with all security tools):
docker compose up -dnpx prisma generate
npx prisma db pushNavigate to /api/seed in your browser or call it via curl after starting the dev server. This creates the default organization and test accounts.
npm run devOpen http://localhost:3000 and log in with one of the pre-seeded accounts.
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET |
✅ | — | 256-bit secret for signing JWTs. Generate with openssl rand -base64 32 |
DATABASE_URL |
✅ | — | PostgreSQL connection string (Supabase pooler or local Docker) |
NODE_ENV |
❌ | development |
Set to production to enable HSTS and Secure cookies |
UPSTASH_REDIS_REST_URL |
❌ | — | Upstash Redis URL for distributed rate limiting |
UPSTASH_REDIS_REST_TOKEN |
❌ | — | Upstash Redis token |
GITHUB_CLIENT_ID |
❌ | — | GitHub OAuth App client ID |
GITHUB_CLIENT_SECRET |
❌ | — | GitHub OAuth App client secret |
GOOGLE_CLIENT_ID |
❌ | — | Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
❌ | — | Google OAuth client secret |
DISCORD_WEBHOOK_URL |
❌ | — | Discord webhook for scan completion alerts |
RENDER_WORKER_URL |
❌ | — | URL of the deployed scan worker (for tool availability checks) |
WORKER_API_KEY |
❌ | — | Shared secret for authenticating worker ↔ API requests |
WORKER_POLL_INTERVAL |
❌ | 3000 |
Worker DB poll interval in milliseconds |
WORKER_MAX_CONCURRENT |
❌ | 3 |
Max concurrent scan executions per worker |
BLOCK_PRIVATE_SCANS |
❌ | false |
Block scans targeting RFC 1918 private IP ranges |
ALLOWED_REGISTRATION_DOMAINS |
❌ | — | Comma-separated allowlist of email domains for registration |
SEED_ADMIN_PASSWORD |
✅¹ | — | Password for the seeded admin account |
SEED_ANALYST_PASSWORD |
✅¹ | — | Password for the seeded analyst account |
SEED_VIEWER_PASSWORD |
✅¹ | — | Password for the seeded viewer account |
¹ Required only when seeding the database for the first time.
PwnOps includes a hardened scan engine (lib/scan-engine.ts) that executes real security tools with strict safety controls:
- Input validation — Targets must match IPv4, CIDR (
/16–/32), or FQDN patterns - No shell interpolation — Uses
execFile(notexec) to prevent command injection - DNS rebinding protection — FQDNs are resolved and checked against blocked ranges
- Execution timeouts — Each tool has a per-scan time limit
- Whitelisted binaries — Only registered tools can be executed
| Tool | Key | Description | Timeout |
|---|---|---|---|
| Nmap | nmap |
TCP SYN scan with service/version detection | 5 min |
| Nmap Recon | nmap-recon |
Host discovery scan across a subnet | 2 min |
| testssl.sh | testssl |
Comprehensive SSL/TLS configuration audit | 10 min |
| Lynis | lynis |
System hardening and configuration audit | 5 min |
| Subfinder | subfinder |
Fast passive subdomain enumeration | 5 min |
| Nuclei | nuclei |
Template-based vulnerability scanner (critical/high/medium) | 15 min |
| WhatWeb | whatweb |
Technology stack detection and fingerprinting | 2 min |
All tools come pre-installed in the Docker worker image (Dockerfile.worker). For local development without Docker, install them on your host:
# Debian/Ubuntu
sudo apt install nmap testssl.sh lynis whatweb
# Subfinder & Nuclei (Go binaries)
go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latestThe background worker (worker/scan-worker.ts) runs as a standalone Node.js process, decoupled from the Next.js runtime:
┌──────────────┐ poll ┌──────────────┐ execute ┌──────────────┐
│ PostgreSQL │◄──────────────│ Worker │──────────────►│ Scan Tools │
│ (scan queue)│───────────────│ (scan-worker │◄──────────────│ (nmap, etc) │
└──────────────┘ update │ .ts) │ stdout └──────────────┘
└──────┬───────┘
│
┌───────────┴───────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ WebSocket │ │ Webhooks │
│ (live UI) │ │ (Slack/etc) │
└──────────────┘ └──────────────┘
How it works:
- Worker polls the database every
WORKER_POLL_INTERVALms for scans withstatus = 'queued' - Claims a scan by atomically setting
status = 'running' - Executes the tool binary via
execFilewith validated arguments - Streams progress updates to connected clients via WebSocket
- Writes final results to the database and dispatches webhook notifications
- Checks for due
ScheduledScanentries and creates new queued scans from cron schedules
Running the worker:
# Via npm script
npm run worker:scan
# Or directly
npx tsx worker/scan-worker.tsProduction: Use Docker Compose — the worker container comes with all scanning tools pre-installed and sandboxed.
All protected routes require a valid JWT cookie or Authorization: Bearer <api-key> header.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/login |
Authenticate and receive JWT cookie |
POST |
/api/auth/register |
Create new user account |
GET |
/api/auth/me |
Get current authenticated user |
POST |
/api/auth/forgot-password |
Request a password reset email |
POST |
/api/auth/reset-password |
Reset password with token |
POST |
/api/auth/2fa/setup |
Generate 2FA secret and QR code |
POST |
/api/auth/2fa/verify |
Verify OTP and complete login |
GET |
/api/auth/github |
Initiate GitHub OAuth flow |
GET |
/api/auth/google |
Initiate Google OAuth flow |
| Method | Endpoint | Description |
|---|---|---|
GET/POST |
/api/incidents |
List or create incidents |
GET/PUT/DELETE |
/api/incidents/[id] |
Get, update, or delete a specific incident |
GET/POST |
/api/scans |
List or trigger scans |
GET |
/api/scans/tools |
List available scanning tools |
POST |
/api/scans/schedule |
Create a scheduled (cron) scan |
GET |
/api/scans/ws-key |
Obtain a WebSocket authentication key |
GET |
/api/vulnerabilities |
List vulnerabilities with filtering |
GET/POST |
/api/assets |
List or register assets |
GET/PUT/DELETE |
/api/assets/[id] |
Manage a specific asset |
GET/POST |
/api/integrations |
List or create webhook integrations |
GET/PUT/DELETE |
/api/integrations/[id] |
Manage a specific integration |
GET |
/api/users |
List users in the organization |
GET |
/api/dashboard/metrics |
Aggregated dashboard statistics |
GET/POST |
/api/settings/apikeys |
Manage API keys |
POST |
/api/seed |
Seed the database (first run only) |
GET |
/api/health |
Health check endpoint |
┌──────────┐ POST /api/auth/login ┌──────────────┐
│ Client │─────────────────────────►│ Auth API │
│ │◄─────────────────────────│ │
│ │ Set-Cookie: JWT │ ┌────────┐ │
│ │ (HttpOnly, SameSite) │ │bcryptjs│ │
└──────────┘ │ │ hash │ │
│ │ │ verify │ │
│ (2FA enabled?) │ └────────┘ │
│ └──────────────┘
│ POST /api/auth/2fa/verify
│──────────────────────────────► Verify TOTP
│◄────────────────────────────── Full JWT issued
| Layer | Implementation |
|---|---|
| Edge Proxy | proxy.ts — validates JWT, applies rate limits, sets security headers on every request |
| Rate Limiting | Sliding window via Upstash Redis (10 req/min for auth, 120 req/min for API). Falls back to in-memory if Redis is unavailable |
| CSRF Protection | Origin header validation on all POST/PUT/DELETE requests |
| Security Headers | X-Content-Type-Options, X-Frame-Options: DENY, X-XSS-Protection, Referrer-Policy, Content-Security-Policy, Permissions-Policy |
| HSTS | Enabled in production with max-age=63072000; includeSubDomains; preload |
| Password Storage | bcryptjs with cost factor 12 |
| API Key Storage | SHA-256 hashed — raw keys are never stored |
The database seeder creates these accounts for development and testing. Override passwords via environment variables.
| Role | Default Password | Permissions | |
|---|---|---|---|
| Admin | [email protected] |
PwnOps!Admin#2026 |
Full access — user management, all CRUD, scans |
| Analyst | [email protected] |
PwnOps!Analyst#2026 |
Incident & scan management, vulnerability tracking |
| Viewer | [email protected] |
PwnOps!Viewer#2026 |
Read-only access to all dashboards |
⚠️ Warning: Change these passwords immediately after first login in any non-local environment.
npm run lintPwnOps uses Playwright for E2E testing against a real browser:
# Install browser binaries (first time)
npx playwright install --with-deps chromium
# Run tests
npx playwright test
# View HTML report
npx playwright show-reportThe GitHub Actions workflow (.github/workflows/ci.yml) runs on every push and PR:
- Lint — ESLint with Next.js rules
- Build — Full production build
- Test — Playwright E2E suite against a PostgreSQL service container
The Next.js frontend deploys to Vercel with zero configuration. Set your environment variables in the Vercel dashboard and connect your GitHub repo.
Note: The scan worker must be deployed separately — it requires system-level security tools that aren't available in serverless environments.
docker compose up -d # PostgreSQL + scan worker
npm run dev # Next.js dev server# Build and start the worker + Caddy reverse proxy (automatic HTTPS)
docker compose -f docker-compose.prod.yml up -d --build
# Build and start the Next.js app
npm run build
npm startEnsure NODE_ENV=production is set to activate:
Securecookie flag- HSTS headers
- Strict security headers
- Generate a strong
JWT_SECRET(openssl rand -base64 32) - Use a managed PostgreSQL instance (e.g., Supabase, Neon, RDS)
- Configure Upstash Redis for distributed rate limiting
- Set
BLOCK_PRIVATE_SCANS=trueto prevent internal network scanning - Change all default seed passwords
- Set
ALLOWED_REGISTRATION_DOMAINSto restrict sign-ups - Configure webhook integrations for scan/incident alerts
- Enable 2FA for all admin accounts
pwnops/
├── app/ # Next.js 16 App Router
│ ├── api/ # REST API endpoints
│ │ ├── auth/ # Login, register, 2FA, OAuth, password reset
│ │ ├── scans/ # Scan CRUD, scheduling, tool listing, WS keys
│ │ ├── incidents/ # Incident CRUD with comments
│ │ ├── vulnerabilities/ # CVE tracking and status management
│ │ ├── assets/ # Asset inventory CRUD
│ │ ├── integrations/ # Webhook integration management
│ │ ├── users/ # User listing and role management
│ │ ├── settings/apikeys/ # API key generation and management
│ │ ├── dashboard/metrics/ # Aggregated dashboard statistics
│ │ ├── seed/ # Database seeder endpoint
│ │ ├── health/ # Health check
│ │ └── simulate-threat/ # Threat simulation for testing
│ ├── dashboard/ # Protected dashboard pages
│ │ ├── scans/ # Scan list, detail view, report
│ │ ├── incidents/ # Incident list with detail modal
│ │ ├── vulnerabilities/ # Vulnerability table
│ │ ├── assets/ # Asset inventory
│ │ ├── integrations/ # Integration management
│ │ └── users/ # User management (admin)
│ ├── login/ # Login page
│ ├── register/ # Registration page
│ ├── forgot-password/ # Password recovery
│ ├── reset-password/ # Password reset (with token)
│ ├── docs/ # Documentation page
│ ├── api-docs/ # API documentation page
│ ├── status/ # System status page
│ └── support/ # Support/contact page
├── components/ # Shared React components
│ ├── AuthProvider.tsx # Client-side auth context
│ └── KeyboardShortcuts.tsx # Global keyboard shortcuts
├── hooks/ # Custom React hooks
│ └── useEscapeKey.ts # ESC key handler
├── lib/ # Core utilities
│ ├── auth.ts # JWT signing/verification, cookie handling
│ ├── scan-engine.ts # Tool registry, input validation, execution
│ ├── store.ts # Prisma client, CRUD operations, seeding
│ ├── types.ts # TypeScript type definitions
│ └── webhooks.ts # Webhook dispatch (Slack/Discord/PagerDuty)
├── prisma/
│ └── schema.prisma # Database schema (12 models)
├── worker/
│ └── scan-worker.ts # Background scan execution worker
├── tests/
│ └── auth.spec.ts # Playwright E2E tests
├── proxy.ts # Edge proxy (auth, rate limiting, headers)
├── Dockerfile.worker # Worker container with security tools
├── docker-compose.yml # Dev: PostgreSQL + worker
├── docker-compose.prod.yml # Prod: Worker + Caddy (auto-HTTPS)
├── Caddyfile # Caddy reverse proxy config
└── .github/workflows/ci.yml # CI pipeline (lint → build → test)
Contributions are welcome! To get started:
- Fork the repository
- Create a feature branch —
git checkout -b feat/my-feature - Commit your changes —
git commit -m "feat: add my feature" - Push —
git push origin feat/my-feature - Open a Pull Request
Please ensure your code passes npm run lint and all Playwright tests before submitting.
This project is licensed under the MIT License.
Built with 🖤 by 0xshellghost