Skip to main content Skills Marketplace Découvrez et explorez les compétences IA créées par la communauté.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Copier le promptAfficher les détails du prompt Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
npx skills add https://github.com/Aradotso/security-skills --skill pentestops-dashboardLa commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Télécharger Zip Téléchargement... Métiers associés SOC
Basé sur la classification professionnelle SOC
name pentestops-dashboard description Comprehensive penetration testing operations dashboard for managing projects, tasks, findings, clients, and assets with Next.js and MongoDB triggers ["set up pentestops dashboard","create pentest project management system","configure pentestops with docker","add security findings to pentestops","manage penetration testing tasks","deploy pentestops dashboard","integrate cwe database with pentestops","create pentest checklist pages"]
PentestOPS Dashboard Skill
Skill by ara.so — Security Skills collection.
Overview
PentestOPS Dashboard is a comprehensive penetration testing operations platform built with Next.js, Express, and MongoDB. It provides project management, task tracking (Kanban/table views), finding management with CWE integration, client management, asset tracking, rich text pages with Editor.js, checklists, comments, file attachments, version history, and global search.
Key Features:
Full-stack TypeScript application with JWT authentication
Rich text editor with Notion-like features
Single Docker container deployment (includes MongoDB, backend, frontend)
CWE database integration for security findings
File upload support (PDF, DOCX, XLSX, ZIP, images)
Threaded comments and version control
Installation
Local Development Setup
git clone https://github.com/0xBugatti/PentestOPS.git
cd PentestOPS
npm install
frontend && npm install && ..
backend && npm install && ..
> . <<
NODE_ENV=development
BACKEND_PORT=4000
MONGODB_URI=mongodb://localhost:27017/pentest-dashboard
JWT_SECRET=$(openssl rand - 32)
JWT_REFRESH_SECRET=$(openssl rand - 32)
CORS_ORIGIN=http://localhost:3000
ALLOW_REGISTRATION=
MAX_FILE_SIZE=10485760
UPLOAD_DIR=./backend/uploads
NEXT_PUBLIC_API_URL=http://localhost:4000
EOF
docker run -d --name mongodb -p 27017:27017 mongo:latest
npm run dev
cd
cd
cd
cd
cat
env
'EOF'
base64
base64
true
Frontend: http://localhost:3000
Backend API: http://localhost:4000
Docker Deployment (Production)
docker build -t pentestops-dashboard:latest .
docker run -d \
--name pentestops \
--restart unless-stopped \
-p 3000:3000 \
-p 4000:4000 \
-v pentestops-data:/data/db \
-v pentestops-uploads:/app/uploads \
-e JWT_SECRET=${JWT_SECRET} \
-e JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET} \
-e NODE_ENV=production \
-e CORS_ORIGIN=https://yourdomain.com \
-e ALLOW_REGISTRATION=false \
pentestops-dashboard:latest
Core API Endpoints
Authentication
POST /api/auth/register
{
"username" : "pentester" ,
"email" : "pentester@example.com" ,
"password" : "SecurePass123!" ,
"firstName" : "John" ,
"lastName" : "Doe"
}
POST /api/auth/login
{
"username" : "pentester" ,
"password" : "SecurePass123!"
}
POST /api/auth/refresh
{
"refreshToken" : "your-refresh-token"
}
GET /api/auth/profile
Headers : Authorization : Bearer {accessToken}
Projects
POST /api/projects
{
"name" : "Web Application Security Assessment" ,
"description" : "Comprehensive security audit of client web application" ,
"status" : "in-progress" ,
"startDate" : "2024-01-15T00:00:00Z" ,
"endDate" : "2024-02-15T00:00:00Z" ,
"client" : "client-id" ,
"tags" : ["webapp" , "owasp" , "critical" ]
}
GET /api/projects?status=in -progress&search=webapp&sort=createdAt
GET /api/projects/{projectId}
PUT /api/projects/{projectId}
{
"status" : "completed" ,
"progress" : 100
}
DELETE /api/projects/{projectId}
Tasks
POST /api/tasks
{
"title" : "SQL Injection Testing" ,
"description" : "Test all input fields for SQL injection vulnerabilities" ,
"status" : "todo" ,
"priority" : "high" ,
"project" : "project-id" ,
"assignee" : "user-id" ,
"dueDate" : "2024-01-20T00:00:00Z" ,
"tags" : ["sqli" , "webapp" , "owasp-a03" ],
"checklist" : ["Check login form" , "Test search parameters" , "Verify API endpoints" ]
}
GET /api/tasks?project={projectId}&status=in -progress&priority=high
PUT /api/tasks/{taskId}
{
"status" : "in-progress" ,
"progress" : 50
}
POST /api/tasks/{taskId}/subtasks
{
"title" : "Test login form SQL injection" ,
"completed" : false
}
POST /api/tasks/{taskId}/comments
{
"content" : "Found SQL injection in username parameter" ,
"parentComment" : "parent-comment-id"
}
Findings
POST /api/findings
{
"title" : "SQL Injection in Login Form" ,
"description" : "The login form is vulnerable to SQL injection attacks" ,
"severity" : "critical" ,
"status" : "open" ,
"cweId" : "CWE-89" ,
"cvssScore" : 9.8 ,
"cvssVector" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" ,
"project" : "project-id" ,
"affectedAssets" : ["asset-id" ],
"steps" : [
"Navigate to login page" ,
"Enter ' OR '1'='1 in username field" ,
"Observe authentication bypass"
],
"impact" : "Attackers can bypass authentication and gain unauthorized access" ,
"recommendation" : "Use parameterized queries or prepared statements" ,
"references" : ["https://owasp.org/www-community/attacks/SQL_Injection" ],
"tags" : ["sqli" , "authentication" , "critical" ]
}
GET /api/findings?project={projectId}&severity=critical&status=open
PUT /api/findings/{findingId}
{
"status" : "fixed" ,
"remediation" : "Implemented parameterized queries" ,
"retestDate" : "2024-01-25T00:00:00Z"
}
CWE Database
POST /api/cwes/import
Content -Type : multipart/form-data
file : cwes.csv
GET /api/cwes?search=injection&type =vulnerability
GET /api/cwes/89
Clients
POST /api/clients
{
"name" : "Acme Corporation" ,
"email" : "security@acme.com" ,
"phone" : "+1-555-0123" ,
"website" : "https://acme.com" ,
"industry" : "Technology" ,
"contacts" : [
{
"name" : "Jane Smith" ,
"role" : "CISO" ,
"email" : "jane.smith@acme.com" ,
"phone" : "+1-555-0124"
}
],
"notes" : "Primary contact for all security assessments"
}
GET /api/clients?search=acme
PUT /api/clients/{clientId}
Pages (Checklists/Documentation)
POST /api/pages
{
"title" : "OWASP Top 10 Testing Checklist" ,
"slug" : "owasp-top-10-checklist" ,
"content" : {
"time" : 1640995200000 ,
"blocks" : [
{
"type" : "header" ,
"data" : {
"text" : "OWASP Top 10 Testing Checklist" ,
"level" : 1
}
},
{
"type" : "paragraph" ,
"data" : {
"text" : "Comprehensive checklist for testing OWASP Top 10 vulnerabilities"
}
},
{
"type" : "checklist" ,
"data" : {
"items" : [
{
"text" : "A01:2021 - Broken Access Control" ,
"checked" : false
},
{
"text" : "A02:2021 - Cryptographic Failures" ,
"checked" : false
},
{
"text" : "A03:2021 - Injection" ,
"checked" : true
}
]
}
},
{
"type" : "code" ,
"data" : {
"code" : "' OR '1'='1' --" ,
"language" : "sql"
}
}
],
"version" : "2.28.0"
},
"isPublic" : false ,
"tags" : ["owasp" , "checklist" , "webapp" ]
}
GET /api/pages/owasp-top-10 -checklist
PUT /api/pages/owasp-top-10 -checklist
{
"content" : { }
}
PUT /api/tasks/{taskId}
{
"linkedPages" : ["owasp-top-10-checklist" ]
}
File Attachments
POST /api/attachments
Content -Type : multipart/form-data
file : screenshot.png
entityType : finding
entityId : finding-id
GET /api/attachments/{attachmentId}/download
GET /api/attachments/{attachmentId}/view
GET /api/attachments?entityType=finding&entityId={findingId}
Assets
POST /api/assets
{
"name" : "Web Server - Production" ,
"type" : "server" ,
"ipAddress" : "192.168.1.100" ,
"hostname" : "web-prod-01.acme.com" ,
"os" : "Ubuntu 22.04 LTS" ,
"ports" : [
{
"port" : 443 ,
"protocol" : "tcp" ,
"service" : "https" ,
"version" : "nginx/1.18.0"
}
],
"vulnerabilities" : ["CVE-2023-1234" ],
"project" : "project-id" ,
"notes" : "Primary web server for production environment"
}
GET /api/assets?project={projectId}&type =server
PUT /api/findings/{findingId}
{
"affectedAssets" : ["asset-id" ]
}
Global Search
GET /api/search?q=sql+injection&type =finding,task&project={projectId}
Frontend Integration
API Client Setup
import axios from 'axios' ;
const api = axios.create ({
baseURL : process.env .NEXT_PUBLIC_API_URL ,
headers : {
'Content-Type' : 'application/json'
}
});
api.interceptors .request .use ((config ) => {
const token = localStorage .getItem ('accessToken' );
if (token) {
config.headers .Authorization = `Bearer ${token} ` ;
}
return config;
});
api.interceptors .response .use (
(response ) => response,
async (error) => {
const originalRequest = error.config ;
if (error.response ?.status === 401 && !originalRequest._retry ) {
originalRequest._retry = true ;
const refreshToken = localStorage .getItem ('refreshToken' );
if (refreshToken) {
try {
const { data } = await axios.post (
`${process.env.NEXT_PUBLIC_API_URL} /api/auth/refresh` ,
{ refreshToken }
);
localStorage .setItem ('accessToken' , data.accessToken );
originalRequest.headers .Authorization = `Bearer ${data.accessToken} ` ;
return api (originalRequest);
} catch (err) {
localStorage .removeItem ('accessToken' );
localStorage .removeItem ('refreshToken' );
window .location .href = '/login' ;
}
}
}
return Promise .reject (error);
}
);
export default api;
Creating a Project with Tasks
'use client' ;
import { useState } from 'react' ;
import api from '@/lib/api' ;
import { useRouter } from 'next/navigation' ;
export default function CreateProject ( ) {
const router = useRouter ();
const [loading, setLoading] = useState (false );
const handleSubmit = async (e : React .FormEvent <HTMLFormElement > ) => {
e.preventDefault ();
setLoading (true );
const formData = new FormData (e.currentTarget );
try {
const { data : project } = await api.post ('/api/projects' , {
name : formData.get ('name' ),
description : formData.get ('description' ),
status : 'planning' ,
startDate : formData.get ('startDate' ),
endDate : formData.get ('endDate' ),
client : formData.get ('clientId' ),
tags : formData.get ('tags' )?.toString ().split (',' ).map (t => t.trim ())
});
const initialTasks = [
{ title : 'Reconnaissance' , status : 'todo' , priority : 'high' },
{ title : 'Vulnerability Scanning' , status : 'todo' , priority : 'high' },
{ title : 'Manual Testing' , status : 'todo' , priority : 'medium' },
{ title : 'Report Writing' , status : 'todo' , priority : 'low' }
];
await Promise .all (
initialTasks.map (task =>
api.post ('/api/tasks' , {
...task,
project : project._id
})
)
);
router.push (`/projects/${project._id} ` );
} catch (error) {
console .error ('Failed to create project:' , error);
} finally {
setLoading (false );
}
};
return (
<form onSubmit ={handleSubmit} >
{/* Form fields */}
</form >
);
}
Creating Findings with CWE Lookup
'use client' ;
import { useState, useEffect } from 'react' ;
import api from '@/lib/api' ;
interface CWE {
id : string ;
name : string ;
description : string ;
}
export default function FindingForm ({ projectId }: { projectId: string } ) {
const [cwes, setCwes] = useState<CWE []>([]);
const [selectedCwe, setSelectedCwe] = useState<string >('' );
useEffect (() => {
const searchCWEs = async (query : string ) => {
if (query.length < 2 ) return ;
const { data } = await api.get (`/api/cwes?search=${query} ` );
setCwes (data.cwes );
};
const debounce = setTimeout (() => searchCWEs (selectedCwe), 300 );
return () => clearTimeout (debounce);
}, [selectedCwe]);
const handleSubmit = async (formData : FormData ) => {
const finding = {
title : formData.get ('title' ),
description : formData.get ('description' ),
severity : formData.get ('severity' ),
status : 'open' ,
cweId : selectedCwe,
cvssScore : parseFloat (formData.get ('cvssScore' ) as string ),
cvssVector : formData.get ('cvssVector' ),
project : projectId,
steps : formData.get ('steps' )?.toString ().split ('\n' ),
impact : formData.get ('impact' ),
recommendation : formData.get ('recommendation' ),
references : formData.get ('references' )?.toString ().split ('\n' )
};
try {
const { data } = await api.post ('/api/findings' , finding);
const files = formData.getAll ('screenshots' ) as File [];
for (const file of files) {
const uploadData = new FormData ();
uploadData.append ('file' , file);
uploadData.append ('entityType' , 'finding' );
uploadData.append ('entityId' , data._id );
await api.post ('/api/attachments' , uploadData, {
headers : { 'Content-Type' : 'multipart/form-data' }
});
}
return data;
} catch (error) {
console .error ('Failed to create finding:' , error);
throw error;
}
};
return (
<div >
<input
type ="text"
placeholder ="Search CWE (e.g., SQL Injection)"
value ={selectedCwe}
onChange ={(e) => setSelectedCwe(e.target.value)}
/>
{cwes.length > 0 && (
<ul >
{cwes.map(cwe => (
<li key ={cwe.id} onClick ={() => setSelectedCwe(cwe.id)}>
{cwe.id}: {cwe.name}
</li >
))}
</ul >
)}
{/* Rest of form */}
</div >
);
}
Backend Development
Creating Custom Middleware
import rateLimit from 'express-rate-limit' ;
export const apiLimiter = rateLimit ({
windowMs : 15 * 60 * 1000 ,
max : 100 ,
message : 'Too many requests from this IP, please try again later'
});
export const authLimiter = rateLimit ({
windowMs : 15 * 60 * 1000 ,
max : 5 ,
skipSuccessfulRequests : true
});
app.use ('/api/' , apiLimiter);
app.use ('/api/auth/login' , authLimiter);
Custom Finding Model Extension
import mongoose, { Schema , Document } from 'mongoose' ;
export interface IFinding extends Document {
title : string ;
severity : 'critical' | 'high' | 'medium' | 'low' | 'info' ;
cweId : string ;
cvssScore : number ;
project : mongoose.Types .ObjectId ;
calculateRiskScore (): number ;
generateReport (): string ;
}
const FindingSchema = new Schema <IFinding >({
title : { type : String , required : true },
description : { type : String , required : true },
severity : {
type : String ,
enum : ['critical' , 'high' , 'medium' , 'low' , 'info' ],
required : true
},
status : {
type : String ,
enum : ['open' , 'fixed' , 'accepted' , 'false-positive' ],
default : 'open'
},
cweId : { type : String },
cvssScore : { type : Number , min : 0 , max : 10 },
cvssVector : { type : String },
project : { type : Schema .Types .ObjectId , ref : 'Project' , required : true },
affectedAssets : [{ type : Schema .Types .ObjectId , ref : 'Asset' }],
steps : [{ type : String }],
impact : { type : String },
recommendation : { type : String },
references : [{ type : String }],
tags : [{ type : String }]
}, {
timestamps : true
});
FindingSchema .methods .calculateRiskScore = function ( ): number {
const severityScores = {
critical : 10 ,
high : 7.5 ,
medium : 5 ,
low : 2.5 ,
info : 0
};
const baseScore = severityScores[this .severity ];
const assetMultiplier = this .affectedAssets .length > 0 ? 1.2 : 1 ;
return Math .min (baseScore * assetMultiplier, 10 );
};
FindingSchema .methods .generateReport = function ( ): string {
return `
# ${this .title}
**Severity:** ${this .severity.toUpperCase()}
**CVSS Score:** ${this .cvssScore || 'N/A' }
**CWE:** ${this .cweId || 'N/A' }
**Status:** ${this .status}
## Description
${this .description}
## Steps to Reproduce
${this .steps.map((step, i) => `${i + 1 } . ${step} ` ).join('\n' )}
## Impact
${this .impact}
## Recommendation
${this .recommendation}
## References
${this .references.map(ref => `- ${ref} ` ).join('\n' )}
` .trim ();
};
export default mongoose.model <IFinding >('Finding' , FindingSchema );
Bulk Import Findings from Scanner Output
import express from 'express' ;
import Finding from '../models/Finding' ;
import multer from 'multer' ;
import xml2js from 'xml2js' ;
const router = express.Router ();
const upload = multer ({ dest : '/tmp/uploads' });
router.post ('/import/nmap' , upload.single ('file' ), async (req, res) => {
try {
const xmlData = await fs.promises .readFile (req.file !.path , 'utf-8' );
const parser = new xml2js.Parser ();
const result = await parser.parseStringPromise (xmlData);
const findings = [];
for (const host of result.nmaprun .host || []) {
const ip = host.address [0 ].$ .addr ;
for (const port of host.ports ?.[0 ]?.port || []) {
if (port.state [0 ].$ .state === 'open' ) {
const service = port.service ?.[0 ];
findings.push ({
title : `Open Port ${port.$.portid} /${port.$.protocol} ` ,
description : `Service: ${service?.$.name || 'unknown' } (${service?.$.product || 'N/A' } )` ,
severity : 'info' ,
status : 'open' ,
project : req.body .projectId ,
affectedAssets : [ip],
tags : ['port-scan' , 'nmap' ]
});
}
}
}
const created = await Finding .insertMany (findings);
await fs.promises .unlink (req.file !.path );
res.json ({ imported : created.length , findings : created });
} catch (error) {
res.status (500 ).json ({ error : 'Failed to import Nmap results' });
}
});
export default router;
Configuration
Environment Variables Reference
NODE_ENV=production
BACKEND_PORT=4000
MONGODB_URI=mongodb://localhost:27017/pentest-dashboard
JWT_SECRET=${JWT_SECRET}
JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}
CORS_ORIGIN=https://yourdomain.com
ALLOW_REGISTRATION=false
MAX_FILE_SIZE=10485760
UPLOAD_DIR=/app/uploads
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
Nginx Reverse Proxy Configuration # /etc/nginx/sites-available/pentestops
upstream backend {
server localhost:4000;
}
upstream frontend {
server localhost:3000;
}
server {
listen 443 ssl http2;
server_name pentestops.example.com;
ssl_certificate /etc/letsencrypt/live/pentestops.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pentestops.example.com/privkey.pem;
# Frontend
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Backend API
location /api {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 10M;
}
# WebSocket support (if needed)
location /socket.io {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
MongoDB Backup Script #!/bin/bash
BACKUP_DIR="/opt/backups/pentestops"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
mkdir -p $BACKUP_DIR
docker exec pentestops mongodump \
--archive=/tmp/backup_${DATE} .archive \
--db=pentest-dashboard \
--gzip
docker cp pentestops:/tmp/backup_${DATE} .archive \
$BACKUP_DIR /mongodb_${DATE} .archive
tar -czf $BACKUP_DIR /uploads_${DATE} .tar.gz \
/opt/pentestops/uploads
find $BACKUP_DIR -type f -mtime +$RETENTION_DAYS -delete
echo "Backup completed: $DATE "
Common Patterns
Automated Pentest Workflow
import api from '../lib/api' ;
async function runAutomatedPentest (projectId : string ) {
const reconTasks = [
{ title : 'DNS Enumeration' , command : 'dnsenum domain.com' },
{ title : 'Subdomain Discovery' , command : 'subfinder -d domain.com' },
{ title : 'Port Scanning' , command : 'nmap -sV -A target.com' }
];
for (const task of reconTasks) {
await api.post ('/api/tasks' , {
title : task.title ,
description : task.command ,
project : projectId,
status : 'todo' ,
tags : ['automated' , 'recon' ]
});
}
const scanResults = await runNucleiScan ('https://target.com' );
for (const result of scanResults) {
await api.post ('/api/findings' , {
title : result.info .name ,
description : result.info .description ,
severity : mapSeverity (result.info .severity ),
status : 'open' ,
project : projectId,
cweId : result.info .classification ?.['cwe-id' ]?.[0 ],
tags : result.info .tags ,
references : result.info .reference
});
}
const findings = await api.get (`/api/findings?project=${projectId} ` );
const report = generateExecutiveSummary (findings.data );
await api.post ('/api/pages' , {
title : `Automated Scan Report - ${new Date ().toLocaleDateString()} ` ,
slug : `scan-report-${Date .now()} ` ,
content : report,
project : projectId
});
}
function mapSeverity (nucleiSeverity : string ): string {
const mapping : Record <string , string > = {
'critical' : 'critical' ,
'high' : 'high' ,
'medium' : 'medium' ,
'low' : 'low' ,
'info' : 'info'
};
return mapping[nucleiSeverity] || 'info' ;
}
Exporting Pentest Report
import api from '@/lib/api' ;
import jsPDF from 'jspdf' ;
import autoTable from 'jspdf-autotable' ;
export async function generatePDF (projectId : string ): Promise <Blob > {
const [project, findings, tasks] = await Promise .all ([
api.get (`/api/projects/${projectId} ` ),
api.get (`/api/findings?project=${projectId} ` ),
api.get (`/api/tasks?project=${projectId} ` )
]);
const doc = new jsPDF ();
doc.setFontSize (24 );
doc.text ('Penetration Testing Report' , 20 , 30 );
doc.setFontSize (16 );
doc.text (project.data .name , 20 , 45 );
doc.setFontSize (12 );
doc.text (`Generated: ${new Date ().toLocaleDateString()} ` , 20 , 55 );
doc.addPage ();
doc.setFontSize (18 );