Skip to content
ReconForge

ReconForge

A self-hosted reconnaissance automation platform for authorized security research. Bring your own API keys and your own tools — ReconForge orchestrates the full recon pipeline behind a polished UI, with authorization & scope enforcement built into the core.

CI License: AGPL v3 Go Status false-allow coverage


⚖️ Authorized use only. ReconForge is built for bug-bounty hunters, contracted penetration testers, and asset owners operating within an authorized scope. It is designed to make authorized recon effortless and unauthorized recon hard. It is not "abuse-proof" — see SECURITY.md and ACCEPTABLE_USE.md. You are responsible for having authorization for every target you scan.

Table of contents

Why ReconForge

Best-of-breed recon capability already exists as open-source CLI tools (ProjectDiscovery's subfinder/httpx/naabu/nuclei, OWASP Amass, BBOT). The gap is a secure-by-design, self-hosted platform that orchestrates them without surrendering your data or API keys to a SaaS — and that treats authorization as architecture, not as an afterthought.

The incumbent self-hosted recon UI proved the demand but shipped repeated broken-access-control vulnerabilities. ReconForge's thesis is that the authorization/scope engine is the hardest and most important part — so we build and prove it first.

What makes ReconForge different:

  • 🛡️ Authorization-first. A safety-critical Scope Engine decides — for every asset — in-scope vs out-of-scope vs hard-blocked, and whether active probing is allowed.
  • 🧩 Bring your own keys & tools. Your API keys are injected only at runtime, never persisted in plaintext. Tools run as sandboxed, declarative adapters.
  • 🏠 Self-hosted. Your recon data and credentials stay on your infrastructure.
  • 🔓 Pure open source (AGPL-3.0), community-sustained. No SaaS, no telemetry.

Project status

Stage 0 — pre-alpha. This release ships and proves the safety-critical core (the Scope & Authorization Engine) and the scopecheck CLI, before any orchestration or UI is built. The engine clears a hard gate: zero false-allow on an adversarial corpus and 100% hard-block coverage. See docs/STAGE0_GATE_REPORT.md.

Design docs: docs/STRATEGY_BRIEF.md · docs/BUILD_PLAN.md.

Architecture

The target architecture. Implemented today: the Scope & Authorization Engine and the CLI. Everything else is on the roadmap and is introduced behind interfaces, incrementally.

flowchart LR
    UI["Web UI / CLI"] --> GW["API Gateway<br/>authn · authz · rate-limit · audit"]
    GW --> SE["Scope & Authorization Engine<br/>✅ implemented (Stage 0)"]
    GW --> CP["Control Plane<br/>scan lifecycle"]
    CP --> WF["Workflow engine<br/>durable, resumable DAG"]
    WF --> WK["Workers<br/>sandboxed tool exec · egress policy"]
    WK --> TOOLS[("BYO tools<br/>subfinder · httpx · naabu · nuclei …")]
    SE -. "every target re-checked<br/>before any active probe" .-> WK
    CP --> DB[("PostgreSQL<br/>assets · findings · audit")]
    WK --> OBJ[("Object store<br/>raw output · screenshots")]
    WK --> SEC[("Secrets vault<br/>BYO API keys")]

    classDef done fill:#0e7490,stroke:#22d3ee,color:#fff;
    class SE done;
Loading

The recon pipeline

Modeled as a DAG of stages that emit and consume typed events (asset types), which enables automatic wiring, deduplication, and recursion. Each stage/tool carries an intrusiveness tier (PASSIVE → LIGHT-ACTIVE → ACTIVE → INTRUSIVE) gated by the Scope Engine. Newly discovered assets re-enter the pipeline passive-only until promoted.

flowchart LR
    S["Scope / seed"] --> SUB["Subdomain enum<br/>passive + active"]
    SUB --> DNS["DNS resolution"]
    DNS --> PORT["Port / service scan"]
    PORT --> WEB["Web probe / fingerprint"]
    WEB --> CRAWL["Crawl / content discovery"]
    CRAWL --> SHOT["Screenshot"]
    WEB --> NUC["Vuln templates (nuclei)"]
    CRAWL --> SECR["Secret / leak discovery"]
    NUC --> DEDUP["Dedup / correlate"]
    SECR --> DEDUP
    SHOT --> DEDUP
    DEDUP --> REP["Report / export"]
    SUB -. "discovered assets re-enter<br/>(passive-only until promoted)" .-> DNS
Loading

The Scope & Authorization Engine

This is the safety net that decides whether the platform may touch an asset — and the one component the whole product depends on.

Decision flow (implemented)

flowchart TD
    A(["Asset + provenance"]) --> N["Normalize<br/>URL→host · lowercase · trim dot<br/>detect obfuscated IP"]
    N -->|unparseable| OOS1["OUT_OF_SCOPE<br/>(fail-closed)"]
    N --> HB{"Hard-blocked?<br/>RFC1918 · loopback · link-local<br/>metadata 169.254.169.254<br/>CGNAT · NAT64 · obfuscated IP"}
    HB -->|yes| BLK["BLOCKED<br/>(overrides an explicit allow)"]
    HB -->|no| DENY{"Matches an exclusion?"}
    DENY -->|yes| OOS2["OUT_OF_SCOPE<br/>(excluded)"]
    DENY -->|no| ALLOW{"Matches an allow rule?"}
    ALLOW -->|no| OOS3["OUT_OF_SCOPE<br/>(default-deny)"]
    ALLOW -->|yes| PROV{"Seed or discovered?"}
    PROV -->|seed| ACT["IN_SCOPE<br/>active probing allowed"]
    PROV -->|discovered| PASS["IN_SCOPE<br/>passive-only until promoted"]

    classDef block fill:#7f1d1d,stroke:#ef4444,color:#fff;
    classDef ok fill:#14532d,stroke:#22c55e,color:#fff;
    classDef warn fill:#78350f,stroke:#f59e0b,color:#fff;
    class BLK block;
    class ACT ok;
    class PASS,OOS1,OOS2,OOS3 warn;
Loading

Design invariants

Invariant Guarantee
Default-deny Anything not explicitly in an allow rule is out of scope.
Strict precedence hard-block > exclusion > allow > default-deny.
Zero false-allow Nothing out-of-scope or blocked is ever returned as in-scope and active-probing-allowed. Enforced by an adversarial corpus test (TestGate).
Discovered = passive Assets found via recursion are passive-only until manually promoted.
Anti-SSRF safety net RFC1918, loopback, link-local, cloud metadata (169.254.169.254), CGNAT, NAT64, IPv6 ULA, and obfuscated IP encodings are hard-blocked even if explicitly allowed. Metadata is never relaxed, even in self-host mode.
Evasion-resistant Hostnames are normalized (case, trailing dot, URL/userinfo host extraction, IPv4-mapped IPv6) before matching; wildcard matching is suffix-safe; IDN homographs fail closed.
No overly-broad scope *.com, bare *, 0.0.0.0/0, and CIDRs broader than the configured minimum are rejected at construction.

Install & build

Requires Go 1.24+. The core is dependency-free (standard library only).

git clone https://github.com/Zuhef/ReconForge.git
cd ReconForge

make build      # build the scopecheck CLI
make test       # unit tests + the adversarial gate
make gate       # just the zero-false-allow gate
make cover      # coverage report

Install the CLI directly:

go install github.com/Zuhef/ReconForge/cmd/scopecheck@latest

Usage

scopecheck classifies one or more assets against a scope, from flags or a file.

# inline scope
scopecheck -allow "*.example.com,93.184.216.0/24" -exclude "blog.example.com" \
    app.example.com blog.example.com 169.254.169.254

# from a scope file
scopecheck -scope-file examples/scope.example.json app.example.com

# treat inputs as discovered (recursed) assets — passive-only by default
scopecheck -allow "*.example.com" -discovered new.example.com

# scan your own lab (permits RFC1918/loopback; never relaxes metadata blocks)
scopecheck -allow "192.168.1.0/24" -self-host 192.168.1.10

Example output:

ASSET                            DECISION
----------------------------------------------------------------------------------------------------
app.example.com                  IN_SCOPE     active-allowed kind=hostname rule=*.example.com    in scope (allow rule)
blog.example.com                 OUT_OF_SCOPE passive-only   kind=hostname rule=blog.example.com explicitly excluded by deny rule
169.254.169.254                  BLOCKED      passive-only   kind=ip       rule=-                link-local / cloud metadata (169.254.0.0/16)

Flags: -allow, -exclude, -scope-file, -self-host, -discovered, -allow-active-discovered, -min-v4, -min-v6. Exit codes: 0 all in-scope · 1 some out-of-scope · 2 some hard-blocked.

Scope file format

A scope is described once as JSON (unknown fields are rejected — a typo in a security-critical config must fail loudly). See examples/scope.example.json.

{
  "allow": ["*.example.com", "example.org", "93.184.216.0/24"],
  "deny":  ["blog.example.com", "*.internal.example.com"],
  "options": {
    "allow_active_probing_on_discovered": false,
    "allow_private_for_self_host": false,
    "min_ipv4_prefix": 16,
    "min_ipv6_prefix": 32
  }
}

Project layout

internal/scope/      # the engine (stdlib-only)
  scope.go           #   types, rules, Scope construction + validation
  normalize.go       #   evasion-resistant normalization
  match.go           #   domain/wildcard/CIDR matching + public-suffix guard
  hardblock.go       #   anti-SSRF / metadata / private-range safety net
  classify.go        #   the Classifier (precedence + default-deny)
  scopefile.go       #   JSON scope-file loader
  *_test.go          #   unit tests + the adversarial corpus / gate
cmd/scopecheck/      # CLI to classify an asset against a scope
examples/            # example scope file
docs/                # strategy brief, build plan, Stage 0 gate report
assets/              # logo

Testing & the zero-false-allow gate

go test ./internal/...                        # unit tests + gate (+coverage)
go test -run TestGate -v ./internal/scope     # the zero-false-allow gate only

The gate asserts two existential invariants across a 49-case adversarial corpus (suffix-confusion, metadata/SSRF, obfuscated IPs, exclusions, normalization evasions, discovered-asset handling): zero false-allow and 100% hard-block coverage. Any regression that introduces a false-allow is a release blocker. Current scope-engine coverage: 89.9%.

Roadmap

Phase Focus Status
Stage 0 Scope & Authorization Engine + CLI + adversarial gate ✅ done
P0 Foundations: CI security gates, auth + TLS, Postgres RLS, audit log 🔜 next
P1 Ownership-proof + bug-bounty scope import; grow the adversarial corpus planned
P2 DAG orchestrator + worker sandbox + DNS-aware egress enforcement planned
P3 BYO-key vault + curated tool adapters + web UI planned
P4 Full 12-stage pipeline, continuous monitoring, third-party pentest planned
P5 Multi-tenant, distributed scan fleets, adapter marketplace later

See docs/BUILD_PLAN.md for the full plan and the open decisions.

Security

Please report vulnerabilities privately — see SECURITY.md. Scope and authorization bypasses are treated as Critical. The scope-engine false-allow error budget is zero.

Contributing

Contributions welcome! See CONTRIBUTING.md and CODE_OF_CONDUCT.md. The golden rule: any change to internal/scope must keep TestGate green, and new evasions must be added to the corpus in the same PR.

License

AGPL-3.0 — pure open source, community-sustained. By contributing you agree your contributions are licensed under AGPL-3.0.


Built for authorized security research. Use responsibly — see ACCEPTABLE_USE.md.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages