ReconScope is an import-only, local reconnaissance visualization application. It parses existing Nmap XML, Nuclei JSONL, Subfinder JSONL, and HTTPX JSONL output into a deterministic graph. It never executes scanners or makes network requests.
The parser returns this JSON envelope:
{
"schema_version": "1.0",
"sources": [],
"nodes": [],
"edges": [],
"metadata": {
"node_counts": {},
"edge_count": 0,
"warnings": []
}
}Node types are organization, domain, host, service, and
vulnerability. Edges are contains, hosts, exposes, and affects. Every imported
observation retains its source filename, format, line number when available,
field path, raw record, and a bounded excerpt.
Stable IDs make future snapshot comparison deterministic. For example:
domain:acme.test
host:app.acme.test
service:app.acme.test:443:https
vulnerability:app.acme.test:443:exposed-debug-endpoint
The bundled sample_data/before and sample_data/after directories use only
reserved .test domains. They intentionally include duplicate observations,
all four parser formats, a removed host, a fixed finding, a new finding, and a
changed service version/technology for later checkpoints.
This checkpoint uses only the Python standard library, so no scanner or network dependency is needed:
python3 -m unittest discover -s backend/tests -vProgrammatic usage:
from backend.app.models import ScanFile
from backend.app.parsers import parse_scans
graph = parse_scans([
ScanFile(name="nmap.xml", path="sample_data/before/nmap.xml"),
])Install the local API dependencies in a virtual environment:
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txtRun the API locally:
.venv/bin/uvicorn backend.app.main:app --reloadThe interactive API documentation is available at http://127.0.0.1:8000/docs.
The backend stores snapshots in memory and loads only local files or uploaded
content. It never executes scanners or makes outbound requests.
ReconScope uses a transparent additive score for each vulnerability:
score = min(100, severity_points + exposure_bonus + evidence_bonus)
Severity points are info=0, unknown=5, low=20, medium=40, high=70,
and critical=90. HTTPX evidence for a reachable HTTP/HTTPS service adds 10;
Nmap evidence for an open service adds 5. A matched URL/target adds 5, and
non-empty extracted results add 5. Each node exposes the component values in
properties.risk_breakdown. Scores are highest observed finding scores, not
CVSS values or probability estimates.
The graph endpoint accepts repeated query parameters such as:
/api/graph?severity=high&port=443&technology=React&domain=acme.test
Filtering returns the selected findings or services plus their connected organization/domain/host/service path so the result remains explainable.
The React/Cytoscape interface runs on port 5173 and loads the local API on port 8000 by default:
cd frontend
npm install
npm run devThe center canvas is a Cytoscape compound graph. The left panel filters the current snapshot, the graph highlights a selected node's neighborhood, and the right panel shows normalized properties plus the exact source line or XML path and raw excerpt retained by the parsers.
Use Compare samples to load the bundled before and after snapshots and
compare them in one view. The diff keeps the current graph plus baseline-only
fixed findings and removed assets, with visible NEW, FIXED, CHANGED, and
REMOVED status styling. Select a status in the diff panel to keep only that
change class and its explanatory parent path.
The backend comparison endpoint is:
POST /api/diff
{"before_snapshot_id":"snapshot-1","after_snapshot_id":"snapshot-2"}
Reports for the same pair are available as Markdown or HTML:
/api/report/markdown?before_snapshot_id=snapshot-1&after_snapshot_id=snapshot-2
/api/report/html?before_snapshot_id=snapshot-1&after_snapshot_id=snapshot-2
flowchart LR
UI["React + Cytoscape UI"] -->|"local HTTP only"| API["FastAPI API"]
API --> STORE["In-memory snapshot store"]
STORE --> PARSERS["Deterministic import parsers"]
PARSERS --> SCANS["Nmap XML · Nuclei JSONL · Subfinder JSONL · HTTPX JSONL"]
STORE --> DIFF["Diff + explainable risk scoring"]
STORE --> REPORTS["Markdown / HTML reports"]
FIXTURES["Bundled .test demo fixtures"] --> PARSERS
The backend has no database, worker, scanner process, or outbound network
client. Snapshots are held in memory for the lifetime of the API process. The
frontend is a static Vite release bundle served by a local Python static server
in the Docker setup; running npm run build refreshes the checked-in bundle.
From a fresh clone:
docker compose up --buildOpen http://localhost:5173. The API documentation is at
http://localhost:8000/docs.
The complete demo path is:
- Click Load sample with Before snapshot selected.
- Expand the organization, domain, host, and service levels in the graph.
- Click a vulnerability and inspect its exact source evidence in the right panel.
- Click Compare samples to load the bundled before/after snapshots.
- Use the diff status cards to inspect NEW, FIXED, CHANGED, or REMOVED assets.
- Click Markdown or HTML under Export to open the comparison report.
The bundled data lives in sample_data/before and sample_data/after. It uses
reserved .test domains and contains duplicate observations plus intentional
new, fixed, changed, and removed cases, so the workflow is deterministic and
safe to run without owning scan output.
To run without Docker:
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
.venv/bin/uvicorn backend.app.main:app --reloadIn a second terminal:
cd frontend
npm install
npm run devThe interactive Swagger UI is available at http://127.0.0.1:8000/docs, and
the OpenAPI schema is available at http://127.0.0.1:8000/openapi.json.
The frontend defaults to http://localhost:8000; set
VITE_API_BASE_URL when using a different local API port.
./.venv/bin/pytest -q
cd frontend && npm run typecheck && npm run build
docker compose configReconScope is intentionally import-only. It does not run Nmap, Nuclei, Subfinder, or HTTPX, and it does not make outbound requests. It is a local explanation and comparison surface for scan data that already exists.
