Summary
SecuScan saves scan results to the output/ directory, but the React
frontend has no way to list, reload, or compare past scans. Every
session starts blank — previous findings in output/ are invisible to
the UI unless the user manually digs through files.
🚨 Problem
Current broken flow:
- User runs Scan A on
target1.local → results saved to output/scan_xyz.json
- User applies a fix to the target
- User runs Scan B on the same target
- Scan A results are gone from the UI — no way to compare before/after
- The
output/ folder accumulates files that the frontend never reads
This is a fundamental gap for a pentesting tool. Security engineers
always need to compare scan results across time to verify that
vulnerabilities were actually remediated.
Reproduction:
Run any scan via the SecuScan UI
Close the browser tab / refresh the page
Navigate back to the Findings page
→ Expected: previous scan results are listed and selectable
→ Actual: Findings page is empty / shows only current scan state
✅ Proposed Fix — Scan History Sidebar + Result Loader
Backend: API endpoint to list past scan results
# backend/routes/history.py
import os, json
from pathlib import Path
from flask import Blueprint, jsonify # or FastAPI router equivalent
history_bp = Blueprint("history", __name__)
OUTPUT_DIR = Path(__file__).parent.parent / "output"
@history_bp.route("/api/history", methods=["GET"])
def list_scans():
"""Return metadata for all past scans in output/"""
scans = []
for f in sorted(OUTPUT_DIR.glob("*.json"), key=os.path.getmtime, reverse=True):
try:
with open(f) as fp:
data = json.load(fp)
scans.append({
"id": f.stem,
"filename": f.name,
"target": data.get("target", "unknown"),
"timestamp": data.get("timestamp", str(f.stat().st_mtime)),
"finding_count": len(data.get("findings", [])),
"severity_summary": _count_severities(data.get("findings", []))
})
except Exception:
continue
return jsonify(scans)
@history_bp.route("/api/history/<scan_id>", methods=["GET"])
def get_scan(scan_id: str):
"""Load a specific past scan by ID"""
path = OUTPUT_DIR / f"{scan_id}.json"
if not path.exists():
return jsonify({"error": "Scan not found"}), 404
with open(path) as fp:
return jsonify(json.load(fp))
def _count_severities(findings: list) -> dict:
counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
for f in findings:
sev = f.get("severity", "info").lower()
counts[sev] = counts.get(sev, 0) + 1
return counts
Frontend: Scan History sidebar component
// frontend/src/components/ScanHistory.tsx
import { useEffect, useState } from "react";
interface ScanMeta {
id: string;
target: string;
timestamp: string;
finding_count: number;
severity_summary: Record<string, number>;
}
interface Props {
onSelect: (scanId: string) => void;
activeScanId?: string;
}
export function ScanHistory({ onSelect, activeScanId }: Props) {
const [history, setHistory] = useState<ScanMeta[]>([]);
useEffect(() => {
fetch("/api/history")
.then((r) => r.json())
.then(setHistory)
.catch(console.error);
}, []);
if (history.length === 0) {
return (
<div className="text-muted-foreground text-sm p-4">
No past scans found.
</div>
);
}
return (
<div className="flex flex-col gap-1 p-2">
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground px-2 mb-1">
Scan History
</h3>
{history.map((scan) => (
<button
key={scan.id}
onClick={() => onSelect(scan.id)}
className={`text-left rounded-md px-3 py-2 text-sm transition-colors
${activeScanId === scan.id
? "bg-primary text-primary-foreground"
: "hover:bg-muted"}`}
>
<div className="font-medium truncate">{scan.target}</div>
<div className="text-xs text-muted-foreground flex gap-2 mt-0.5">
<span>{new Date(scan.timestamp).toLocaleDateString()}</span>
<span>·</span>
<span>{scan.finding_count} findings</span>
{scan.severity_summary.critical > 0 && (
<span className="text-red-500 font-semibold">
{scan.severity_summary.critical} critical
</span>
)}
</div>
</button>
))}
</div>
);
}
Wire into the Findings page
// frontend/src/pages/Findings.tsx (update existing)
import { ScanHistory } from "@/components/ScanHistory";
// Add state for selected historical scan
const [activeScanId, setActiveScanId] = useState<string | undefined>();
// When a history item is selected, fetch and display it
useEffect(() => {
if (!activeScanId) return;
fetch(`/api/history/${activeScanId}`)
.then(r => r.json())
.then(data => setFindings(data.findings))
.catch(console.error);
}, [activeScanId]);
// In JSX — add sidebar
<div className="flex gap-4">
<aside className="w-64 shrink-0 border-r">
<ScanHistory onSelect={setActiveScanId} activeScanId={activeScanId} />
</aside>
<main className="flex-1">
{/* existing findings table */}
</main>
</div>
📊 Impact Table
| Scenario |
Before |
After |
| View past scan |
❌ Impossible |
✅ Click in sidebar |
| Compare before/after fix |
❌ Impossible |
✅ Switch between scans |
output/ folder |
Fills up silently |
Browseable in UI |
| Re-run same target |
Loses previous data |
Both scans visible |
| Critical finding count |
Only current scan |
All historical scans |
📁 Files to Add / Modify
backend/routes/history.py — new API blueprint (2 endpoints)
backend/app.py — register history_bp
frontend/src/components/ScanHistory.tsx — new sidebar component
frontend/src/pages/Findings.tsx (or equivalent) — wire sidebar + history fetch
🧪 How to Test
- Run 2 separate scans on different targets
- Navigate to Findings page → sidebar should list both scans with target name + date
- Click scan 1 → findings table updates to show scan 1 results
- Click scan 2 → findings table switches to scan 2 results
- Refresh page → sidebar still shows both scans (persisted from
output/)
Happy to implement if assigned.
Estimated effort: 4–6 hours | Difficulty: Intermediate
Labels: level:intermediate (35 pts) · type:feature · gssoc
Summary
SecuScan saves scan results to the
output/directory, but the Reactfrontend has no way to list, reload, or compare past scans. Every
session starts blank — previous findings in
output/are invisible tothe UI unless the user manually digs through files.
🚨 Problem
Current broken flow:
target1.local→ results saved tooutput/scan_xyz.jsonoutput/folder accumulates files that the frontend never readsThis is a fundamental gap for a pentesting tool. Security engineers
always need to compare scan results across time to verify that
vulnerabilities were actually remediated.
Reproduction:
Run any scan via the SecuScan UI
Close the browser tab / refresh the page
Navigate back to the Findings page
→ Expected: previous scan results are listed and selectable
→ Actual: Findings page is empty / shows only current scan state
✅ Proposed Fix — Scan History Sidebar + Result Loader
Backend: API endpoint to list past scan results
Frontend: Scan History sidebar component
Wire into the Findings page
📊 Impact Table
output/folder📁 Files to Add / Modify
backend/routes/history.py— new API blueprint (2 endpoints)backend/app.py— registerhistory_bpfrontend/src/components/ScanHistory.tsx— new sidebar componentfrontend/src/pages/Findings.tsx(or equivalent) — wire sidebar + history fetch🧪 How to Test
output/)