A fast Rust CLI tool that queries multiple threat intelligence sources for a given IP address. Single-IP lookups open a live ratatui TUI where results populate in real time as each API call completes. After all sources finish, an AI Analysis panel streams a Grok-powered threat narrative directly into the TUI. Bulk and JSON modes output to the terminal or a file unchanged.
- Live TUI for single-IP lookups — sources populate with animated spinners as concurrent API calls finish; verdict updates in real time
- Eleven concurrent API lookups, then a streaming AI analysis (Grok via OpenRouter) fires automatically once all sources are terminal
- AI Analysis streams chunk-by-chunk into the detail pane — no waiting for the full response before text appears
- Synthesized SUMMARY verdict (CLEAN / SUSPICIOUS / MALICIOUS) recomputed live in the TUI header as each source arrives
- AI Analysis is informational only — it does not contribute to the verdict
- Graceful degradation — missing keys or failed sources show
[source unavailable]/✗, the rest still display --verboseflag reveals exactly why each source failed (CLI/JSON mode)--onlyflag to query a subset of sources; skipped sources show-in the TUI--no-colorflag for clean file output--outputto write results directly to a file- Bulk mode via
--file ips.txt— processes a list of IPs/CIDRs with rate-limit courtesy delays - CIDR range support —
scope-recon 10.0.0.0/28expands and queries each host (max 256) - On-disk caching under
~/.cache/scope-recon/with configurable TTL;rin the TUI bypasses cache and re-queries all sources including AI - Shodan InternetDB fallback when no Shodan API key is set (free, no key required)
queried_attimestamp on every report for audit trails and cache freshness checks--jsonflag for machine-readable output; bulk JSON output is a JSON array- No API key required for geolocation (ip-api.com), BGP routing data (RIPE Stat), or basic port data (Shodan InternetDB)
- IPinfo works without a token (rate-limited); set
IPINFO_TOKENfor 50k req/month
All eleven intelligence sources run concurrently. AI Analysis fires sequentially after all eleven are complete.
| Source | Purpose | Key Required |
|---|---|---|
| ip-api.com | Geolocation, ASN, ISP | No |
| Shodan | Open ports, service banners, CVEs | No (InternetDB fallback) / Yes (full) |
| VirusTotal | Vendor reputation consensus | Yes |
| AlienVault OTX | Threat campaigns, pulse correlation | Yes |
| AbuseIPDB | Abuse confidence score, report history | Yes |
| GreyNoise | Internet noise vs. targeted activity | Optional |
| ThreatFox | Malware C2 IOC matching | Yes (free) |
| RIPE Stat | BGP routing, ASN, prefix | No |
| IPQualityScore | Fraud score, VPN/proxy/TOR/bot detection | Yes |
| Pulsedive | Aggregated risk level, threat feed names | Yes |
| IPinfo | Hostname, org, timezone; privacy flags (paid) | Optional |
| OpenRouter / Grok | AI threat narrative, malware family context | Yes |
Running scope-recon <IP> without --json, --output, or --file opens a full-screen terminal UI:
┌─ scope-recon ──────────────────────────────────────────────────────┐
│ IP: 8.8.8.8 VERDICT: ● CLEAN │
├──────────────────┬─────────────────────────────────────────────────┤
│ SOURCES │ GEOLOCATION (ip-api.com) │
│ │ │
│ ▶ Geolocation ✓ │ Country: United States │
│ Shodan ✓ │ Region: Virginia │
│ AbuseIPDB ✓ │ City: Ashburn │
│ VirusTotal ✓ │ ISP: Google LLC │
│ OTX ✗ │ Org: Google Public DNS │
│ GreyNoise ✓ │ ASN: AS15169 Google LLC │
│ ThreatFox ✓ │ │
│ BGPView ✓ │ │
│ IPQS ✗ │ │
│ Pulsedive ✗ │ │
│ IPInfo ✓ │ │
│ AI Analysis ⠸ │ │
├──────────────────┴─────────────────────────────────────────────────┤
│ q quit ↑↓/jk navigate PgUp/PgDn scroll detail r refresh │
└────────────────────────────────────────────────────────────────────┘
AI Analysis shows a spinner while the other eleven sources are still loading, then continues spinning while Grok streams its response. Text appears in the detail pane chunk by chunk as it arrives.
Status icons
| Icon | Meaning |
|---|---|
⠋⠙⠸… (animated) |
Loading |
✓ green |
Done |
✗ red |
Error / key not set |
- dim |
Skipped (--only filter) |
Keybindings
| Key | Action |
|---|---|
q / Ctrl-C |
Quit |
↑ / k |
Select previous source |
↓ / j |
Select next source |
PgUp / [ |
Scroll detail pane up |
PgDn / ] |
Scroll detail pane down |
r |
Re-query all sources (bypasses cache) |
The TUI activates only for single-IP interactive use. All other modes (--json, --file, --output, CIDR ranges) use the original CLI output path unchanged.
- Rust 1.70 or later
git clone https://github.com/youruser/scope-recon
cd scope-recon
cargo build --releaseThe binary will be at target/release/scope-recon. Optionally move it to your PATH:
cp target/release/scope-recon ~/.local/bin/Register for free accounts at each service:
| Service | Registration |
|---|---|
| Shodan | https://account.shodan.io/register |
| VirusTotal | https://www.virustotal.com/gui/join-us |
| AlienVault OTX | https://otx.alienvault.com |
| AbuseIPDB | https://www.abuseipdb.com/register |
| GreyNoise | https://www.greynoise.io (community tier) |
| ThreatFox | https://auth.abuse.ch/ (abuse.ch Authentication Portal) |
| IPQualityScore | https://www.ipqualityscore.com/create-account |
| Pulsedive | https://pulsedive.com/register |
| IPinfo | https://ipinfo.io/signup |
| OpenRouter | https://openrouter.ai (model: x-ai/grok-3-beta) |
ip-api.com and RIPE Stat require no account or key. Shodan falls back to InternetDB (ports, hostnames, tags, vulns — no service banners) if SHODAN_API_KEY is not set. IPinfo works without a token (1,000 req/day shared limit); set IPINFO_TOKEN for 50,000 req/month. IPQualityScore and Pulsedive have free tiers (1,000 req/month and 250 req/day respectively). If OPENROUTER_API_KEY is not set, AI Analysis shows ✗ and is skipped — all other sources are unaffected.
Avoid typing export KEY=value directly in your terminal — the command gets saved to your shell history (~/.zsh_history, ~/.bash_history) in plaintext.
Recommended: add to your shell profile in an editor
Open ~/.zshrc (or ~/.bashrc) in a text editor and add:
export SHODAN_API_KEY=your_shodan_key_here
export VIRUSTOTAL_API_KEY=your_virustotal_key_here
export OTX_API_KEY=your_otx_key_here
export ABUSEIPDB_API_KEY=your_abuseipdb_key_here
export GREYNOISE_API_KEY=your_greynoise_key_here # optional
export THREATFOX_API_KEY=your_threatfox_key_here
export IPQS_API_KEY=your_ipqualityscore_key_here
export PULSEDIVE_API_KEY=your_pulsedive_key_here
export IPINFO_TOKEN=your_ipinfo_token_here # optional
export OPENROUTER_API_KEY=your_openrouter_key_here # optional; enables AI AnalysisThen lock down the file so only your user can read it:
chmod 600 ~/.zshrcReload to apply:
source ~/.zshrcAlternative: inline per-command (never stored in history)
Prefix the command directly — keys never touch your history:
SHODAN_API_KEY=abc VIRUSTOTAL_API_KEY=xyz scope-recon 8.8.8.8In zsh, set
HIST_IGNORE_SPACEand prefix the command with a leading space to suppress history recording for that line.
Alternative: use a secrets manager
Tools like pass, 1Password CLI (op run --), or Bitwarden CLI can inject secrets at runtime without them residing in any config file:
op run -- scope-recon 8.8.8.8What to avoid
- Typing
export KEY=valuein the terminal (saved to history) - Storing keys in a
.envfile inside a git repository - Sharing terminal screenshots that include
printenvorenvoutput
scope-recon [TARGET] [OPTIONS]
Arguments:
[TARGET] IP address or CIDR range to investigate (e.g. 1.2.3.4 or 1.2.3.0/24)
Options:
--file <FILE> File containing IPs/CIDRs, one per line (# for comments)
--json Output as JSON instead of pretty-printed table
--verbose Show why each source failed
--no-color Disable color output
--only <SOURCES> Comma-separated list of sources to query
--output <FILE> Write output to file instead of stdout
--cache-ttl <SECS> Cache TTL in seconds; 0 disables caching (default: 3600)
-h, --help Print help
scope-recon 8.8.8.8Opens the TUI (see TUI Interface above). Sources populate live with animated spinners; navigate with ↑↓ or jk to inspect each source's detail pane. Once all eleven sources are complete, AI Analysis fires automatically and streams its response. Press q to quit, r to refresh.
Process a list of IPs from a file (one IP or CIDR per line, # for comments):
scope-recon --file targets.txt
scope-recon --file targets.txt --json --output results.json# targets.txt
8.8.8.8
1.1.1.1
# 192.168.1.0/28 (commented out)
A 500ms courtesy delay is inserted between each target to avoid hammering APIs. JSON bulk output is a JSON array containing one object per IP.
scope-recon 192.168.1.0/30Expands to host IPs and queries each one sequentially. Maximum 256 hosts per range.
scope-recon 8.8.8.8 --only shodan,virustotal
scope-recon 8.8.8.8 --only threatfox,greynoise
scope-recon 8.8.8.8 --only openrouter # AI fires with whatever data is availableSources not in the list are silently skipped — they do not appear as errors in --verbose output. When openrouter is not in the --only list, AI Analysis shows - (skipped).
scope-recon 8.8.8.8 --verboseAppends a SOURCE ERRORS section showing exactly why each unavailable source failed:
SOURCE ERRORS
AbuseIPDB: ABUSEIPDB_API_KEY not set
VirusTotal: VirusTotal rate limited after retry
OTX: OTX_API_KEY not set
AI Analysis: OPENROUTER_API_KEY not set
scope-recon 8.8.8.8 --no-colorColors are also automatically disabled when --output writes to a file.
scope-recon 8.8.8.8 --json --output report.json
scope-recon 8.8.8.8 --output report.txt # pretty output, no colorResults are cached to ~/.cache/scope-recon/{ip}.json for 1 hour by default. On a cache hit, the tool skips all API calls and returns the stored report instantly — including the AI Analysis if it was cached.
# Use cached data for up to 24 hours
scope-recon 8.8.8.8 --cache-ttl 86400
# Disable caching entirely
scope-recon 8.8.8.8 --cache-ttl 0With --verbose, cache hits are reported to stderr:
[cache hit] 8.8.8.8 — queried at 2026-03-07T10:00:00+00:00
If any source returns HTTP 429 (rate limited), the tool waits 2 seconds and retries once automatically. A warning is printed to stderr:
warning: VirusTotal rate limited (429) — retrying in 2s...
The verdict is computed from the eleven intelligence sources only. AI Analysis is informational and does not affect it.
| Verdict | Triggers |
|---|---|
MALICIOUS |
Any VT malicious detections · AbuseIPDB score ≥ 75 · GreyNoise classification = malicious · ThreatFox C2 IOC match · IPQS fraud score ≥ 75 · Pulsedive risk = high/critical |
SUSPICIOUS |
VT suspicious detections only · AbuseIPDB score 25–74 · OTX pulses with no harder signal · IPQS fraud score 30–74 · Pulsedive risk = medium |
CLEAN |
None of the above; positive signals (whitelisted, RIOT, 0 detections) listed |
- SUMMARY verdict: green (CLEAN), yellow (SUSPICIOUS), red (MALICIOUS)
- VirusTotal: red if any malicious, yellow if suspicious only, green if clean
- AbuseIPDB score: green (0–24 LOW), yellow (25–74 MEDIUM), red (75–100 HIGH)
- GreyNoise classification: green (benign), red (malicious), yellow (unknown), dimmed (not seen)
- OTX pulses: green if 0, yellow/bold if any found
- ThreatFox C2 IOCs: green if 0, red/bold if any found
scope-recon 8.8.8.8 --json{
"queried_at": "2026-03-07T10:00:00+00:00",
"ip": "8.8.8.8",
"ipapi": {
"country": "United States",
"region": "Virginia",
"city": "Ashburn",
"isp": "Google LLC",
"org": "Google Public DNS",
"asn": "AS15169 Google LLC"
},
"shodan": {
"org": "Google LLC",
"isp": "Google LLC",
"country": "United States",
"open_ports": [53, 443],
"services": [
{ "port": 53, "transport": "udp", "product": null, "version": null },
{ "port": 443, "transport": "tcp", "product": "nginx", "version": "1.14.0" }
],
"hostnames": ["dns.google"],
"tags": [],
"vulns": []
},
"abuseipdb": {
"abuse_confidence": 0,
"total_reports": 53,
"country": "US",
"domain": "google.com",
"isp": "Google LLC",
"usage_type": "Search Engine Spider",
"last_reported_at": "2024-10-15",
"is_tor": false,
"is_whitelisted": true
},
"virustotal": {
"malicious": 0,
"suspicious": 0,
"harmless": 59,
"undetected": 35,
"last_analysis_date": "2026-03-06"
},
"otx": { "pulse_count": 0, "pulse_names": [] },
"greynoise": {
"noise": false,
"riot": true,
"classification": "benign",
"name": "Google Public DNS",
"last_seen": "2026-03-07"
},
"threatfox": { "ioc_count": 0, "iocs": [] },
"bgpview": {
"asn": 15169,
"asn_name": "GOOGLE",
"asn_description": "Google LLC",
"country_code": "US",
"ptr_record": "dns.google",
"prefixes": ["8.8.8.0/24"],
"rir": "ARIN"
},
"ipqs": {
"fraud_score": 0,
"proxy": false,
"vpn": false,
"tor": false,
"bot_status": false,
"recent_abuse": false,
"abuse_velocity": "none",
"isp": "Google LLC",
"country_code": "US"
},
"pulsedive": {
"risk": "none",
"last_seen": null,
"threats": [],
"feeds": []
},
"ipinfo": {
"hostname": "dns.google",
"city": "Mountain View",
"region": "California",
"country": "US",
"org": "AS15169 Google LLC",
"timezone": "America/Los_Angeles",
"is_vpn": null,
"is_proxy": null,
"is_tor": null,
"is_hosting": null
},
"ai_analysis": {
"analysis": "8.8.8.8 is Google's primary public DNS resolver..."
}
}Unavailable sources (including AI Analysis when no key is set) appear as null. Bulk output (--file) is a JSON array.
scope-recon 1.2.3.4 --json | jq '.virustotal.malicious'
scope-recon 1.2.3.4 --json | jq '.otx.pulse_names'
scope-recon 1.2.3.4 --json | jq '.threatfox.iocs[].malware'
scope-recon 1.2.3.4 --json | jq '.shodan.services[] | "\(.port)/\(.transport) \(.product // "-")"'
scope-recon 1.2.3.4 --json | jq '.ai_analysis.analysis'| Situation | Behavior |
|---|---|
| API key env var not set | Source shown as [source unavailable]; reason shown with --verbose |
| API returns non-2xx | Source shown as [source unavailable]; reason shown with --verbose |
| API returns 429 (rate limited) | Waits 2s and retries once; warns to stderr |
| Network timeout / DNS failure | Source shown as [source unavailable]; tool continues |
| GreyNoise 404 (IP not in dataset) | Shown as class: not seen rather than an error |
| ThreatFox no results | Shows C2 IOCs: 0, not an error |
| RIPE Stat (IP not announced) | Returns empty result (no prefixes), not an error |
| Pulsedive unknown (IP not in database) | Shows risk: unknown, not an error |
| IPinfo without token | Works with shared rate limit (1,000 req/day); privacy fields absent |
| Shodan key not set | Falls back to InternetDB automatically (no key required) |
OPENROUTER_API_KEY not set |
AI Analysis shows ✗; all other sources unaffected |
| OpenRouter API error | AI Analysis shows error message; verdict and other sources unaffected |
| Invalid IP or hostname passed | Rejected immediately before any API calls |
| CIDR range exceeds 256 hosts | Rejected with a clear error message |
| Cache entry expired | Silently re-queries all sources |
| Source | Limit |
|---|---|
| ip-api.com | 45 requests/minute, no key required |
| Shodan InternetDB | No published limit, no key required |
| Shodan (full API) | 1 request/second; free accounts have limited access |
| VirusTotal | 4 requests/minute, 500/day |
| AlienVault OTX | No published hard limit on free tier |
| AbuseIPDB | 1,000 checks/day |
| GreyNoise | Community tier: limited daily lookups |
| ThreatFox | No published limit; free key required |
| RIPE Stat | No published limit; no key required |
| IPQualityScore | 1,000 lookups/month (free tier) |
| Pulsedive | 250 requests/day (free tier) |
| IPinfo | 1,000 req/day unauthenticated; 50,000/month with free token |
| OpenRouter / Grok | Pay-per-token; see https://openrouter.ai/pricing |
scope-recon/
├── Cargo.toml
└── src/
├── main.rs # CLI dispatch, TUI mode detection, bulk/CIDR expansion,
│ # cache integration, concurrent query orchestration,
│ # sequential AI call after join!, tests
├── cli.rs # Clap CLI struct and all flags
├── model.rs # ThreatReport and all summary structs (Serialize + Deserialize + Clone)
├── cache.rs # On-disk cache load/save with TTL, tests
├── output.rs # pretty_print(), json_print(), compute_verdict()
├── tui/
│ ├── mod.rs # run_tui(), terminal setup/teardown, tokio::select! event loop,
│ │ # spawn_queries(), all_sources_terminal(), AI trigger logic
│ ├── app.rs # App state, SourceState<T>, SourceUpdate, handle_key(), apply_update()
│ └── ui.rs # render() — header, source list, detail pane, footer, build_partial_report()
└── api/
├── mod.rs
├── retry.rs # Generic 429-aware retry wrapper
├── ipapi.rs # ip-api.com geolocation (no key)
├── shodan.rs # Shodan full API — open ports, service banners, CVEs
├── internetdb.rs # Shodan InternetDB fallback (no key), tests
├── abuseipdb.rs # AbuseIPDB abuse score, usage type, report history
├── virustotal.rs # VirusTotal vendor consensus, last analysis date
├── otx.rs # AlienVault OTX pulse/campaign correlation
├── greynoise.rs # GreyNoise noise vs. targeted classification
├── threatfox.rs # ThreatFox malware C2 IOC matching (free key required)
├── bgpview.rs # RIPE Stat BGP routing, ASN, prefix (no key)
├── ipqs.rs # IPQualityScore fraud/proxy/VPN/bot detection
├── pulsedive.rs # Pulsedive aggregated risk and threat feeds
├── ipinfo.rs # IPinfo hostname, org, timezone, privacy flags
└── openrouter.rs # OpenRouter/Grok streaming AI analysis (TUI) and batch (CLI)
MIT