Add automatic multi-format report generation and ZIP archiving
Implements automatic generation of JSON, HTML, and ZIP outputs after every scan, with all files sharing the same timestamp for easy correlation. Features: - Automatic HTML report generation after every scan - ZIP archive creation containing JSON, HTML, and all screenshots - Unified timestamp across all outputs (JSON, HTML, ZIP, screenshots) - Graceful error handling (scan continues if HTML/ZIP generation fails) - Email-ready ZIP archives for easy sharing Technical changes: - Fixed timestamp mismatch between scan() and save_report() - Added generate_outputs() method to SneakyScanner class - scan() now returns (report, timestamp) tuple - save_report() accepts timestamp parameter instead of generating new one - main() updated to call generate_outputs() for all output formats - Added zipfile import and HTMLReportGenerator import - Dockerfile updated to copy templates/ directory Output structure: - scan_report_YYYYMMDD_HHMMSS.json (JSON report) - scan_report_YYYYMMDD_HHMMSS.html (HTML report) - scan_report_YYYYMMDD_HHMMSS.zip (archive with JSON, HTML, screenshots) - scan_report_YYYYMMDD_HHMMSS_screenshots/ (screenshots directory) Documentation updated: - README.md: Updated Output Format, Features, Quick Start sections - CLAUDE.md: Updated Core Components, Scan Workflow, Key Design Decisions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
100
src/scanner.py
100
src/scanner.py
@@ -10,6 +10,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any
|
||||
@@ -20,6 +21,7 @@ from libnmap.process import NmapProcess
|
||||
from libnmap.parser import NmapParser
|
||||
|
||||
from screenshot_capture import ScreenshotCapture
|
||||
from report_generator import HTMLReportGenerator
|
||||
|
||||
# Force unbuffered output for Docker
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
@@ -684,12 +686,11 @@ class SneakyScanner:
|
||||
if self.screenshot_capture:
|
||||
self.screenshot_capture._close_browser()
|
||||
|
||||
return report
|
||||
return report, scan_timestamp
|
||||
|
||||
def save_report(self, report: Dict[str, Any]) -> Path:
|
||||
"""Save scan report to JSON file"""
|
||||
timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
|
||||
output_file = self.output_dir / f"scan_report_{timestamp}.json"
|
||||
def save_report(self, report: Dict[str, Any], scan_timestamp: str) -> Path:
|
||||
"""Save scan report to JSON file using provided timestamp"""
|
||||
output_file = self.output_dir / f"scan_report_{scan_timestamp}.json"
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
json.dump(report, f, indent=2)
|
||||
@@ -697,6 +698,86 @@ class SneakyScanner:
|
||||
print(f"\nReport saved to: {output_file}", flush=True)
|
||||
return output_file
|
||||
|
||||
def generate_outputs(self, report: Dict[str, Any], scan_timestamp: str) -> Dict[str, Path]:
|
||||
"""
|
||||
Generate all output formats: JSON, HTML report, and ZIP archive
|
||||
|
||||
Args:
|
||||
report: Scan report dictionary
|
||||
scan_timestamp: Timestamp string in format YYYYMMDD_HHMMSS
|
||||
|
||||
Returns:
|
||||
Dictionary with paths to generated files: {'json': Path, 'html': Path, 'zip': Path}
|
||||
"""
|
||||
output_paths = {}
|
||||
|
||||
# Step 1: Save JSON report
|
||||
print("\n" + "="*60, flush=True)
|
||||
print("Generating outputs...", flush=True)
|
||||
print("="*60, flush=True)
|
||||
|
||||
json_path = self.save_report(report, scan_timestamp)
|
||||
output_paths['json'] = json_path
|
||||
|
||||
# Step 2: Generate HTML report
|
||||
html_path = self.output_dir / f"scan_report_{scan_timestamp}.html"
|
||||
|
||||
try:
|
||||
print(f"\nGenerating HTML report...", flush=True)
|
||||
|
||||
# Auto-detect template directory relative to this script
|
||||
template_dir = Path(__file__).parent.parent / 'templates'
|
||||
|
||||
# Create HTML report generator
|
||||
generator = HTMLReportGenerator(
|
||||
json_report_path=str(json_path),
|
||||
template_dir=str(template_dir)
|
||||
)
|
||||
|
||||
# Generate report
|
||||
html_result = generator.generate_report(output_path=str(html_path))
|
||||
output_paths['html'] = Path(html_result)
|
||||
|
||||
print(f"HTML report saved to: {html_path}", flush=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: HTML report generation failed: {e}", file=sys.stderr, flush=True)
|
||||
print(f"Continuing with JSON output only...", file=sys.stderr, flush=True)
|
||||
# Don't add html_path to output_paths if it failed
|
||||
|
||||
# Step 3: Create ZIP archive
|
||||
zip_path = self.output_dir / f"scan_report_{scan_timestamp}.zip"
|
||||
|
||||
try:
|
||||
print(f"\nCreating ZIP archive...", flush=True)
|
||||
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
# Add JSON report
|
||||
zipf.write(json_path, json_path.name)
|
||||
|
||||
# Add HTML report if it was generated
|
||||
if 'html' in output_paths and html_path.exists():
|
||||
zipf.write(html_path, html_path.name)
|
||||
|
||||
# Add screenshots directory if it exists
|
||||
screenshot_dir = self.output_dir / f"scan_report_{scan_timestamp}_screenshots"
|
||||
if screenshot_dir.exists() and screenshot_dir.is_dir():
|
||||
# Add all files in screenshot directory
|
||||
for screenshot_file in screenshot_dir.iterdir():
|
||||
if screenshot_file.is_file():
|
||||
# Preserve directory structure in ZIP
|
||||
arcname = f"{screenshot_dir.name}/{screenshot_file.name}"
|
||||
zipf.write(screenshot_file, arcname)
|
||||
|
||||
output_paths['zip'] = zip_path
|
||||
print(f"ZIP archive saved to: {zip_path}", flush=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: ZIP archive creation failed: {e}", file=sys.stderr, flush=True)
|
||||
# Don't add zip_path to output_paths if it failed
|
||||
|
||||
return output_paths
|
||||
|
||||
|
||||
def main():
|
||||
# Configure logging
|
||||
@@ -723,12 +804,15 @@ def main():
|
||||
|
||||
try:
|
||||
scanner = SneakyScanner(args.config, args.output_dir)
|
||||
report = scanner.scan()
|
||||
output_file = scanner.save_report(report)
|
||||
report, scan_timestamp = scanner.scan()
|
||||
output_paths = scanner.generate_outputs(report, scan_timestamp)
|
||||
|
||||
print("\n" + "="*60, flush=True)
|
||||
print("Scan completed successfully!", flush=True)
|
||||
print(f"Results: {output_file}", flush=True)
|
||||
print("="*60, flush=True)
|
||||
print(f" JSON Report: {output_paths.get('json', 'N/A')}", flush=True)
|
||||
print(f" HTML Report: {output_paths.get('html', 'N/A')}", flush=True)
|
||||
print(f" ZIP Archive: {output_paths.get('zip', 'N/A')}", flush=True)
|
||||
print("="*60, flush=True)
|
||||
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user