A fast, production-ready subdomain enumeration tool powered by Cloudflare's edge network. Discover subdomains from passive OSINT sources, resolve DNS records, and get results cached at the edge for instant repeat lookups.
Live: subdomainfinder.yashlunawat.com
Browser
│
├─ GET /api/scan/:domain ← instant (cache check)
│ │
│ ├─ X-Cache: HIT ──────────► Return results from KV immediately
│ ├─ X-Cache: STALE ──────────► Return old results + show refresh banner
│ └─ X-Cache: MISS ──────────► Open SSE stream →
│ Worker queries passive OSINT sources (parallel)
│ Streams each subdomain as found
│ Resolves DNS via 1.1.1.1 at CF edge
│ Writes results to KV
│ Sends { event: "complete" }
│
└─ POST /api/scan/:domain/refresh ← manual cache refresh
| Age | Status | Behavior |
|---|---|---|
| < 24 hours | HIT | Returned instantly, no scan |
| 24h – 7 days | STALE | Returned instantly + stale banner shown |
| > 7 days / never | MISS | Fresh SSE scan triggered |
| Product | Purpose |
|---|---|
| Workers | API server (Hono), OSINT queries, DNS resolution |
| KV | Scan result cache with 7-day TTL |
| D1 | User accounts and scan history (SQLite) |
| Pages | Frontend hosting |
Queues (background stale refresh) require the Workers Paid plan and can be enabled by adding the
[[queues]]bindings towrangler.toml.
Frontend
- React 18 + TypeScript
- Vite
- Tailwind CSS
- Zustand (state management)
- TanStack Query
- Lucide icons
Backend (Cloudflare Workers)
- Hono (lightweight web framework)
- Cloudflare KV (scan cache)
- Cloudflare D1 (SQLite database)
- DNS-over-HTTPS via
1.1.1.1(internal CF network)
OSINT Sources
- crt.sh — Certificate Transparency logs
- Cert Spotter — CT fallback with conservative free-tier use
- HackerTarget — DNS search API
- Wayback CDX — archived URL hostnames
- urlscan — optional enrichment source with small unauthenticated quotas
subdomain-finder/
├── src/ # React frontend
│ ├── pages/
│ │ └── Scanner.tsx # Main scan UI
│ ├── store/
│ │ └── scanStore.ts # Zustand store (two-phase: cache → SSE)
│ ├── lib/services/
│ │ └── scanApiService.ts # Typed API client + fetch-based SSE
│ └── components/
│ ├── scanner/
│ │ ├── ScanForm.tsx
│ │ ├── ScanProgress.tsx
│ │ ├── ScanControls.tsx
│ │ └── StaleCacheBanner.tsx # Shows age of cached results
│ └── results/
│ ├── ResultsTable.tsx
│ └── ExportButtons.tsx
│
└── api/ # Cloudflare Worker
├── wrangler.toml # Worker config (KV, D1 bindings)
├── schema.sql # D1 schema
└── src/
├── index.ts # Hono app + queue consumer export
├── types.ts # Env, KVCacheEntry, SSEEvent types
├── routes/
│ └── scanRoutes.ts # GET /:domain, GET /:domain/stream, POST /:domain/refresh
└── services/
├── cacheService.ts # KV read/write, HIT/STALE/MISS logic
├── osintService.ts # Passive source integrations (server-side, no CORS proxy)
├── dnsWorker.ts # DNS resolution via 1.1.1.1 at CF edge
└── scanRunner.ts # Shared executor (SSE + Queue consumer)
Prerequisites: Node.js 18+, a Cloudflare account with wrangler authenticated.
# 1. Install dependencies
npm install
cd api && npm install && cd ..
# 2. Start the Worker (port 8787)
cd api && npx wrangler dev
# 3. In a separate terminal, start the frontend (port 5173)
npm run devThe frontend reads VITE_API_BASE_URL from .env. Copy .env.example:
cp .env.example .env
# Local: VITE_API_BASE_URL=http://localhost:8787
# Production: VITE_API_BASE_URL=https://subdomain-finder-api.yashlunawat-tech.workers.devLocal wrangler dev simulates KV and D1 in .wrangler/state/ — no cloud resources needed for local development.
Production note:
subdomainfinder.yashlunawat.com/api/*is not currently the canonical API path. Build the frontend with the Worker URL above unless Cloudflare Pages is configured to proxy/api/*to the Worker.
cd api
# Create KV namespace
npx wrangler kv:namespace create SCAN_CACHE
npx wrangler kv:namespace create SCAN_CACHE --preview
# Paste the returned IDs into api/wrangler.toml
# Create D1 database
npx wrangler d1 create subdomain-finder-db
# Paste the returned ID into api/wrangler.toml
# Run schema migration
npx wrangler d1 execute subdomain-finder-db --remote --file=./schema.sqlcd api && npx wrangler deployVITE_API_BASE_URL=https://<your-worker>.workers.dev npx vite build
CLOUDFLARE_ACCOUNT_ID=<your-account-id> npx wrangler pages deploy dist --project-name subdomain-finderReturns cached scan results instantly.
Response headers:
X-Cache: HIT | STALE | MISSX-Cache-Age: <seconds>
Response body:
{
"results": [
{
"subdomain": "api.example.com",
"ipAddresses": ["1.2.3.4"],
"source": "crtsh",
"resolved": true,
"discoveredAt": 1714000000000
}
],
"meta": {
"fetchedAt": 1714000000000,
"status": "HIT"
}
}Server-Sent Events stream of a live scan.
Query params: sources=crtsh,certspotter,hackertarget,wayback,urlscan · resolveDns=true|false · concurrency=1..50 · timeout=5..30
SSE events:
data: {"event":"progress","message":"Querying crt.sh...","percent":10}
data: {"event":"source","source":"crtsh","status":"running","message":"Querying crt.sh..."}
data: {"event":"subdomain","subdomain":"api.example.com","ipAddresses":[],"source":"crtsh","resolved":false}
data: {"event":"subdomain","subdomain":"api.example.com","ipAddresses":["1.2.3.4"],"source":"crtsh","resolved":true}
data: {"event":"complete","total":42,"resolved":38,"cachedAt":1714000000000,"status":"partial","sources":[]}
Manually triggers a fresh scan. Returns 202 when Queues are not configured (free plan); the frontend opens an SSE stream in that case.
- Instant repeat lookups — KV cache means second visits to any domain are sub-100ms
- Live streaming — results appear as each OSINT source responds, not after everything finishes
- DNS resolution at the edge — resolved via
1.1.1.1on Cloudflare's internal network - Source-aware reliability — upstream failures are shown as partial/failed scans and are not cached as empty success
- No CORS proxies — all external API calls happen server-side in the Worker
- Stale-while-revalidate — old results shown immediately with a banner showing cache age
- Pause / Resume / Stop — AbortController on the SSE connection; Worker continues scanning and writes to KV
- Export — JSON, CSV, plain-text, or copy to clipboard
- URL sharing —
/scan/:domainauto-starts a scan on load
- Position the project as a fast passive subdomain discovery tool for bug bounty hunters, students, and developers who need exportable recon without signup.
- Launch and cross-post on GitHub, X/Twitter, LinkedIn, Reddit communities such as r/bugbounty and r/cybersecurity, Indie Hackers, Hacker News “Show HN,” Product Hunt, and security Discord/Telegram groups.
- Create focused SEO pages and examples for “subdomain finder,” “crt.sh alternative,” “passive subdomain enumeration,” “find subdomains online,” and “bug bounty recon checklist.”
- Add trust signals: open-source implementation notes, source transparency, uptime/status notes, changelog entries, and the authorized-use disclaimer below.
- Track impact with privacy-conscious analytics: scans started, successful scans, median time to first result, source failure rates, exports, shared scan URLs, and top landing queries.
This tool is provided for authorized security testing, research, and educational purposes only. Always obtain explicit permission before scanning domains you do not own. The authors are not responsible for any misuse.