pentestify-security-report-generator

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pentestify Security Report Generator

Pentestify 安全报告生成工具

Skill by ara.so — Security Skills collection.
Pentestify is an interactive penetration testing report generator that allows security professionals to:
  • Register vulnerabilities using predefined templates or manually
  • Visualize risk statistics in real-time
  • Export structured corporate reports in PDF format
  • Work in bilingual mode (Spanish/English)
  • Persist reports and findings to SQLite database
  • Manage multiple reports with automatic saving
Built with FastAPI backend (async) and Vanilla JavaScript SPA frontend.
ara.so 提供的技能——安全技能合集。
Pentestify 是一款交互式渗透测试报告生成工具,可供安全专业人员:
  • 使用预定义模板或手动记录漏洞
  • 实时可视化风险统计数据
  • 导出结构化企业级PDF报告
  • 支持双语模式(西班牙语/英语)
  • 将报告和检测结果持久化到SQLite数据库
  • 管理多份报告并自动保存
基于异步FastAPI后端和Vanilla JavaScript单页应用(SPA)前端构建。

Installation

安装

Quick Start with Docker (Recommended)

Docker快速启动(推荐)

bash
undefined
bash
undefined

Build the image

Build the image

docker build -t pentestify:latest .
docker build -t pentestify:latest .

Run with persistent volume

Run with persistent volume

docker run -d
-p 8000:8000
-v pentestify_data:/app/data
--name pentestify
pentestify:latest
docker run -d
-p 8000:8000
-v pentestify_data:/app/data
--name pentestify
pentestify:latest
undefined
undefined

Manual Installation

手动安装

bash
undefined
bash
undefined

Clone repository

Clone repository

Create virtual environment

Create virtual environment

python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate
python3 -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate

Install dependencies

Install dependencies

pip install -r requirements.txt
pip install -r requirements.txt

Install Playwright browsers for PDF generation

Install Playwright browsers for PDF generation

playwright install chromium
playwright install chromium

Run the server

Run the server

python3 run.py

The application will be available at `http://localhost:8000`
python3 run.py

应用将在 `http://localhost:8000` 地址可用

Architecture Overview

架构概述

Frontend (SPA)
  ├── index.html (Single Page Application)
  ├── js/app.js (Vanilla JavaScript)
  └── css/styles.css (Pure CSS)
       ↓ HTTP/REST
Backend (FastAPI)
  ├── main.py (API endpoints)
  ├── models.py (SQLAlchemy ORM)
  ├── schemas.py (Pydantic validation)
  └── database.py (SQLite connection)
Database (SQLite)
  └── pentestify.db
      ├── reports table
      └── findings table
Frontend (SPA)
  ├── index.html (Single Page Application)
  ├── js/app.js (Vanilla JavaScript)
  └── css/styles.css (Pure CSS)
       ↓ HTTP/REST
Backend (FastAPI)
  ├── main.py (API endpoints)
  ├── models.py (SQLAlchemy ORM)
  ├── schemas.py (Pydantic validation)
  └── database.py (SQLite connection)
Database (SQLite)
  └── pentestify.db
      ├── reports table
      └── findings table

API Endpoints

API端点

Reports Management

报告管理

Create Report
python
undefined
创建报告
python
undefined

POST /api/reports

POST /api/reports

Request body

Request body

{ "client_name": "Acme Corp", "report_date": "2026-05-21", "auditor_name": "John Doe", "executive_summary": "Security assessment of web application", "scope": "Web application, API endpoints", "methodology": "OWASP Testing Guide v4.2", "language": "en" # or "es" }

**Get All Reports**
```python
{ "client_name": "Acme Corp", "report_date": "2026-05-21", "auditor_name": "John Doe", "executive_summary": "Security assessment of web application", "scope": "Web application, API endpoints", "methodology": "OWASP Testing Guide v4.2", "language": "en" # or "es" }

**获取所有报告**
```python

GET /api/reports

GET /api/reports

Response: List of reports with metadata

Response: List of reports with metadata


**Get Single Report**
```python

**获取单个报告**
```python

GET /api/reports/{report_id}

GET /api/reports/{report_id}

Response: Full report with all findings

Response: Full report with all findings


**Update Report**
```python

**更新报告**
```python

PUT /api/reports/{report_id}

PUT /api/reports/{report_id}

Same body structure as POST

Same body structure as POST


**Delete Report**
```python

**删除报告**
```python

DELETE /api/reports/{report_id}

DELETE /api/reports/{report_id}

Cascades to delete all associated findings

Cascades to delete all associated findings

undefined
undefined

Findings Management

检测结果管理

Add Finding to Report
python
undefined
为报告添加检测结果
python
undefined

POST /api/reports/{report_id}/findings

POST /api/reports/{report_id}/findings

{ "title": "SQL Injection in Login Form", "severity": "critical", # critical, high, medium, low, info "cvss_score": 9.8, "description": "The application is vulnerable to SQL injection...", "impact": "Complete database compromise possible", "affected_systems": "login.php, /api/auth endpoint", "evidence": "Payload: ' OR '1'='1 -- ", "remediation": "Use parameterized queries...", "references": "CWE-89, OWASP A03:2021", "order_index": 0 # Position in report }

**Reorder Findings**
```python
{ "title": "SQL Injection in Login Form", "severity": "critical", # critical, high, medium, low, info "cvss_score": 9.8, "description": "The application is vulnerable to SQL injection...", "impact": "Complete database compromise possible", "affected_systems": "login.php, /api/auth endpoint", "evidence": "Payload: ' OR '1'='1 -- ", "remediation": "Use parameterized queries...", "references": "CWE-89, OWASP A03:2021", "order_index": 0 # Position in report }

**重新排序检测结果**
```python

PUT /api/reports/{report_id}/findings/reorder

PUT /api/reports/{report_id}/findings/reorder

{ "finding_ids": [3, 1, 2, 4] # New order by ID }

**Delete Finding**
```python
{ "finding_ids": [3, 1, 2, 4] # New order by ID }

**删除检测结果**
```python

DELETE /api/findings/{finding_id}

DELETE /api/findings/{finding_id}

Automatically reorders remaining findings

Automatically reorders remaining findings

undefined
undefined

PDF Export

PDF导出

Generate PDF Report
python
undefined
生成PDF报告
python
undefined

GET /api/reports/{report_id}/export/pdf

GET /api/reports/{report_id}/export/pdf

Returns PDF file download with corporate formatting

Returns PDF file download with corporate formatting

undefined
undefined

Database Backup

数据库备份

Export Database
python
undefined
导出数据库
python
undefined

GET /api/database/export

GET /api/database/export

Returns pentestify.db file for backup

Returns pentestify.db file for backup


**Import Database**
```python

**导入数据库**
```python

POST /api/database/import

POST /api/database/import

Form-data: file (pentestify.db)

Form-data: file (pentestify.db)

undefined
undefined

Backend Code Examples

后端代码示例

Creating a Custom Vulnerability Template

创建自定义漏洞模板

python
undefined
python
undefined

backend/models.py extension example

backend/models.py extension example

from sqlalchemy import Column, Integer, String, Float, ForeignKey, Text from sqlalchemy.orm import relationship from .database import Base
class FindingTemplate(Base): """Predefined vulnerability templates""" tablename = "finding_templates"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
severity = Column(String)
cvss_base = Column(Float)
description_template = Column(Text)
remediation_template = Column(Text)
cwe_id = Column(String, nullable=True)

def to_finding(self, report_id: int):
    """Convert template to actual finding"""
    return Finding(
        report_id=report_id,
        title=self.name,
        severity=self.severity,
        cvss_score=self.cvss_base,
        description=self.description_template,
        remediation=self.remediation_template,
        references=f"CWE-{self.cwe_id}" if self.cwe_id else ""
    )
undefined
from sqlalchemy import Column, Integer, String, Float, ForeignKey, Text from sqlalchemy.orm import relationship from .database import Base
class FindingTemplate(Base): """Predefined vulnerability templates""" tablename = "finding_templates"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
severity = Column(String)
cvss_base = Column(Float)
description_template = Column(Text)
remediation_template = Column(Text)
cwe_id = Column(String, nullable=True)

def to_finding(self, report_id: int):
    """Convert template to actual finding"""
    return Finding(
        report_id=report_id,
        title=self.name,
        severity=self.severity,
        cvss_score=self.cvss_base,
        description=self.description_template,
        remediation=self.remediation_template,
        references=f"CWE-{self.cwe_id}" if self.cwe_id else ""
    )
undefined

Adding Authentication Endpoint

添加认证端点

python
undefined
python
undefined

backend/main.py

backend/main.py

from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import os
security = HTTPBearer()
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): """Verify API token from environment""" expected_token = os.getenv("PENTESTIFY_API_TOKEN") if not expected_token: raise HTTPException( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Authentication not configured" ) if credentials.credentials != expected_token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication token" ) return credentials.credentials
from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import os
security = HTTPBearer()
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): """Verify API token from environment""" expected_token = os.getenv("PENTESTIFY_API_TOKEN") if not expected_token: raise HTTPException( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Authentication not configured" ) if credentials.credentials != expected_token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication token" ) return credentials.credentials

Protected endpoint example

Protected endpoint example

@app.post("/api/reports", dependencies=[Depends(verify_token)]) async def create_report_protected(report: ReportCreate, db: Session = Depends(get_db)): # Existing logic pass
undefined
@app.post("/api/reports", dependencies=[Depends(verify_token)]) async def create_report_protected(report: ReportCreate, db: Session = Depends(get_db)): # Existing logic pass
undefined

Custom PDF Styling

自定义PDF样式

python
undefined
python
undefined

backend/main.py - Modify PDF generation

backend/main.py - Modify PDF generation

from playwright.async_api import async_playwright
async def generate_pdf_custom(report_html: str, output_path: str): """Generate PDF with custom styling""" async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page()
    # Inject custom CSS
    custom_css = """
    <style>
        @page { 
            size: A4; 
            margin: 20mm;
            @top-center {
                content: "CONFIDENTIAL - Internal Use Only";
                color: red;
                font-size: 10pt;
            }
        }
        body { 
            font-family: 'Arial', sans-serif;
            line-height: 1.6;
        }
        .severity-critical { 
            background: #dc3545;
            color: white;
            padding: 8px;
            border-radius: 4px;
        }
    </style>
    """
    
    full_html = custom_css + report_html
    await page.set_content(full_html)
    
    await page.pdf(
        path=output_path,
        format='A4',
        print_background=True,
        display_header_footer=True,
        header_template='<div style="font-size:10px; text-align:center; width:100%;">Pentestify Report</div>',
        footer_template='<div style="font-size:10px; text-align:center; width:100%;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>'
    )
    
    await browser.close()
undefined
from playwright.async_api import async_playwright
async def generate_pdf_custom(report_html: str, output_path: str): """Generate PDF with custom styling""" async with async_playwright() as p: browser = await p.chromium.launch() page = await browser.new_page()
    # Inject custom CSS
    custom_css = """
    <style>
        @page { 
            size: A4; 
            margin: 20mm;
            @top-center {
                content: "CONFIDENTIAL - Internal Use Only";
                color: red;
                font-size: 10pt;
            }
        }
        body { 
            font-family: 'Arial', sans-serif;
            line-height: 1.6;
        }
        .severity-critical { 
            background: #dc3545;
            color: white;
            padding: 8px;
            border-radius: 4px;
        }
    </style>
    """
    
    full_html = custom_css + report_html
    await page.set_content(full_html)
    
    await page.pdf(
        path=output_path,
        format='A4',
        print_background=True,
        display_header_footer=True,
        header_template='<div style="font-size:10px; text-align:center; width:100%;">Pentestify Report</div>',
        footer_template='<div style="font-size:10px; text-align:center; width:100%;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>'
    )
    
    await browser.close()
undefined

Frontend Integration Examples

前端集成示例

Calling the API from JavaScript

从JavaScript调用API

javascript
// js/app.js example patterns

// Create new report
async function createReport(reportData) {
    try {
        const response = await fetch('/api/reports', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(reportData)
        });
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const newReport = await response.json();
        console.log('Report created:', newReport.id);
        return newReport;
    } catch (error) {
        console.error('Error creating report:', error);
        throw error;
    }
}

// Add finding with auto-save
async function addFinding(reportId, findingData) {
    const finding = {
        ...findingData,
        order_index: await getNextOrderIndex(reportId)
    };
    
    const response = await fetch(`/api/reports/${reportId}/findings`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(finding)
    });
    
    return await response.json();
}

// Export PDF
async function exportPDF(reportId) {
    const response = await fetch(`/api/reports/${reportId}/export/pdf`);
    const blob = await response.blob();
    
    // Trigger download
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `pentest-report-${reportId}.pdf`;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    a.remove();
}

// Load report with findings
async function loadReport(reportId) {
    const response = await fetch(`/api/reports/${reportId}`);
    const report = await response.json();
    
    // Populate form fields
    document.getElementById('client-name').value = report.client_name || '';
    document.getElementById('report-date').value = report.report_date || '';
    
    // Load findings ordered by order_index
    const findings = report.findings.sort((a, b) => a.order_index - b.order_index);
    findings.forEach(finding => renderFinding(finding));
}
javascript
// js/app.js example patterns

// Create new report
async function createReport(reportData) {
    try {
        const response = await fetch('/api/reports', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(reportData)
        });
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const newReport = await response.json();
        console.log('Report created:', newReport.id);
        return newReport;
    } catch (error) {
        console.error('Error creating report:', error);
        throw error;
    }
}

// Add finding with auto-save
async function addFinding(reportId, findingData) {
    const finding = {
        ...findingData,
        order_index: await getNextOrderIndex(reportId)
    };
    
    const response = await fetch(`/api/reports/${reportId}/findings`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(finding)
    });
    
    return await response.json();
}

// Export PDF
async function exportPDF(reportId) {
    const response = await fetch(`/api/reports/${reportId}/export/pdf`);
    const blob = await response.blob();
    
    // Trigger download
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `pentest-report-${reportId}.pdf`;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    a.remove();
}

// Load report with findings
async function loadReport(reportId) {
    const response = await fetch(`/api/reports/${reportId}`);
    const report = await response.json();
    
    // Populate form fields
    document.getElementById('client-name').value = report.client_name || '';
    document.getElementById('report-date').value = report.report_date || '';
    
    // Load findings ordered by order_index
    const findings = report.findings.sort((a, b) => a.order_index - b.order_index);
    findings.forEach(finding => renderFinding(finding));
}

Implementing Drag-and-Drop Reordering

实现拖拽重新排序

javascript
// Enable drag-and-drop for findings list
function enableFindingsReorder() {
    const findingsList = document.getElementById('findings-list');
    
    findingsList.addEventListener('dragstart', (e) => {
        e.target.classList.add('dragging');
    });
    
    findingsList.addEventListener('dragend', async (e) => {
        e.target.classList.remove('dragging');
        await saveFindingsOrder();
    });
    
    findingsList.addEventListener('dragover', (e) => {
        e.preventDefault();
        const afterElement = getDragAfterElement(findingsList, e.clientY);
        const draggable = document.querySelector('.dragging');
        
        if (afterElement == null) {
            findingsList.appendChild(draggable);
        } else {
            findingsList.insertBefore(draggable, afterElement);
        }
    });
}

async function saveFindingsOrder() {
    const findingElements = document.querySelectorAll('.finding-item');
    const findingIds = Array.from(findingElements).map(el => 
        parseInt(el.dataset.findingId)
    );
    
    const reportId = getCurrentReportId();
    await fetch(`/api/reports/${reportId}/findings/reorder`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ finding_ids: findingIds })
    });
}
javascript
// Enable drag-and-drop for findings list
function enableFindingsReorder() {
    const findingsList = document.getElementById('findings-list');
    
    findingsList.addEventListener('dragstart', (e) => {
        e.target.classList.add('dragging');
    });
    
    findingsList.addEventListener('dragend', async (e) => {
        e.target.classList.remove('dragging');
        await saveFindingsOrder();
    });
    
    findingsList.addEventListener('dragover', (e) => {
        e.preventDefault();
        const afterElement = getDragAfterElement(findingsList, e.clientY);
        const draggable = document.querySelector('.dragging');
        
        if (afterElement == null) {
            findingsList.appendChild(draggable);
        } else {
            findingsList.insertBefore(draggable, afterElement);
        }
    });
}

async function saveFindingsOrder() {
    const findingElements = document.querySelectorAll('.finding-item');
    const findingIds = Array.from(findingElements).map(el => 
        parseInt(el.dataset.findingId)
    );
    
    const reportId = getCurrentReportId();
    await fetch(`/api/reports/${reportId}/findings/reorder`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ finding_ids: findingIds })
    });
}

Common Patterns

通用模式

Auto-Save Implementation

自动保存实现

javascript
// Debounced auto-save for report fields
let autoSaveTimeout;

function setupAutoSave() {
    const formInputs = document.querySelectorAll('#report-form input, #report-form textarea');
    
    formInputs.forEach(input => {
        input.addEventListener('input', () => {
            clearTimeout(autoSaveTimeout);
            autoSaveTimeout = setTimeout(async () => {
                await saveReport();
                showNotification('Report saved', 'success');
            }, 2000); // Save 2 seconds after last change
        });
    });
}

async function saveReport() {
    const reportData = {
        client_name: document.getElementById('client-name').value,
        report_date: document.getElementById('report-date').value,
        auditor_name: document.getElementById('auditor-name').value,
        executive_summary: document.getElementById('executive-summary').value,
        scope: document.getElementById('scope').value,
        methodology: document.getElementById('methodology').value,
        language: document.getElementById('language-select').value
    };
    
    const reportId = getCurrentReportId();
    if (reportId) {
        await fetch(`/api/reports/${reportId}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(reportData)
        });
    }
}
javascript
// Debounced auto-save for report fields
let autoSaveTimeout;

function setupAutoSave() {
    const formInputs = document.querySelectorAll('#report-form input, #report-form textarea');
    
    formInputs.forEach(input => {
        input.addEventListener('input', () => {
            clearTimeout(autoSaveTimeout);
            autoSaveTimeout = setTimeout(async () => {
                await saveReport();
                showNotification('Report saved', 'success');
            }, 2000); // Save 2 seconds after last change
        });
    });
}

async function saveReport() {
    const reportData = {
        client_name: document.getElementById('client-name').value,
        report_date: document.getElementById('report-date').value,
        auditor_name: document.getElementById('auditor-name').value,
        executive_summary: document.getElementById('executive-summary').value,
        scope: document.getElementById('scope').value,
        methodology: document.getElementById('methodology').value,
        language: document.getElementById('language-select').value
    };
    
    const reportId = getCurrentReportId();
    if (reportId) {
        await fetch(`/api/reports/${reportId}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(reportData)
        });
    }
}

Severity Badge Rendering

风险等级徽章渲染

javascript
function getSeverityBadge(severity) {
    const badges = {
        critical: { color: '#dc3545', label: 'CRITICAL' },
        high: { color: '#fd7e14', label: 'HIGH' },
        medium: { color: '#ffc107', label: 'MEDIUM' },
        low: { color: '#0dcaf0', label: 'LOW' },
        info: { color: '#6c757d', label: 'INFO' }
    };
    
    const badge = badges[severity] || badges.info;
    return `<span style="background: ${badge.color}; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;">${badge.label}</span>`;
}
javascript
function getSeverityBadge(severity) {
    const badges = {
        critical: { color: '#dc3545', label: 'CRITICAL' },
        high: { color: '#fd7e14', label: 'HIGH' },
        medium: { color: '#ffc107', label: 'MEDIUM' },
        low: { color: '#0dcaf0', label: 'LOW' },
        info: { color: '#6c757d', label: 'INFO' }
    };
    
    const badge = badges[severity] || badges.info;
    return `<span style="background: ${badge.color}; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;">${badge.label}</span>`;
}

Real-Time Statistics

实时统计数据

javascript
async function updateStatistics(reportId) {
    const response = await fetch(`/api/reports/${reportId}`);
    const report = await response.json();
    
    const stats = {
        total: report.findings.length,
        critical: report.findings.filter(f => f.severity === 'critical').length,
        high: report.findings.filter(f => f.severity === 'high').length,
        medium: report.findings.filter(f => f.severity === 'medium').length,
        low: report.findings.filter(f => f.severity === 'low').length,
        avgCvss: (report.findings.reduce((sum, f) => sum + f.cvss_score, 0) / report.findings.length).toFixed(1)
    };
    
    document.getElementById('stat-total').textContent = stats.total;
    document.getElementById('stat-critical').textContent = stats.critical;
    document.getElementById('stat-high').textContent = stats.high;
    document.getElementById('stat-avg-cvss').textContent = stats.avgCvss;
}
javascript
async function updateStatistics(reportId) {
    const response = await fetch(`/api/reports/${reportId}`);
    const report = await response.json();
    
    const stats = {
        total: report.findings.length,
        critical: report.findings.filter(f => f.severity === 'critical').length,
        high: report.findings.filter(f => f.severity === 'high').length,
        medium: report.findings.filter(f => f.severity === 'medium').length,
        low: report.findings.filter(f => f.severity === 'low').length,
        avgCvss: (report.findings.reduce((sum, f) => sum + f.cvss_score, 0) / report.findings.length).toFixed(1)
    };
    
    document.getElementById('stat-total').textContent = stats.total;
    document.getElementById('stat-critical').textContent = stats.critical;
    document.getElementById('stat-high').textContent = stats.high;
    document.getElementById('stat-avg-cvss').textContent = stats.avgCvss;
}

Testing

测试

Running Tests

运行测试

bash
undefined
bash
undefined

All tests

All tests

python -m pytest backend/tests/ -v
python -m pytest backend/tests/ -v

Specific test file

Specific test file

python -m pytest backend/tests/test_reports.py -v
python -m pytest backend/tests/test_reports.py -v

With coverage

With coverage

python -m pytest backend/tests/ -v --cov=backend --cov-report=html
python -m pytest backend/tests/ -v --cov=backend --cov-report=html

Watch mode during development

Watch mode during development

pytest-watch backend/tests/
undefined
pytest-watch backend/tests/
undefined

Example Test Cases

测试用例示例

python
undefined
python
undefined

backend/tests/test_findings.py

backend/tests/test_findings.py

import pytest from fastapi.testclient import TestClient
def test_add_finding_to_report(client: TestClient, sample_report_id: int): """Test adding a vulnerability finding""" finding = { "title": "Cross-Site Scripting (XSS)", "severity": "high", "cvss_score": 7.3, "description": "Reflected XSS in search parameter", "impact": "Session hijacking, credential theft", "affected_systems": "/search?q=<payload>", "evidence": "<script>alert(document.cookie)</script>", "remediation": "Implement output encoding", "references": "CWE-79, OWASP A03:2021", "order_index": 0 }
response = client.post(f"/api/reports/{sample_report_id}/findings", json=finding)
assert response.status_code == 200

data = response.json()
assert data["title"] == finding["title"]
assert data["severity"] == finding["severity"]
assert "id" in data
def test_reorder_findings(client: TestClient, sample_report_with_findings: dict): """Test drag-and-drop finding reordering""" report_id = sample_report_with_findings["report_id"] finding_ids = sample_report_with_findings["finding_ids"]
# Reverse order
new_order = list(reversed(finding_ids))

response = client.put(
    f"/api/reports/{report_id}/findings/reorder",
    json={"finding_ids": new_order}
)
assert response.status_code == 200

# Verify new order
report_response = client.get(f"/api/reports/{report_id}")
findings = report_response.json()["findings"]

for idx, finding in enumerate(sorted(findings, key=lambda x: x["order_index"])):
    assert finding["id"] == new_order[idx]
undefined
import pytest from fastapi.testclient import TestClient
def test_add_finding_to_report(client: TestClient, sample_report_id: int): """Test adding a vulnerability finding""" finding = { "title": "Cross-Site Scripting (XSS)", "severity": "high", "cvss_score": 7.3, "description": "Reflected XSS in search parameter", "impact": "Session hijacking, credential theft", "affected_systems": "/search?q=<payload>", "evidence": "<script>alert(document.cookie)</script>", "remediation": "Implement output encoding", "references": "CWE-79, OWASP A03:2021", "order_index": 0 }
response = client.post(f"/api/reports/{sample_report_id}/findings", json=finding)
assert response.status_code == 200

data = response.json()
assert data["title"] == finding["title"]
assert data["severity"] == finding["severity"]
assert "id" in data
def test_reorder_findings(client: TestClient, sample_report_with_findings: dict): """Test drag-and-drop finding reordering""" report_id = sample_report_with_findings["report_id"] finding_ids = sample_report_with_findings["finding_ids"]
# Reverse order
new_order = list(reversed(finding_ids))

response = client.put(
    f"/api/reports/{report_id}/findings/reorder",
    json={"finding_ids": new_order}
)
assert response.status_code == 200

# Verify new order
report_response = client.get(f"/api/reports/{report_id}")
findings = report_response.json()["findings"]

for idx, finding in enumerate(sorted(findings, key=lambda x: x["order_index"])):
    assert finding["id"] == new_order[idx]
undefined

Configuration

配置

Environment Variables

环境变量

bash
undefined
bash
undefined

.env file (not included in repo)

.env file (not included in repo)

PENTESTIFY_API_TOKEN=your_secret_token_here PENTESTIFY_DB_PATH=/app/data/pentestify.db PENTESTIFY_HOST=0.0.0.0 PENTESTIFY_PORT=8000 PENTESTIFY_LOG_LEVEL=info
undefined
PENTESTIFY_API_TOKEN=your_secret_token_here PENTESTIFY_DB_PATH=/app/data/pentestify.db PENTESTIFY_HOST=0.0.0.0 PENTESTIFY_PORT=8000 PENTESTIFY_LOG_LEVEL=info
undefined

Docker Volume Management

Docker卷管理

bash
undefined
bash
undefined

Create named volume for persistence

Create named volume for persistence

docker volume create pentestify_data
docker volume create pentestify_data

Backup database

Backup database

docker run --rm
-v pentestify_data:/data
-v $(pwd):/backup
alpine cp /data/pentestify.db /backup/pentestify_backup.db
docker run --rm
-v pentestify_data:/data
-v $(pwd):/backup
alpine cp /data/pentestify.db /backup/pentestify_backup.db

Restore database

Restore database

docker run --rm
-v pentestify_data:/data
-v $(pwd):/backup
alpine cp /backup/pentestify_backup.db /data/pentestify.db
undefined
docker run --rm
-v pentestify_data:/data
-v $(pwd):/backup
alpine cp /backup/pentestify_backup.db /data/pentestify.db
undefined

Custom Port Binding

自定义端口绑定

bash
undefined
bash
undefined

Run on different port

Run on different port

docker run -d -p 9000:8000 -v pentestify_data:/app/data pentestify:latest
docker run -d -p 9000:8000 -v pentestify_data:/app/data pentestify:latest
undefined
undefined

Troubleshooting

故障排除

PDF Generation Fails

PDF生成失败

Issue:
Error generating PDF: Playwright browser not found
Solution:
bash
undefined
问题:
Error generating PDF: Playwright browser not found
解决方案:
bash
undefined

Reinstall Playwright browsers

Reinstall Playwright browsers

playwright install chromium
playwright install chromium

If in Docker, rebuild image

If in Docker, rebuild image

docker build --no-cache -t pentestify:latest .
undefined
docker build --no-cache -t pentestify:latest .
undefined

Database Locked Error

数据库锁定错误

Issue:
sqlite3.OperationalError: database is locked
Solution:
python
undefined
问题:
sqlite3.OperationalError: database is locked
解决方案:
python
undefined

backend/database.py - Increase timeout

backend/database.py - Increase timeout

engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False, "timeout": 30} )
undefined
engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False, "timeout": 30} )
undefined

Findings Not Reordering

检测结果无法重新排序

Issue: Drag-and-drop doesn't persist order
Solution: Check order_index initialization
python
undefined
问题: 拖拽操作无法保存排序结果
解决方案: 检查order_index初始化
python
undefined

Ensure all findings have order_index set

Ensure all findings have order_index set

async def fix_missing_order_indexes(db: Session, report_id: int): findings = db.query(Finding).filter(Finding.report_id == report_id).all() for idx, finding in enumerate(findings): if finding.order_index is None: finding.order_index = idx db.commit()
undefined
async def fix_missing_order_indexes(db: Session, report_id: int): findings = db.query(Finding).filter(Finding.report_id == report_id).all() for idx, finding in enumerate(findings): if finding.order_index is None: finding.order_index = idx db.commit()
undefined

CORS Issues in Development

开发环境下的CORS问题

Issue: Frontend can't connect to backend
Solution:
python
undefined
问题: 前端无法连接后端
解决方案:
python
undefined

backend/main.py

backend/main.py

from fastapi.middleware.cors import CORSMiddleware
app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000", "http://127.0.0.1:8000"], allow_credentials=True, allow_methods=[""], allow_headers=[""], )
undefined
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000", "http://127.0.0.1:8000"], allow_credentials=True, allow_methods=[""], allow_headers=[""], )
undefined

Reports Not Saving

报告无法保存

Issue: Auto-save doesn't work
Solution: Check browser console for errors
javascript
// Add error handling
async function saveReport() {
    try {
        // ... save logic
    } catch (error) {
        console.error('Save failed:', error);
        showNotification('Failed to save report', 'error');
    }
}
问题: 自动保存功能失效
解决方案: 检查浏览器控制台错误
javascript
// Add error handling
async function saveReport() {
    try {
        // ... save logic
    } catch (error) {
        console.error('Save failed:', error);
        showNotification('Failed to save report', 'error');
    }
}

Integration Examples

集成示例

CI/CD Pipeline

CI/CD流水线

yaml
undefined
yaml
undefined

.github/workflows/test.yml

.github/workflows/test.yml

name: Test Pentestify
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2
  - name: Set up Python
    uses: actions/setup-python@v2
    with:
      python-version: '3.9'
  
  - name: Install dependencies
    run: |
      pip install -r requirements.txt
      playwright install chromium
  
  - name: Run tests
    run: python -m pytest backend/tests/ -v --cov=backend
  
  - name: Build Docker image
    run: docker build -t pentestify:test .
undefined
name: Test Pentestify
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2
  - name: Set up Python
    uses: actions/setup-python@v2
    with:
      python-version: '3.9'
  
  - name: Install dependencies
    run: |
      pip install -r requirements.txt
      playwright install chromium
  
  - name: Run tests
    run: python -m pytest backend/tests/ -v --cov=backend
  
  - name: Build Docker image
    run: docker build -t pentestify:test .
undefined

Nginx Reverse Proxy

Nginx反向代理

nginx
undefined
nginx
undefined

/etc/nginx/sites-available/pentestify

/etc/nginx/sites-available/pentestify

server { listen 80; server_name pentest.example.com;
location / {
    proxy_pass http://localhost:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# Increase timeout for PDF generation
location /api/reports/*/export/pdf {
    proxy_pass http://localhost:8000;
    proxy_read_timeout 120s;
}
}

This skill provides comprehensive coverage of Pentestify's capabilities for AI coding agents to assist developers in generating professional security reports.
server { listen 80; server_name pentest.example.com;
location / {
    proxy_pass http://localhost:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# Increase timeout for PDF generation
location /api/reports/*/export/pdf {
    proxy_pass http://localhost:8000;
    proxy_read_timeout 120s;
}
}

本技能全面覆盖了Pentestify的功能,可帮助AI编码助手协助开发者生成专业的安全报告。