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.
⚖️ 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.mdandACCEPTABLE_USE.md. You are responsible for having authorization for every target you scan.
- Why ReconForge
- Project status
- Architecture
- The recon pipeline
- The Scope & Authorization Engine
- Install & build
- Usage
- Scope file format
- Project layout
- Testing & the zero-false-allow gate
- Roadmap
- Security · Contributing · License
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.
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.
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;
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
This is the safety net that decides whether the platform may touch an asset — and the one component the whole product depends on.
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;
| 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. |
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 reportInstall the CLI directly:
go install github.com/Zuhef/ReconForge/cmd/scopecheck@latestscopecheck 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.10Example 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.
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
}
}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
go test ./internal/... # unit tests + gate (+coverage)
go test -run TestGate -v ./internal/scope # the zero-false-allow gate onlyThe 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%.
| 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.
Please report vulnerabilities privately — see SECURITY.md. Scope and
authorization bypasses are treated as Critical. The scope-engine false-allow error
budget is zero.
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.
AGPL-3.0 — pure open source, community-sustained. By contributing you agree your contributions are licensed under AGPL-3.0.