An automated Python-based reconnaissance and vulnerability discovery tool for web applications. Built for authorised security assessments — maps the attack surface, detects misconfigurations, and prioritises findings by severity.
This tool is for authorised security testing only. Always obtain written permission before scanning any web application. Unauthorised scanning may violate computer fraud and cybercrime laws. The authors assume no liability for misuse.
This tool is for authorized security testing only. All development and testing conducted on intentionally vulnerable environments (DVWA, WebGoat) owned by the developer.
Explore the scanner's reporting capabilities, dashboard layout, and verification logs through our interactive live demo:
🔗 Launch Interactive Showcase Landing Page
- 🧪 DVWA Lab Scan Report: Scan of Damn Vulnerable Web Application showing browser-verified SQLi, CMDi, and XSS findings.
- 🛍️ OWASP Juice Shop Report: Scan of modern Node/Angular application showing CORS, SSRF, and authentication observations.
- 🏢 Sanitized SMB Scan Report: Realistic production-style report with sensitive company domains and network identifiers sanitized to
example-smb.local.
Obscure ships a lab eval harness under evals/:
| Artifact | Purpose |
|---|---|
evals/ground_truth/dvwa.yaml |
Authorized DVWA expectations |
evals/score_scan.py |
Precision / recall / F1 scorer |
evals/results/LATEST.md |
Latest scored table |
docs/EVALUATION.md |
Ablation matrix + recipes |
docs/CONTRIBUTION.md |
Originality / viva framing |
docs/DEMO_SCRIPT.md |
Timed demo walkthrough |
Fixture smoke (no live target): F1 ≈ 0.91 on evals/fixtures/sample_dvwa_scan_results.json (see evals/results/LATEST.md). Replace with your authorized DVWA numbers via bash evals/run_lab_eval.sh.
PDFs for viva / portfolio:
| Document | Path |
|---|---|
| How It Works (full guide) | docs/Obscure_How_It_Works_Project_Guide.pdf |
| Evaluation & portfolio pack | docs/Obscure_Evaluation_Portfolio.pdf |
python3 docs/build_how_it_works_pdf.py
python3 docs/build_evaluation_portfolio_pdf.pyLearning (honest scope):
- Continuous payload / verification memory — IMPLEMENTED (
payload_stats.json, learning DB) - Mid-assessment pattern propose — IMPLEMENTED (
--adaptive-learn→ PROPOSED only; mock if noLLM_API_KEY) - Auto-ACTIVE patterns — not implemented (human
promote --approve)
The scanner automates the reconnaissance, vulnerability mapping, active verification, and attack chain reasoning phases of a web application penetration test. It operates across a structured seven-phase penetration testing sequence:
- Phase 0: Target Profiling — Identification of framework fingerprints, technology mappings, brain advisories, and knowledge graph vulnerability reasoning.
- Phase 1: Recon — Active DNS and subdomain enumeration.
- Phase 2: Crawl — spider-rs (
spider_cli) page discovery + enrichment (legacy BeautifulSoup fallback ifspidermissing). - Phase 2.5: Form Intelligence — Schema parsing of fields/parameters and input surface mapping.
- Phase 3: Scan — Parameter reflection canary analysis, active injection scans (SQLi, CMDi, LFI, SSRF), and CSRF verification.
- Phase 4: Verification — Proof validation via headless Playwright execution and findings classification.
- Phase 5: Data Flow Mapping — Stored data discovery (Stored XSS) and form workflow path mapping.
- Phase 6: Attack Chains — Shortest path attack graphs mapping to high-impact administrative targets.
| Feature | Description |
|---|---|
| Threat Intelligence Brain | 7-module cognitive engine mapping vulnerabilities, caching advisories, and dynamically evolutionizing payloads. |
| Form Intelligence Layer | Parses input/select/textarea schema metadata, logging them as Input Surfaces to guide scanning. |
| Active Parameter Reflection | Injects canaries to classify reflection states (raw vs encoded) and avoid blind payload firing. |
| CSRF Analysis | Detects anti-CSRF tokens, tests dynamic token rotation, and attempts bypasses to confirm vulnerabilities. |
| Stored Data Discovery | Tracks inputs across administrative and visited endpoints for stored XSS vectors. |
| Playwright Browser Verification | Executes payloads in a headless browser to confirm XSS and other client-side exploits. |
| ML-Based False Positive Classification | Random Forest classifier trained on 43 feature dimensions to filter false positives. |
| Attack Chain Reasoning | Security Graph builds attack paths from multiple low-severity findings into a high-impact chain. |
| WAF Evasion | Multi-signal evasion engine with adaptive delays, header rotation, and jitter. |
| Subdomain Takeover Detection | DNS enumeration and CNAME mismatch detection for cloud provider takeover vulnerabilities. |
| CMS Identification | Detects WordPress, Joomla, Drupal, and other CMS platforms with version fingerprinting. |
| Priority Scoring Engine | Composite 0–100 score for every URL based on risk factors, reflections, and security headers. |
| Scan History & Trends | Compares results across scans to track New, Fixed, Reopened, and Not Fixed findings. |
| Multi-Format Reporting | Generates HTML reports (with interactive dashboard), JSON results, and PDF exports. |
| Intercepting Proxy | Built-in HTTP proxy logger (port 8080) for manual browser session integration. |
| Authentication Support | Cookies, Bearer tokens, and custom headers for authenticated scanning. |
| Compliance Tagging | Every finding maps to CWE, OWASP Top 10 (2017 & 2021), WSTG, PCI DSS, HIPAA, and ISO 27001. |
| Desktop GUI | CustomTkinter app (--gui): splash screen, scan-config panel with automatic flags, live log, and a results dashboard with stat cards, findings table and detail pane. |
| Packaging | Ships as a self-contained .deb (Linux, app-menu entry) or .exe (Windows) with a bundled runtime + Chromium. |
The scanner comprises 20+ dedicated modules, each targeting a specific vulnerability class:
| Module | Vulnerability Type |
|---|---|
sqli_scanner.py |
SQL Injection (error-based, time-based) |
ssrf_scanner.py |
Server-Side Request Forgery (with OOB listener) |
reflected_xss.py |
Reflected Cross-Site Scripting |
dom_xss.py |
DOM-based XSS (static + Playwright verification) |
cmdi_scanner.py |
Command Injection (direct + blind) |
lfi_scanner.py |
Local File Inclusion |
auth_scanner.py |
Authentication bypass & credential analysis |
auth_failures.py |
Auth failure & enumeration detection |
security_misconfig.py |
Security misconfigurations & sensitive file probes |
insecure_design.py |
Insecure design patterns & IDOR |
outdated_components.py |
Outdated libraries, CVE detection |
cms_scanner.py |
CMS identification & admin path enumeration |
http_method_abuse.py |
Dangerous HTTP method (PUT/DELETE/PATCH) abuse |
subdomain_takeover.py |
Subdomain takeover & DNS enumeration |
waf_evasion.py |
WAF detection & evasion |
browser_verifier.py |
Browser-based vulnerability verification |
security_graph.py |
Attack path aggregation & chain reasoning |
target_profiler.py |
Target technology fingerprinting |
attack_planner.py |
Target-specific scan prioritization |
threat_brain.py |
7-stage threat intelligence engine |
observation_pipeline.py |
Behavioral observation & pattern logging |
compliance_scanner.py |
India compliance (DPDP / CERT-In / IT Act / SPDI) |
With --compliance (also included in --full-scan), compliance runs in two stages:
- Phase 3 (parallel) — passive probes (
probe_compliance) run in the same thread pool as SQLi/XSS/etc. - Phase 4.5 (finalize) — maps Critical/High vuln findings onto IT Act / DPDP / CERT-In / SPDI controls (
finalize_compliance)
python3 main.py --update # Threat (NVD) + Regulatory (MeitY/CERT-In) → SQLite
python3 -m obscure.update # same
python3 main.py --update --if-staleStores:
- CVE DB:
.scan_history/obscure/cve.db - Compliance DB:
.scan_history/obscure/compliance.db - Auditable
feed_auditrows (SUCCESS, added/updated/removed, DB version)
Assessment Engine produces Findings / Risk / Compliance → Obscure Report.
- Control catalog:
compliance/controls_india.json - Legal reference PDFs:
compliance/legal_documents/(gitignored; fetch via./scripts/fetch_india_legal_docs.shwithGH_TOKEN) - Results land in
scan_results.json→complianceand an India Compliance report section
This is a technical assessment aid — not legal advice or certification.
- Python 3.8+
- Playwright (for browser verification)
dnspython(for subdomain enumeration)
# Clone the repository
git clone https://github.com/your-username/obscure.git
cd obscure
# Install Python dependencies
pip install -r requirements.txt
# Install Playwright browsers
playwright install
# Optional but recommended: spider-rs crawler for Phase 2 discovery
cargo install spider_cli
# Or point at an existing binary: export SPIDER_BIN=/path/to/spider
beautifulsoup4==4.14.3 certifi==2026.2.25 charset-normalizer==3.4.7 colorama==0.4.6 idna==3.11 requests==2.33.1 soupsieve==2.8.3 typing_extensions==4.15.0 urllib3==2.6.3 python-whois tqdm Jinja2 dnspython playwright scikit-learn pandas scipy networkx
---
## Usage
### Basic Scan
```bash
python main.py -u https://example.com
python main.py -u https://example.com -d 3 --delay 0.5 --threads 5 --full-scan# Via cookies (JSON format)
python main.py -u https://example.com --cookies '{"session":"abc123"}'
# Via Bearer token
python main.py -u https://example.com --bearer-token "eyJhbGciOi..."
# Via custom headers
python main.py -u https://example.com --headers '{"Authorization": "Bearer ...", "X-Custom": "value"}'| Flag | Description |
|---|---|
-u, --url |
Target URL to scan |
-d, --depth |
Crawl depth (default: 2) |
--delay |
Delay between requests (seconds) |
--threads |
Number of concurrent threads (default: 5) |
--output-dir, -o |
Output directory (default: scan_results/) |
--no-redirect-test |
Skip redirect chain analysis |
--browser-verify |
Playwright + backend verification for XSS, SQLi, CMDi, LFI (on by default with --full-scan) |
--compliance |
Enable India compliance checks (DPDP, CERT-In, IT Act, SPDI) |
--full-scan |
Enable SQLi, SSRF, DOM-XSS, Subdomain, observation, compliance, WAF evasion, browser verification, and LLM (does not enable --pdf or --adaptive-learn) |
--llm |
LLM prediction scoring / analysis (on by default with --full-scan; credentials from .env) |
--adaptive-learn |
Opt-in: after verification, propose declarative XSS patterns → PROPOSED only (never auto-ACTIVE; not implied by --full-scan) |
--adaptive-learn-trial |
With --adaptive-learn: bounded TESTING trials for newly proposed patterns |
--update |
Refresh India compliance + NVD feeds together (no scan; no -u needed) |
--gui |
Launch the desktop GUI (CustomTkinter app) — all flags become UI controls |
--pdf |
Generate a PDF report via the dedicated generator (minimalist theme from scan data) |
--pdf-html |
With --pdf: convert the existing HTML report to PDF as-is (classic mode) |
--waf-evasion |
Enable multi-signal WAF evasion |
--waf-min-delay |
Minimum delay for WAF evasion (default: 1.0s) |
--waf-max-delay |
Maximum delay for WAF evasion (default: 5.0s) |
--ssrf-oob-port |
Port for SSRF out-of-band listener (default: 9999) |
--agentic |
Enable bounded, evidence-driven adaptive mid-scan controller |
--agentic-budget |
Request budget for agentic scan controller (default: 500) |
--agentic-max-steps |
Maximum decision steps for agentic controller (default: 200) |
By default, scan results are saved to scan_results/ with the following files:
| File | Description |
|---|---|
scan_report.html |
Interactive HTML pentest report with dashboard, graphs, and detailed findings |
scan_report.pdf |
Print-ready PDF report (generated with --pdf) |
scan_results.json |
Machine-readable JSON findings |
priority_report.txt |
Severity-sorted plain-text summary |
scan_history/ |
JSONL logs of historical scans for trend comparison |
The ml/ directory contains the ML-powered false positive classifier and risk scoring engine:
# Train the false positive classifier and risk scorer
python ml/train.py \
--dvwa ml/data/dvwa_samples.json \
--clean ml/data/clean_samples.json \
--output-dir ml/models/| File | Purpose |
|---|---|
ml/features.py |
43-dimension feature engineering pipeline |
ml/train.py |
Training pipeline (Random Forest + Gradient Boosting) |
ml/predict.py |
Inference; blends risk with NVD CVSS when matched |
ml/nvd_enrich.py |
Lookup threat_intel_cache.jsonl for ML risk blend |
ml/h1_knowledge.py |
HackerOne public dataset parser |
ml/h1_parser.py |
HackerOne report JSON extractor |
ml/data_collector.py |
Data collection from DVWA and clean sites |
ml/ablation.py |
Feature ablation studies |
ml/validate.py |
Model validation and cross-validation |
ml/evidence_strength.py |
Evidence strength calculation engine |
ml/models/ |
Trained models (fp_classifier, risk_scorer, scaler) |
Run the built-in intercepting proxy to capture and analyze live browser traffic:
python proxy.pyThe proxy listens on 127.0.0.1:8080 and logs:
- All HTTP requests and responses
- Extracted parameters with risk classification (HIGH / MEDIUM)
- Output to
proxy_log.txtandproxy_params.txt
Configure your browser to use HTTP Proxy 127.0.0.1 on port 8080 for manual session integration.
Use sanitize_report.py to redact sensitive information from generated reports before sharing:
python sanitize_report.py --input scan_results/scan_report.html --output sanitized_report.htmlpdf_generator.py is a standalone, dedicated PDF generator — usable both by the
scanner (--pdf) and on its own against any scan_results.json:
# Themed report from scan data (minimalist theme, Google Stitch design system)
python3 pdf_generator.py scan_results.json -o report.pdf
# Classic mode: convert an existing HTML report as-is
python3 pdf_generator.py scan_report.html -o report.pdf
# Options: --backend auto|playwright|weasyprint --format A4|Letter --margin 0.4- Theme: strict minimalism (white background, Inter, hairline borders,
severity dots + stacked ratio bar) — designed in Google Stitch as the
"Obscure Minimal" design system (
assets/18132482118194346840) and implemented inreport_template_minimal.html. - Backends: Playwright/Chromium (highest fidelity) with automatic
WeasyPrint fallback (
pip install weasyprint) — no hard browser dependency.
A full desktop application (CustomTkinter) wraps the CLI — every scanner flag becomes an automatic UI control, styled after the Stitch "Obscure Minimal" design system (white, Inter, hairline cards, severity dots).
python3 main.py --gui- Startup screen — splash window with product art, progress bar, then the main window.
- Scan Configuration tab — Target URL / depth / delay / output dir;
Scan Intensity (
Auto — Full ScanorCustomwith per-module checkboxes: SQLi, SSRF, DOM-XSS, Subdomain, WAF evasion, Observation, Browser verification, LLM); Reports (themed PDF / classic HTML-as-PDF); Authentication (cookies, bearer token, headers); Run card with progress bar, Start/Stop, and a live log console. - Results tab — severity stat cards (CRITICAL/HIGH/MEDIUM/LOW), findings table (severity dot · title · endpoint · risk score) and a detail pane with insight ("x.x out of 10 risk"), CWE/OWASP/confidence, description, evidence and remediation — plus one-click Open PDF / HTML Report.
- The GUI runs the scanner as a child process (clean isolation), so Stop really stops it, and stdout streams live into the log console.
Ship the scanner (plus the PDF generator) as a self-contained binary with a bundled Python runtime:
# Linux — builds dist/webscan_<ver>_<arch>.deb (bundles Chromium for --pdf/--dom-xss)
bash build/build_linux_deb.sh
sudo dpkg -i dist/webscan_1.0.0_amd64.deb
webscan -u https://your-authorized-target --pdf
# Desktop app (Penligent-style): app-menu entry "Obscure", icon, GUI window
webscan-gui # or: launch "Obscure" from your application menu
# Windows (run on a Windows host) — builds dist/webscan.exe
powershell -ExecutionPolicy Bypass -File build\build_windows_exe.ps1
.\dist\webscan.exe -u https://your-authorized-target --pdf
.\dist\webscan.exe --gui # desktop GUISee build/README.md for apt-repo setup, code-signing,
and troubleshooting.
main.py (Entry Point / Phase Orchestrator)
│
├── Target Profiler → Technology fingerprinting (CMS, Backend, DB, WAF, JS Framework)
│
├── Threat Brain → 7-module intelligence engine (collector, mapper, advisory, payload)
│
├── Recon & Crawl → Subdomain enumeration + recursive page discovery
│
├── Form Intelligence → Input field schema parsing & surface mapping
│
├── Scanner Modules → 20+ vulnerability detection engines (SQLi, XSS, SSRF, CMDi, LFI…)
│
├── Verification → Playwright-based proof-of-concept execution
│
├── Security Graph → Attack path aggregation & chain reasoning
│
├── ML Engine → False positive classification + risk scoring
│
└── Report Generator → Jinja2 HTML dashboard, JSON, PDF, and plain-text outputs
obscure/
│
├── main.py # Core scanner entry point & phase orchestrator
├── requirements.txt # Python dependencies
├── sanitize_report.py # Report sanitization/redaction utility
├── proxy.py # Intercepting HTTP proxy logger
│
├── demo/ # Public Demo Showcase
│ ├── index.html # Interactive demo landing page
│ ├── assets/ # Mock screenshots & assets
│ └── reports/ # Sanitized/lab scan reports
│
├── scanners/ # Scanner cores & modules
│ ├── finding.py # Core Finding dataclass with CVSS scoring & compliance tags
│ ├── threat_brain.py # 7-stage Threat Intelligence Brain
│ ├── target_profiler.py # Technology fingerprinting & advisories
│ ├── attack_planner.py # Target-specific scan prioritization
│ ├── browser_verifier.py # Headless Playwright verification
│ ├── security_graph.py # Attack path aggregation
│ └── ... # 20+ scanner modules
│
├── ml/ # Machine learning engine
│ ├── features.py # 43-dimension feature engineering
│ ├── train.py # Classifier training (Random Forest + Gradient Boosting)
│ ├── predict.py # Inference pipeline
│ ├── data/ # Training datasets (DVWA, clean sites, HackerOne)
│ └── models/ # Trained models (pickle files)
│
├── ui/ # Desktop GUI (CustomTkinter)
│ ├── app.py # Main app: splash, Scan Configuration, Results tabs
│ ├── runner.py # ScanConfig → argv, child-process runner, result summarizer
│ └── assets/ # splash.png, icon-128/256/512.png
│
├── build/ # Packaging (`.deb` / `.exe`)
│ ├── build_linux_deb.sh # PyInstaller + dpkg-deb → dist/webscan_<ver>_amd64.deb
│ ├── build_windows_exe.ps1/.bat
│ └── README.md # apt-repo, signing, troubleshooting
│
├── templates/
│ └── report_template.html # Jinja2 HTML report template
│
├── scan_results/ # Default output directory
│ ├── scan_report.html # Visual pentest report
│ ├── scan_report.pdf # Print-ready PDF report
│ ├── scan_results.json # Machine-readable findings
│ └── priority_report.txt # Severity-sorted findings
│
└── README.md
Vulnerabilities are scored on a BusinessRiskScore scale of 0.0–10.0 and grouped into three confidence classes:
| Class | Description |
|---|---|
| VerifiedVulnerability | Actively exploited / verified findings (high severity, CONFIRMED confidence). |
| PotentialVulnerability | Inferred vulnerabilities lacking proof-of-concept validation (LIKELY confidence). |
| Observation | Technology fingerprints, exposures, and headers mapped to the target. |
The ML False Positive Classifier (Random Forest, 43 features) filters out noise, classifying findings as true_positive or false_positive to separate actionable findings from the appendix.
Evidence Strength — calculated from payload replay reliability — and ML Probability feed into the classification engine.
Priority URL Scoring — Each discovered URL receives a composite 0–100 score based on: critical keywords in path, secrets exposed, parameter reflection, risky HTTP methods, form presence, security header gaps, and JWT exposure.
To achieve high precision without compromising recall, Obscure employs an 8-layer evidence gating architecture:
- Pre-Scan Canary Baselining (
canary_baseline.py): Prior to firing attack payloads, sends unique random canaries with test characters (cb_xxxx<'\"&>) to isolate parameter reflection, encoding, and DOM location in a single pass. Parameters that do not reflect are immediately pruned. - Context-Aware AST & HTML Parsing (
reflection_context.py): Walks HTML DOM trees using HTML5 parser ASTs to classify reflection semantics (html_body,attr_q,script_str,comment,textarea,style). Reflections trapped in inert contexts (comments, textareas, styles) are suppressed from triggering XSS alarms. - Structural Diffing & Soft-404 / WAF Gating (
structural_diff.py): Strips text and attributes to compute DOM tag skeletons and 64-bit SimHash values. Rejects false alarms generated by single-page app soft-404 pages or WAF challenge/block templates. - Welch's t-Test v2 (
sqli_scanner.py,cmdi_scanner.py): Time-blind injection detection uses two-sample Welch's t-tests with Median Absolute Deviation (MAD) outlier filtering and baseline variance rejection ($\sigma > 1.5$s aborts time testing). - Strict MIME & CSP Gating (
csp_evaluator.py): Non-HTML MIME types (application/json,text/plain) served withnosniffcannot execute HTML payloads. Evaluates CSP directives (script-src,default-src, nonces,strict-dynamic) to downgrade mitigated reflections. - Dynamic Execution Sink Taint Analysis (
browser_verifier.py): Playwright instruments runtime JavaScript sinks (eval,Function,setTimeout,Element.prototype.innerHTML,document.write,location.assign) via_INSTRUMENT_JSbefore scripts load, confirming that user taint directly reached an active sink. - Dual-Probe Differential Controls (
response_differ.py): Strips dynamic nonces, session IDs, and timestamps before comparing responses with SequenceMatcher, eliminating false positive byte-length deltas in boolean SQLi. - Noise-Stripped Normalization & Calibrated ML Scoring (
ml/features.py): Replaced redundant feature dimensions with true structural similarity and verified error persistence checks across 43 features.
- CSRF simulation parity — Bypass detection expects consistent HTTP responses when tokens are omitted; custom client-side validations may require manual verification.
- Subdomain scope — Active reconnaissance treats out-of-scope targets as restricted unless explicitly authorized.
- Authenticated JS flows — Playwright session injection supports cookie/bearer persistence, but complex multi-factor authentication flows must be pre-authenticated.
- ML model freshness — The false positive classifier requires periodic retraining on new datasets to maintain accuracy.
Gudimella Shivprasad
- OWASP Top 10 Project
- OWASP Web Security Testing Guide (WSTG)
- PortSwigger Web Security Academy
- HackerOne Public Vulnerability Dataset
Built for learning, research, and authorised security assessments. Always obtain explicit authorization before testing remote hosts.
Optional post-scan analysis (--llm, also enabled by --full-scan) enhances CONFIRMED/LIKELY findings. Credentials come from .env (not CLI flags): LLM_API_KEY, LLM_BASE_URL, LLM_MODEL, LLM_USE_CLINE. If LLM_API_KEY is unset, Obscure uses a rule-based mock (score_source=llm_mock) — not a live model.
# Live or mock depending on .env
python3 main.py -u https://target.example --llm --sqli --browser-verify
# Mid-assessment pattern propose (separate from --llm analysis)
python3 main.py -u https://target.example --adaptive-learn --browser-verify
# → writes adaptive_learn.json; patterns stay PROPOSED until human promote| Flag / config | Behavior |
|---|---|
--llm |
Post-scan finding enhancement / scoring |
.env LLM_* |
Endpoint + key (mock if key missing) |
--adaptive-learn |
Propose declarative XSS patterns → PROPOSED only |
--adaptive-learn-trial |
Bounded TESTING trials for new proposals |
LLM/ML assist scoring and proposals; verification + evidence remain authoritative. Auto-ACTIVE is refused.
The LLM analysis runs after the scan completes. Enhanced findings may include prediction scores and narrative fields in llm_analysis.json. Pattern learning writes adaptive_learn.json under the scan output directory.
Compatible with any OpenAI-compatible endpoint configured via .env (defaults documented in CLAUDE.md).
An adaptive feedback loop that adapts during scanning while remaining strictly bounded and evidence-driven:
Discover → Observe → Decide next check → Test → Verify
↑ ↓
└──────────── Update scan state ───────┘
The agent proposes next permitted actions, the PolicyGate enforces strict bounds (scope, budget, rate limiting, and check allowlists), the DeterministicRunner dispatches approved checks using existing scanner modules, and the VerificationEngine confirms proof-of-concept evidence.
# Run with agentic adaptive loop (replaces static Phase 3 scanning)
python3 main.py -u https://target.example --agentic --agentic-budget 300 --browser-verifyEvery action, gate decision, observation, and verification result is logged to agentic_trace.json for reproducible analysis and offline benchmarking.