Web2 recon pipeline — subdomain enumeration (subfinder, Chaos API, assetfinder), live host discovery (dnsx, httpx), URL crawling (katana, waybackurls, gau), directory fuzzing (ffuf), JS analysis (LinkFinder, SecretFinder), continuous monitoring (new subdomain alerts, JS change detection, GitHub commit watch). Use when starting recon on any web2 target or when asked about asset discovery, subdomain enum, or attack surface mapping.
Resources
1Install
npx skillscat add sekolah76/syadagentic/web2-recon Install via the SkillsCat registry.
WEB2 RECON PIPELINE
Full asset discovery from nothing to a prioritized URL list ready for hunting.
SETUP (one-time)
# 1. Set your Chaos API key (get free key at chaos.projectdiscovery.io)
export CHAOS_API_KEY="your-key-here"
# Add to ~/.zshrc or ~/.bashrc for persistence:
echo 'export CHAOS_API_KEY="your-key-here"' >> ~/.zshrc
# 2. Update nuclei templates (run weekly)
nuclei -update-templates
# 3. Configure subfinder with API keys for more sources
mkdir -p ~/.config/subfinder
cat > ~/.config/subfinder/config.yaml << 'EOF'
# Get free keys at: virustotal.com, securitytrails.com, censys.io, shodan.io
virustotal: [YOUR_VT_KEY]
securitytrails: [YOUR_ST_KEY]
censys_apiid: YOUR_CENSYS_ID
censys_secret: YOUR_CENSYS_SECRET
shodan: [YOUR_SHODAN_KEY]
EOF
# 4. Verify all tools installed
which subfinder httpx dnsx nuclei katana waybackurls gau dalfox ffuf anew gf interactsh-clientTHE 5-MINUTE RULE
If a target shows nothing interesting after 5 minutes of recon, move on. Don't burn hours on dead surface.
5-minute kill signals:
- All subdomains return 403 or static marketing pages
- No API endpoints visible in URLs
- No JavaScript bundles with interesting endpoint paths
- nuclei returns 0 medium/high findings
- No forms, no authentication, no user data
MANUAL-ONLY PROGRAM OVERRIDE
Before running this pipeline, inspect the live bounty policy for automation restrictions. If automated tools, scripts, scanners, public URL indexes, or scanner-generated findings are prohibited:
- Do not run the standard recon pipeline, bulk CT harvesting,
httpx,katana,gau,waybackurls,ffuf,nuclei, scripted multi-host probes, or scanner wrappers against that program. - Create
SCOPE_FENCE.mdfirst, recording the exact prohibition and explicit third-party host exclusions. - Use a normal browser or intercepting proxy manually. Follow links and inspect requests generated by one researcher-controlled session.
- Treat public certificate-transparency and archived-URL discoveries as prohibited too when the policy excludes search-engine/index/archive findings—not merely as low-confidence leads.
- If prior automation was accidentally used before the policy was fully parsed, quarantine its outputs: do not test, report, or use those discoveries. Restart from manually reached first-party pages.
- Honor operator-selected exclusions independently of program scope. Example: if the user says to skip native apps, remove Windows/macOS/Linux/iOS/Android workstreams immediately rather than keeping them as pending tasks.
This override outranks every command below.
STANDARD RECON PIPELINE
Pre-Hunt: Always Run First
TARGET="target.com"
# Step 0: Passive — crt.sh certificate transparency (no API key needed)
# NOTE: crt.sh is flaky (502s/timeouts) and large domains truncate JSON. Retry with a UA,
# and use regex fallback if json.load fails. See references/passive_ct_dns_brute_curl_fingerprint.md
curl -s "https://crt.sh/?q=%.${TARGET}&output=json" \
| jq -r '.[].name_value' \
| sed 's/\*\.//g' \
| sort -u > /tmp/subs.txt
echo "[+] crt.sh: $(wc -l < /tmp/subs.txt) subdomains"
# Step 1: Chaos API (ProjectDiscovery — most comprehensive source)
curl -s "https://dns.projectdiscovery.io/dns/$TARGET/subdomains" \
-H "Authorization: $CHAOS_API_KEY" \
| jq -r '.[]' >> /tmp/subs.txt
echo "[+] Chaos returned $(wc -l < /tmp/subs.txt) subdomains"
# Step 2: subfinder (passive multi-source)
subfinder -d $TARGET -silent | anew /tmp/subs.txt
assetfinder --subs-only $TARGET | anew /tmp/subs.txt
echo "[+] Total subdomains after all sources: $(wc -l < /tmp/subs.txt)"
# Step 3: DNS resolution + live host check
cat /tmp/subs.txt | dnsx -silent | httpx -silent -status-code -title -tech-detect | tee /tmp/live.txt
echo "[+] Live hosts: $(wc -l < /tmp/live.txt)"
# Step 4: URL crawl
cat /tmp/live.txt | awk '{print $1}' | katana -d 3 -jc -kf all -silent | anew /tmp/urls.txt
# Step 5: Historical URLs
echo $TARGET | waybackurls | anew /tmp/urls.txt
gau $TARGET --subs | anew /tmp/urls.txt
echo "[+] Total URLs: $(wc -l < /tmp/urls.txt)"
# Step 6: Nuclei scan
nuclei -l /tmp/live.txt -t ~/nuclei-templates/ -severity critical,high,medium -o /tmp/nuclei.txtOutput to Organized Directory
TARGET="target.com"
RECON_DIR="recon/$TARGET"
mkdir -p $RECON_DIR
# All outputs go here:
/tmp/subs.txt → $RECON_DIR/subdomains.txt
/tmp/live.txt → $RECON_DIR/live-hosts.txt
/tmp/urls.txt → $RECON_DIR/urls.txt
/tmp/nuclei.txt → $RECON_DIR/nuclei.txtATTACK SURFACE TRIAGE
robots.txt → API Surface Discovery (HIGH SIGNAL)
Before any URL crawling, check robots.txt. It often reveals the entire authenticated API surface that search engines are told to skip — these are the most interesting endpoints.
curl -s "https://$TARGET/robots.txt"
# Look for Disallow entries — they reveal hidden API paths:
# Disallow: /webapi/ → API surface
# Disallow: /service_api/ → Service API
# Disallow: /my-servers → Authenticated user pages
# Disallow: /my-orders
# Disallow: /transactions
# Disallow: /balance
# Disallow: /api-keys
# Disallow: /ssh-keys
# Disallow: /profile
# Disallow: /2fa → 2FA endpoints
# Disallow: /hive-login → Special auth flows
# Disallow: /docker-auth → Infrastructure auth
# Real example (Clore.ai): robots.txt exposed 25+ API/auth endpoints
# that were NOT discoverable via crawling (auth-gated)
# After finding targets in robots.txt, probe for API access:
for path in webapi service_api my-servers my-orders balance transactions profile; do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://$TARGET/${path}" -H "Accept: application/json")
echo "/${path}: HTTP ${code}"
done
# If path returns SPA HTML, try POST with JSON body:
curl -s -X POST "https://$TARGET/marketplace/servers" \
-H "Content-Type: application/json" \
-d '{}' -H "Accept: application/json"
# Also probe api.$TARGET subdomain for REST endpoints:
for sub in api dev staging admin mail cdn vpn; do
curl -s -o /dev/null -w "%{http_code}" "https://${sub}.$TARGET/" --connect-timeout 5
echo " ${sub}.$TARGET"
doneFind Interesting Targets in URL List
# Parameters worth testing
cat /tmp/urls.txt | grep -E "[?&](id|user|file|path|url|redirect|next|src|token|key|api_key)=" | tee /tmp/interesting-params.txt
# API endpoints
cat /tmp/urls.txt | grep -E "/api/|/v1/|/v2/|/v3/|/graphql|/rest/|/gql" | tee /tmp/api-endpoints.txt
# File upload endpoints
cat /tmp/urls.txt | grep -E "upload|file|attachment|document|image|avatar|photo|media" | tee /tmp/uploads.txt
# Admin/internal paths
cat /tmp/urls.txt | grep -E "/admin|/internal|/debug|/test|/staging|/dev|/management|/console" | tee /tmp/admin-paths.txt
# Authentication endpoints
cat /tmp/urls.txt | grep -E "/oauth|/login|/auth|/sso|/saml|/oidc|/callback|/token" | tee /tmp/auth-paths.txtgf Patterns (Quick Classification)
# Install gf patterns: https://github.com/tomnomnom/gf
cat /tmp/urls.txt | gf xss | tee /tmp/xss-candidates.txt
cat /tmp/urls.txt | gf ssrf | tee /tmp/ssrf-candidates.txt
cat /tmp/urls.txt | gf idor | tee /tmp/idor-candidates.txt
cat /tmp/urls.txt | gf sqli | tee /tmp/sqli-candidates.txt
cat /tmp/urls.txt | gf redirect | tee /tmp/redirect-candidates.txt
cat /tmp/urls.txt | gf lfi | tee /tmp/lfi-candidates.txt
cat /tmp/urls.txt | gf rce | tee /tmp/rce-candidates.txt
# User-controlled CSS surface (themes, profile pages, HTML email renderers,
# rich-text editors, PDF generators). gf has no pattern for this — grep manually:
cat /tmp/urls.txt | grep -iE "theme|profile|signature|customize|email|invoice|pdf|render|markdown" \
| tee /tmp/css-injection-candidates.txt
# → if any hit, run web2-vuln-classes **CSS Injection**SECRET SCANNING IN JS BUNDLES
Next.js deep-dive: For full extraction of Privy App IDs, WalletConnect project IDs, Thirdweb RPC URLs, QuikNode API keys, API base URLs, and auth flow reconstruction from webpack chunks, see
references/nextjs_bundle_config_extraction.md.
Vue.js SPA deep-dive: For VUE_APP_* env var extraction, API route map from constants, RPC proxy abuse testing, SIWE auth patterns, and S3 upload pre-signed URL discovery, see
references/vuejs_spa_bundle_extraction.md.
SecretFinder (API keys, tokens in JS bundles)
# Activate venv
source ~/tools/SecretFinder/.venv/bin/activate
# Scan a single JS file
python3 ~/tools/SecretFinder/SecretFinder.py -i "https://target.com/static/js/main.js" -o cli
# Scan all JS URLs found in recon
cat /tmp/urls.txt | grep "\.js$" | head -50 | while read url; do
echo "=== $url ==="
python3 ~/tools/SecretFinder/SecretFinder.py -i "$url" -o cli 2>/dev/null
done
deactivateLinkFinder (Endpoints hidden in JS)
source ~/tools/LinkFinder/.venv/bin/activate
# Single JS file
python3 ~/tools/LinkFinder/linkfinder.py -i "https://target.com/app.js" -o cli
# All pages (crawls JS from HTML)
python3 ~/tools/LinkFinder/linkfinder.py -i "https://target.com" -d -o cli
deactivateDIRECTORY FUZZING
ffuf — Standard Fuzzing
# Directory discovery on a live host
ffuf -u "https://target.com/FUZZ" \
-w ~/wordlists/common.txt \
-mc 200,201,204,301,302,307,401,403 \
-ac \
-t 40 \
-o /tmp/ffuf-dirs.json
# API endpoint discovery
ffuf -u "https://target.com/api/FUZZ" \
-w ~/wordlists/api-endpoints.txt \
-mc 200,201,204,301,302 \
-ac \
-t 20
# IDOR fuzzing with authenticated request
# Create req.txt with Authorization: Bearer TOKEN
ffuf -request /tmp/req.txt \
-request-proto https \
-w <(seq 1 10000) \
-fc 404 \
-ac \
-t 10TARGET SCORING — GO / NO-GO
Score before spending time. Skip if score < 4.
| Criterion | Points |
|---|---|
| Max bounty >= $5K | +2 |
| Large user base (>100K) or handles money | +2 |
| Program launched < 60 days ago | +2 |
| Complex features: API, OAuth, file upload, GraphQL | +1 |
| Recent code/feature changes (GitHub, changelog) | +1 |
| Private program (less competition) | +1 |
| Tech stack you know | +1 |
| Source code available | +1 |
| Prior disclosed reports to study | +1 |
< 4: Skip
4-5: Only if nothing better available
6-8: Good — spend 1-3 days
>= 9: Excellent — spend up to 1 week
Pre-Dive Hard Kill Signals
- Max bounty < $500 → not worth your time
- All recent reports are N/A or duplicate → hunters saturated it
- Scope is only a static marketing page → no attack surface
- Company < 5 employees with no revenue → won't pay
- Explicitly excludes your planned bug class in rules
TECH STACK DETECTION (2 min)
# Response headers reveal backend
curl -sI https://target.com | grep -iE "server|x-powered-by|x-aspnet|x-runtime|x-generator"
# Common signals:
# Server: nginx + X-Powered-By: PHP/7.4 → PHP backend
# Server: gunicorn OR X-Powered-By: Express → Python/Node.js
# X-Powered-By: ASP.NET → .NET
# Server: Apache Tomcat → Java
# X-Runtime: Ruby → Ruby on Rails
# X-Powered-By: Sails <sailsjs.com> → Sails.js (Node.js + Vue)
# Framework from JS bundle paths:
# /_next/static/ → Next.js
# /_next/static/chunks/ → Next.js (100+ webpack chunks — each may contain config fragments)
# → extract ALL chunk URLs from <script src=...> and RSC self.__next_f payload
# → see references/nextjs_bundle_config_extraction.md for full deep-dive
# /static/js/main.chunk.js → CRA (React)
# /packs/ → Ruby on Rails + Webpacker
# /__nuxt/ → Nuxt.js (Vue)
# --- Sails.js specific signals (see references/sailsjs_recon_patterns.md) ---
# After detecting X-Powered-By: Sails, probe these immediately:
# 1. Check SAILS_LOCALS._environment in HTML source for dev mode
# curl -s https://target.com | grep -oP 'SAILS_LOCALS.*?};'
# 2. Check /api/health for info disclosure
# curl -s https://target/api/health
# 3. Check socket.io endpoint
# curl -s https://target/socket.io/?EIO=4\&transport=polling
# 4. Check docker IP leak in X-Server-IP header
# curl -sI https://target.com | grep X-Server-IPStack → Primary Bug Class Map
| Stack | Hunt First | Hunt Second |
|---|---|---|
| Ruby on Rails | Mass assignment | IDOR (:id routes) |
| Django | IDOR (ModelViewSet, no object perms) | SSTI (mark_safe) |
| Flask | SSTI (render_template_string) | SSRF (requests lib) |
| Laravel | Mass assignment ($fillable) | IDOR (Eloquent, no ownership) |
| Express (Node.js) | Prototype pollution | Path traversal + debug surface (/_debug, /__debug__) → web2-vuln-classes "Error Disclosure / Debug Endpoints" |
| Spring Boot | Actuator endpoints → web2-vuln-classes "Error Disclosure / Debug Endpoints" for full surface | SSTI (Thymeleaf) |
| ASP.NET | ViewState deserialization | Open redirect (ReturnUrl) |
| Next.js | SSRF via Server Actions + /_next/data/ / /_next/static/chunks/ → web2-vuln-classes "Error Disclosure / Debug Endpoints" |
Open redirect via redirect() |
| GraphQL | Introspection → auth bypass on mutations | IDOR via node(id:) |
| WordPress | Plugin SQLi | REST API auth bypass |
| SPA frameworks (React / Vue / Svelte / Angular) | DOM XSS sinks via state/router → web2-vuln-classes section 3 "postMessage Testing" for cross-frame entry points | Client-side route auth bypass (role check only in JS) |
JS-Rendered Targets / Anti-Bot Crawling → crawl4ai-recon
For sites that block katana/hakrawler with Cloudflare, PerimeterX, DataDome, or heavy client-side rendering that katana cannot execute (React/Vue SPAs, webflow-style sites, login-walled pages), use the crawl4ai-recon skill. Crawl4AI spawns full Chromium with stealth heuristics — handles JS rendering, anti-bot bypass, structured (markdown/JSON) extraction.
When to pivot from katana to Crawl4AI:
- Katana returns mostly empty URL list on a known SPA target
- Target has Cloudflare challenge page on every request
- Manual Burp crawl needed — Crawl4AI automates the headless browser bit
- You want LLM-friendly output (markdown) to feed into agent for endpoint classification
Pipeline integration:
# Standard: katana first (cheap), then crawl4ai for JS-heavy / blocked targets
cat /tmp/live.txt | awk '{print $1}' | katana -d 3 -silent > /tmp/urls.txt
# If low yield (< 50 URLs), or target is known SPA / Cloudflare'd:
while read host; do
crawl4ai-cli "https://$host" --depth 2 -o json -f "./crawl/${host//\//_}.json"
done < /tmp/live.txtSee crawl4ai-recon skill for install, anti-bot config, Docker server hardening (11 published GHSAs), and known CVE patterns to AVOID in your own deployment.
| Sails.js (Node.js + Vue) | Info disclosure via /api/health (_environment, memory, DB/Redis status) + dev mode surface (blueprint API, socket.io) → references/sailsjs_recon_patterns.md | Docker IP leak (X-Server-IP), user enumeration via login error messages, CORS wildcard |
| Express / NestJS | Swagger/OpenAPI docs (/docs, /docs/json, /api-docs) → references/express_nestjs_api_probe.md | Info disclosure via root endpoint ({"environment","mode","version"}), token-in-body auth (not header), sub-app structure (/bot-api, /poh-api, /webapi)
CONTINUOUS MONITORING SETUP
Set up once per target. Alerts you before other hunters.
New Subdomain Alerts (daily cron)
#!/bin/bash
TARGET="target.com"
KNOWN="/tmp/$TARGET-subs-known.txt"
subfinder -d $TARGET -silent > /tmp/$TARGET-subs-fresh.txt
curl -s "https://dns.projectdiscovery.io/dns/$TARGET/subdomains" \
-H "Authorization: $CHAOS_API_KEY" \
| jq -r '.[]' >> /tmp/$TARGET-subs-fresh.txt
# Diff against known
NEW=$(comm -23 <(sort /tmp/$TARGET-subs-fresh.txt) <(sort $KNOWN 2>/dev/null))
if [ -n "$NEW" ]; then
echo "NEW SUBDOMAINS: $NEW"
echo "$NEW" >> $KNOWN
fi
# Schedule: crontab -e → 0 8 * * * /bin/bash ~/monitors/subs-watch.shGitHub Commit Watch
#!/bin/bash
REPO="TargetOrg/target-app"
LAST_SHA="/tmp/$REPO-last-sha.txt"
CURRENT=$(curl -s "https://api.github.com/repos/$REPO/commits?per_page=1" | jq -r '.[0].sha')
KNOWN=$(cat $LAST_SHA 2>/dev/null)
if [ "$CURRENT" != "$KNOWN" ]; then
echo "New commit on $REPO: $CURRENT"
echo $CURRENT > $LAST_SHA
# Get changed files
curl -s "https://api.github.com/repos/$REPO/commits/$CURRENT" \
| jq -r '.files[].filename' | grep -E "auth|middleware|route|permission|role|admin"
fi
# Schedule: */30 * * * * /bin/bash ~/monitors/github-watch.shPORT SCANNING (often skipped — don't skip)
# naabu — fast port scanner from ProjectDiscovery
# Finds non-standard ports: 8080, 8443, 3000, 8888, 9000, etc.
cat /tmp/live.txt | awk '{print $1}' | naabu -port 80,443,8080,8443,3000,4000,5000,8000,8888,9000,9090,9200,6379 -silent | tee /tmp/open-ports.txt
# Why this matters: admin panels, debug services, internal APIs often run on alt ports
# Example wins: :8080/actuator/env (Spring Boot), :9200/_cat/indices (Elasticsearch), :6379 (Redis)SECRET SCANNING IN JS BUNDLES
# trufflehog — high-signal secret detection with entropy analysis
# Scans JS files and git repos
pip install trufflehog3 2>/dev/null || true
trufflehog filesystem --only-verified recon/$TARGET/ 2>/dev/null
# SecretFinder — manual JS bundle scan (already in tools/)
source ~/tools/SecretFinder/.venv/bin/activate
cat /tmp/urls.txt | grep "\.js$" | head -100 | while read url; do
python3 ~/tools/SecretFinder/SecretFinder.py -i "$url" -o cli 2>/dev/null
done
deactivate
# Quick grep for common patterns in downloaded JS
wget -q -r -l 1 -A "*.js" -P /tmp/js-files/ "https://$TARGET" 2>/dev/null
grep -rn "api_key\|apiKey\|client_secret\|access_token\|private_key\|AWS_SECRET\|AKIA" /tmp/js-files/ 2>/dev/nullGITHUB DORKING FOR TARGET
# Search GitHub for hardcoded secrets before hunting the app
TARGET_ORG="TargetOrgName" # Check their GitHub org
# Useful dorks (search on github.com):
# org:TARGET_ORG password
# org:TARGET_ORG api_key
# org:TARGET_ORG "Authorization: Bearer"
# org:TARGET_ORG .env
# org:TARGET_ORG "BEGIN RSA PRIVATE KEY"
# CLI with gh (GitHub CLI):
gh search code "api_key" --owner "$TARGET_ORG" --json path,repository 2>/dev/null | jq '.'
gh search code "password" --owner "$TARGET_ORG" --json path,repository 2>/dev/null | head -20
# GitDorker (if installed):
python3 ~/tools/GitDorker/GitDorker.py -t GITHUB_TOKEN -d ~/tools/GitDorker/Dorks/alldorksv3 -q "$TARGET" -org30-MINUTE RECON PROTOCOL
Minutes 0-5: Read Program Page
Note:
- ALL in-scope assets (every domain listed)
- Out-of-scope list (read carefully — common trap)
- Safe harbor statement
- Impact types accepted (some exclude "low")
- Average bounty amount (signals program generosity)Minutes 5-15: Asset Discovery
Run the standard pipeline above. Focus on live.txt output.
Minutes 15-25: Surface Map
Run gf patterns and the interesting-params grep above.
Minutes 25-30: Manual Exploration
Open Burp Suite. Browse the app with proxy on:
- Register an account
- Perform main user actions (create/read/update/delete resources)
- Note all API calls in Burp history
- Look for endpoints not in your URL list
After 30 min: Prioritize
Priority 1: API endpoints with ID parameters → IDOR candidates
Priority 2: File upload features → XSS/RCE candidates
Priority 3: OAuth/SSO flows → auth bypass candidates
Priority 4: Search/filter with user input → SQLi/SSRF/SSTI candidates
Priority 5: Admin/debug endpoints → auth bypass candidatesFramework-Specific References
See the references/ directory for framework-specific recon patterns:
| File | Covers |
|---|---|
references/express_nestjs_api_probe.md |
Express/NestJS API probing — Swagger discovery, root endpoint info leak, token-in-body auth pattern, sub-app enumeration, auth bypass vectors |
references/sailsjs_recon_patterns.md |
Sails.js detection, dev mode surface, blueprint API probing, socket.io, /api/health leaks, Docker IP leak, CORS misconfig, user enumeration, Next.js build manifest analysis for hybrid stacks, Huawei Cloud OBS detection |
references/nextjs_bundle_config_extraction.md |
Deep-dive Next.js webpack bundle extraction — downloading all chunks, searching for Privy App ID, WalletConnect project ID, Thirdweb RPC URLs, QuikNode API keys, API base URLs, embedded env-var fallbacks, and auth flow reconstruction |
references/vuejs_spa_bundle_extraction.md |
Vue.js SPA extraction — VUE_APP_* env dump, API route map from constants, RPC proxy abuse testing, SIWE auth flow, S3 upload pre-signed URL discovery, /dd/ vs /dcd/ path conventions |
references/payment-platform-sdk-recon.md |
Payment-platform recon using live policy DOM extraction, CT-source fallbacks, production/staging fencing, web-bundle manifests, and public iOS/Android/React Native SDKs as authoritative endpoint/auth maps. Includes owned-account experiment design and false-positive kills. |
references/web3-dapp-frontend-rpc-recon.md |
Web3 SPA recon: distinguish URL routes from UI state, capture runtime chunks, map wallet/RPC/contract surfaces, verify deployments with eth_getCode, safely simulate write functions via eth_call/eth_estimateGas, and kill public-RPC/WAF/scanner false positives. |
references/browser-gas-hunting-api-surface.md |
Browser-side API surface extraction from JS bundles (performance.getEntriesByType), auth flow reverse-engineering (challenge/sign-in/sign_message pattern, EVM signature, Turnkey custodial vs self-custody), token extraction from localStorage, Cloudflare WARP for IP rotation, terminal output encoding workaround |
references/passive_ct_dns_brute_curl_fingerprint.md |
Passive-only recon with just curl + Python stdlib (no subfinder/httpx/dnsx): crt.sh flakiness/retry + truncated-JSON regex fallback, DNS-brute wordlist, wildcard vs 0.0.0.1-sinkhole vs internal-IP interpretation of resolution results, curl fingerprinting (status/server/title), and the Python-vs-ProjectDiscovery httpx gotcha. |