diff --git a/README.md b/README.md index 6b554f5..56e86aa 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ [![Kali Linux](https://img.shields.io/badge/Kali%20Linux-Ready-557C94?logo=kalilinux&logoColor=white)](https://www.kali.org/) [![BlackArch](https://img.shields.io/badge/BlackArch-Available-1E1E2E?logo=archlinux&logoColor=white)](https://blackarch.org/) [![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey)](https://github.com/nox-project/nox-framework) -[![Sources](https://img.shields.io/badge/Sources-126-red)](https://github.com/nox-project/nox-framework) +[![Sources](https://img.shields.io/badge/Sources-123-red)](https://github.com/nox-project/nox-framework) *OSINT framework for red teaming, digital forensics, and corporate exposure analysis.* @@ -31,7 +31,7 @@ NOX is a purpose-built cyber threat intelligence engine designed for operators w | Capability | Detail | |-|-| -| ⚡ **Async Execution Engine** | Massively parallel scanning across 126 intelligence feeds with no sequential bottlenecks and no blocking I/O. | +| ⚡ **Async Execution Engine** | Massively parallel scanning across 123 intelligence feeds with no sequential bottlenecks and no blocking I/O. | | 🛡️ **Guardian Engine** | Integrated OPSEC layer with automatic proxy rotation and SOCKS5 support. Fail-safe kill-switch halts all traffic if the transport circuit is unavailable. | | 🧠 **Risk Scoring** | Dynamic 0–100 scoring with time-decay, source confidence weighting, password complexity analysis, persistence multipliers, and HVT detection. | | 🔗 **Recursive Avalanche Engine** | Every discovered asset — username, email, cracked password, phone — is automatically re-injected as a new scan seed. Per-asset pipeline runs sequentially (breach → crack → dork → scrape); child assets run concurrently. Identifiers from all four phases feed the pivot queue. Global deduplication and configurable depth cap prevent runaway recursion. | @@ -43,7 +43,7 @@ NOX is a purpose-built cyber threat intelligence engine designed for operators w | Feature | Description | |-|-| -| **126 JSON Plugin Sources** | Every intelligence source is a JSON plugin. The execution engine contains zero hardcoded source logic. | +| **123 JSON Plugin Sources** | Every intelligence source is a JSON plugin. The execution engine contains zero hardcoded source logic. | | **Async Core** | Full `asyncio` event loop with JA3 fingerprinting, SSL session management, per-request jitter, and configurable concurrency. | | **Autoscan Pipeline** | `--autoscan` triggers: breach scan → recursive pivot → Google/Bing/SearXNG dorking → paste/Telegram scraping — all in one command. | | **Recursive Avalanche Engine** | Every identifier discovered — from breach records, dork hits, or scraped paste/Telegram content — is re-injected as a new seed. Per-asset pipeline is sequential (breach → crack → dork → scrape); child assets run concurrently via `asyncio.gather`. A global `seen_assets` set prevents infinite loops. Concurrency and depth are fully configurable at runtime via `--threads` and `--depth`. | @@ -108,7 +108,7 @@ Supported fields: `name`, `endpoint`, `method`, `headers`, `regex_pattern` (or ` ``` For each asset (seed + every discovered identifier): ├─ Phase 1 — Breach Scan - │ 126 sources queried in parallel (async) + │ 123 sources queried in parallel (async) │ ├─ Phase 2 — Hash Crack (non-blocking, concurrent) │ Hashes found in breach data → rainbow-table APIs → cracked plaintext @@ -258,7 +258,7 @@ nox-cli --help The post-install script automatically: 1. Creates an isolated virtual environment at `/opt/nox-cli/.venv` 2. Installs all Python dependencies inside the venv (PEP 668 compliant — zero system pollution) -3. Builds the 126 source plugins +3. Builds the 123 source plugins 4. Links `/usr/bin/nox-cli` → `/opt/nox-cli/nox-wrapper.sh` ### Option 2: From Source diff --git a/build_sources.py b/build_sources.py index 7c93776..ed49009 100644 --- a/build_sources.py +++ b/build_sources.py @@ -71,6 +71,11 @@ class SourceConfig(BaseModel): backup_endpoints: List[str] = Field(default_factory=list) # H2: optional confidence override — when set, takes precedence over formula confidence: Optional[float] = None + # Two-phase poll support (e.g. IntelX: POST → job_id → GET results) + poll_endpoint: Optional[str] = None + poll_id_field: Optional[str] = None + poll_id_param: Optional[str] = None + poll_json_root: Optional[str] = None @field_validator("reliability_score") @classmethod @@ -131,6 +136,10 @@ def _mk( bypass_required: Optional[List[str]] = None, user_agent_type: Optional[str] = None, backup_endpoints: Optional[List[str]] = None, + poll_endpoint: Optional[str] = None, + poll_id_field: Optional[str] = None, + poll_id_param: Optional[str] = None, + poll_json_root: Optional[str] = None, ) -> SourceConfig: return SourceConfig( name=name, category=category, endpoint=endpoint, method=method, @@ -150,6 +159,10 @@ def _mk( bypass_required=bypass_required or None, user_agent_type=user_agent_type, backup_endpoints=backup_endpoints or [], + poll_endpoint=poll_endpoint, + poll_id_field=poll_id_field, + poll_id_param=poll_id_param, + poll_json_root=poll_json_root, ) @@ -251,10 +264,12 @@ FREE_PUBLIC_SOURCES: List[SourceConfig] = [ _base("hudsonrock_osint", "breach_data", "https://cavalier.hudsonrock.com/api/json/v2/osint-tools/search-by-email?email={target}", "GET", {"stealers": "$.stealers"}, + rate_limit=5.0, + headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"}, input_type="email", output_type=["email", "domain", "username"], normalization_map={"stealers": "breach_record"}, tags=["passive", "stealth"], - health_check_url="https://cavalier.hudsonrock.com", reliability_score=4), + health_check_url="https://cavalier.hudsonrock.com", reliability_score=3), _base("ipinfo_io", "geolocation", "https://ipinfo.io/{target}/json", "GET", @@ -459,32 +474,15 @@ FREE_PUBLIC_SOURCES: List[SourceConfig] = [ tags=["passive"], health_check_url="https://packetstormsecurity.com", reliability_score=4), - _base("scylla_sh_search", "breaches", - "https://scylla.so/search?q={target}", "GET", - {"results": "$.*"}, - input_type="email", output_type=["email", "domain"], - tags=["passive", "stealth"], - health_check_url="https://scylla.so", reliability_score=2, is_volatile=True, - bypass_required=["cloudflare"], user_agent_type="browser", - backup_endpoints=["https://scylla.so/api/search?q={target}"]), - - _base("vigilante_pw", "breaches", - "https://vigilante.pw/api/search?q={target}", "GET", - {"results": "$.results"}, - input_type="email", output_type=["email"], - tags=["passive", "stealth"], - health_check_url="https://vigilante.pw", reliability_score=2, is_volatile=True), - - # ── New free sources (v1.0.1) ───────────────────────────────────────────── - _base("proxynova_comb", "breaches", "https://api.proxynova.com/comb?query={target}", "GET", {"lines": "$.lines"}, input_type="email", output_type=["email"], normalization_map={"lines": "credential_line"}, tags=["passive", "stealth"], - health_check_url="https://api.proxynova.com", - reliability_score=3, is_volatile=True), + health_check_url="https://api.proxynova.com", reliability_score=3, is_volatile=True), + + # ── New free sources (v1.0.1) ───────────────────────────────────────────── _base("shodan_internetdb", "scanners", "https://internetdb.shodan.io/{target}", "GET", @@ -854,6 +852,10 @@ AUTHENTICATED_PREMIUM_SOURCES += [ payload_template={"term": "{target}", "buckets": [], "lookuplevel": 0, "maxresults": 100, "timeout": 0, "datefrom": "", "dateto": "", "sort": 4, "media": 0, "terminate": []}, + poll_endpoint="https://2.intelx.io/intelligent/search/result", + poll_id_field="id", + poll_id_param="id", + poll_json_root="records", tags=["passive", "stealth"], health_check_url="https://2.intelx.io", reliability_score=5), @@ -925,23 +927,16 @@ AUTHENTICATED_PREMIUM_SOURCES += [ tags=["passive", "stealth"], health_check_url="https://api.flare.io", reliability_score=4), - _base("leak_lookup", "breaches", + _auth("leak_lookup", "breaches", "https://leak-lookup.com/api/search", "POST", {"results": "$.message"}, + headers={"X-API-Key": "{LEAK_LOOKUP_API_KEY}"}, + api_key_slots=["{LEAK_LOOKUP_API_KEY}"], input_type="email", output_type=["email"], payload_template={"query": "{target}", "type": "email_address"}, tags=["passive", "stealth"], health_check_url="https://leak-lookup.com", reliability_score=3, is_volatile=True), - _auth("cit0day", "breaches", - "https://cit0day.in/api/v1/search?query={target}", "GET", - {"results": "$.results"}, - headers={"Authorization": "Bearer {CIT0DAY_API_KEY}"}, - api_key_slots=["{CIT0DAY_API_KEY}"], - input_type="email", output_type=["email"], - tags=["passive", "stealth"], - health_check_url="https://cit0day.in", reliability_score=2, is_volatile=True), - # ── DNS Recon ───────────────────────────────────────────────────────────── _auth("securitytrails_sub", "dns_recon", @@ -1154,7 +1149,7 @@ AUTHENTICATED_PREMIUM_SOURCES += [ api_key_slots=["{TWITTER_BEARER_TOKEN}"], input_type="username", output_type=["username"], tags=["passive"], - health_check_url="https://api.twitter.com", reliability_score=4), + health_check_url="https://api.twitter.com", reliability_score=1), _auth("github_code_search", "code", "https://api.github.com/search/code?q={target}", "GET", diff --git a/nox.py b/nox.py index 7d7b1ed..c5048b4 100644 --- a/nox.py +++ b/nox.py @@ -2105,12 +2105,8 @@ class ProxyManager: sys.exit(1) _PROXY_SOURCES = [ - ( - "https://api.proxyscrape.com/v2/" - "?request=displayproxies&protocol=http&timeout=5000" - "&country=all&ssl=all&anonymity=all" - ), - "https://www.proxy-list.download/api/v1/get?type=http&anon=elite", + "https://api.proxyscrape.com/v3/free-proxy-list/get?request=displayproxies&protocol=http&timeout=5000&proxy_format=protocolipport&format=text", + "https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/protocols/http/data.txt", "https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/http.txt", ] @@ -2225,6 +2221,7 @@ class DorkingEngine(Src): self._dead_proxies: set = set() self._proxy_index: int = 0 self.proxies = ProxyManager.get_proxies() + self._dead_instances: set = set() def _get_next_proxy(self) -> Optional[str]: live = [p for p in self.proxies if p not in self._dead_proxies] @@ -2294,7 +2291,11 @@ class DorkingEngine(Src): from aiohttp_socks import ProxyConnector as _ProxyConnector except ImportError: _ProxyConnector = None - instance = random.choice(_SEARX_INSTANCES) + live_instances = [i for i in _SEARX_INSTANCES if i not in self._dead_instances] + if not live_instances: + self._dead_instances.clear() + live_instances = list(_SEARX_INSTANCES) + instance = random.choice(live_instances) url = f"{instance}/search?q={urllib.parse.quote(query)}&format=json&categories=general" proxy = self._get_next_proxy() try: @@ -2306,6 +2307,7 @@ class DorkingEngine(Src): async with sess.get(url, headers=_random_headers(), timeout=aiohttp_mod.ClientTimeout(total=12)) as resp: if resp.status != 200: + self._dead_instances.add(instance) if proxy: self._dead_proxies.add(proxy) return [] @@ -2316,6 +2318,7 @@ class DorkingEngine(Src): if r.get("url") ] except Exception: + self._dead_instances.add(instance) if proxy: self._dead_proxies.add(proxy) return [] @@ -2452,43 +2455,19 @@ class DorkEngine: def _search(self, query: str, engine: str) -> List[dict]: hits = [] try: - urls = { - "google": f"https://www.google.com/search?q={urllib.parse.quote(query)}&num=10", - "bing": f"https://www.bing.com/search?q={urllib.parse.quote(query)}&count=10", - "ddg": f"{random.choice(_SEARX_INSTANCES)}/search?q={urllib.parse.quote(query)}&format=json&categories=general", - } - use_cs = engine != "ddg" # SearXNG is a plain JSON API — no cloudscraper needed - resp = self.s.get(urls.get(engine, urls["google"]), timeout=15, use_cloudscraper=use_cs) + # Direct Google/Bing HTML scraping is blocked by CAPTCHA/consent walls + # since 2024. Route all engines through SearXNG JSON API. + url = f"{random.choice(_SEARX_INSTANCES)}/search?q={urllib.parse.quote(query)}&format=json&categories=general" + resp = self.s.get(url, timeout=15, use_cloudscraper=False) if not resp.ok: return hits - # DDG/SearXNG returns JSON - if engine == "ddg": - try: - data = resp.json() - for r in data.get("results", [])[:10]: - if r.get("url"): - hits.append({"title": r.get("title", ""), "url": r["url"], "snippet": r.get("content", "")}) - except Exception: - pass - return hits - if not BeautifulSoup: - return hits - soup = BeautifulSoup(resp.text, "html.parser") - selectors = { - "google": ("div.g", "h3", "a[href]", ".VwiC3b"), - "bing": ("li.b_algo", "h2", "a", ".b_caption p"), - } - container, title_sel, link_sel, snippet_sel = selectors.get(engine, selectors["google"]) - for item in soup.select(container)[:10]: - title_el = item.select_one(title_sel) - link_el = item.select_one(link_sel) - snip_el = item.select_one(snippet_sel) - if title_el: - url = link_el.get("href","") if link_el else "" + data = resp.json() + for r in data.get("results", [])[:10]: + if r.get("url"): hits.append({ - "title": title_el.get_text().strip(), - "url": url if url.startswith("http") else "", - "snippet": snip_el.get_text().strip() if snip_el else "", + "title": r.get("title", ""), + "url": r["url"], + "snippet": r.get("content", ""), }) except Exception: pass @@ -6409,6 +6388,39 @@ class NoxSourceProvider(FileSystemProvider): if status not in range(200, 300) or not text: return [] + # Two-phase poll: if poll_endpoint is defined, treat the first response + # as a job submission, extract the job ID via poll_id_field, then poll + # poll_endpoint?= until results arrive. + poll_endpoint = d.get("poll_endpoint", "") + if poll_endpoint: + try: + job_id = json.loads(text).get(d.get("poll_id_field", "id")) + except Exception: + job_id = None + if not job_id: + return [] + poll_param = d.get("poll_id_param", "id") + poll_root = d.get("poll_json_root", d.get("json_root", "")) + poll_url = f"{poll_endpoint}?{poll_param}={job_id}" + delay = 2 + for _ in range(4): + await asyncio.sleep(delay) + p_status, p_text, _ = await self._get(session, poll_url, headers=hdrs) + if p_status not in range(200, 300) or not p_text: + delay = min(delay * 2, 16) + continue + try: + items = json.loads(p_text) + for key in (poll_root.split(".") if poll_root else []): + if isinstance(items, dict): + items = items.get(key, []) + if isinstance(items, list) and items: + return self._by_json(p_text, poll_root, d.get("field_map", {})) + except Exception: + pass + delay = min(delay * 2, 16) + return [] + regex = d.get("regex_pattern", "") if regex: return self._by_regex(text, regex) @@ -6528,8 +6540,15 @@ class SourceOrchestrator: "payload": raw.get("payload_template") or raw.get("payload") or {}, # Pass resolved slot keys so FileSystemProvider can use them "_slot_keys": slot_keys, + # Two-phase poll support + "poll_endpoint": raw.get("poll_endpoint", ""), + "poll_id_field": raw.get("poll_id_field", "id"), + "poll_id_param": raw.get("poll_id_param", "id"), + "poll_json_root": raw.get("poll_json_root", ""), } - sources.append(NoxSourceProvider(self._sem, self._db, self._config, defn)) + inst = NoxSourceProvider(self._sem, self._db, self._config, defn) + inst._bypass_required = raw.get("bypass_required") or [] + sources.append(inst) logger.debug("SourceOrchestrator: loaded %s", jf.name) except Exception as exc: logger.warning("SourceOrchestrator: failed %s — %s", jf.name, exc) @@ -6558,8 +6577,14 @@ class SourceOrchestrator: def get_sources(self, session: "Session", qtype: str) -> List[AsyncSource]: """Return plugin sources applicable to qtype, pre-filtered to avoid creating unnecessary tasks.""" self._ensure_loaded() + # curl_cffi presence cached in OPTIONAL after first _try_import call + _has_cffi = "curl_cffi" in OPTIONAL or _try_import("curl_cffi") is not None sources: List[AsyncSource] = [] for src in self._nox_sources: + bypass = getattr(src, "_bypass_required", []) or [] + if "cloudflare" in bypass and not _has_cffi: + logger.debug("Skipping %s — cloudflare bypass required, curl_cffi absent", src.name) + continue input_type = getattr(src, "_input_type", "") if not input_type or input_type == "any" or not qtype or input_type == qtype: sources.append(src) diff --git a/requirements.txt b/requirements.txt index 4f400a6..2245359 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ brotli>=1.1.0 # brotli decompression for aiohttp br responses zstandard>=0.23.0 # zstd decompression for aiohttp zstd responses (Cloudflare/Fastly CDNs) # ── Intelligence & Scraping ──────────────────────────────────────────── -requests>=2.31.0 +requests>=2.32.3 certifi>=2024.2.2 # up-to-date CA bundle for SSL verification cloudscraper>=1.2.71 # Cloudflare-protected endpoint bypass beautifulsoup4>=4.12.3 diff --git a/sources/cit0day.json b/sources/cit0day.json deleted file mode 100644 index 9a6e475..0000000 --- a/sources/cit0day.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "cit0day", - "category": "breaches", - "endpoint": "https://cit0day.in/api/v1/search?query={target}", - "method": "GET", - "requires_auth": true, - "selectors": { - "results": "$.results" - }, - "rate_limit": 1.0, - "headers": { - "Authorization": "Bearer {CIT0DAY_API_KEY}" - }, - "api_key_slots": [ - "{CIT0DAY_API_KEY}" - ], - "input_type": "email", - "output_type": [ - "email" - ], - "normalization_map": {}, - "tags": [ - "passive", - "stealth" - ], - "health_check_url": "https://cit0day.in", - "expected_status": 200, - "reliability_score": 2, - "is_volatile": true, - "backup_endpoints": [], - "confidence": 0.55 -} \ No newline at end of file diff --git a/sources/helpers/config_handler.py b/sources/helpers/config_handler.py index 0f1b48c..d35e4f2 100644 --- a/sources/helpers/config_handler.py +++ b/sources/helpers/config_handler.py @@ -94,7 +94,6 @@ SERVICE_REGISTRY: Dict[str, Dict] = { "GOOGLE_CX_KEY": {"display": "Google Custom Search (API key)", "public": False}, "GOOGLE_CX_ID": {"display": "Google Custom Search (CX ID)", "public": False}, "GREYNOISE_API_KEY": {"display": "GreyNoise", "public": False}, - "HASHES_API_KEY": {"display": "Hashes.org", "public": False}, "HIBP_API_KEY": {"display": "HaveIBeenPwned", "public": False}, "HIPPO_API_KEY": {"display": "EmailHippo", "public": False}, "HUNTER_API_KEY": {"display": "Hunter.io", "public": False}, @@ -147,6 +146,8 @@ SERVICE_REGISTRY: Dict[str, Dict] = { "MALWAREBAZAAR_API_KEY": {"display": "MalwareBazaar (abuse.ch)", "public": False}, "FULLHUNT_API_KEY": {"display": "FullHunt (attack surface)", "public": False}, "NETLAS_API_KEY": {"display": "Netlas.io (internet scanner)", "public": False}, + # ── Added in v1.0.2 ─────────────────────────────────────────────── + "LEAK_LOOKUP_API_KEY": {"display": "Leak-Lookup", "public": False}, } _PRIVATE_KEYS = {k: v for k, v in SERVICE_REGISTRY.items() if not v["public"]} diff --git a/sources/hudsonrock_osint.json b/sources/hudsonrock_osint.json index 0bcdfda..5ab3b8f 100644 --- a/sources/hudsonrock_osint.json +++ b/sources/hudsonrock_osint.json @@ -7,8 +7,10 @@ "selectors": { "stealers": "$.stealers" }, - "rate_limit": 1.0, - "headers": {}, + "rate_limit": 5.0, + "headers": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" + }, "api_key_slots": [], "input_type": "email", "output_type": [ @@ -25,7 +27,7 @@ ], "health_check_url": "https://cavalier.hudsonrock.com", "expected_status": 200, - "reliability_score": 4, + "reliability_score": 3, "backup_endpoints": [], - "confidence": 0.85 + "confidence": 0.7 } \ No newline at end of file diff --git a/sources/intelx_search.json b/sources/intelx_search.json index b64d04a..7a5cb27 100644 --- a/sources/intelx_search.json +++ b/sources/intelx_search.json @@ -40,5 +40,9 @@ "expected_status": 200, "reliability_score": 5, "backup_endpoints": [], + "poll_endpoint": "https://2.intelx.io/intelligent/search/result", + "poll_id_field": "id", + "poll_id_param": "id", + "poll_json_root": "records", "confidence": 1.0 } \ No newline at end of file diff --git a/sources/leak_lookup.json b/sources/leak_lookup.json index a18bc77..1d3aa5f 100644 --- a/sources/leak_lookup.json +++ b/sources/leak_lookup.json @@ -3,17 +3,21 @@ "category": "breaches", "endpoint": "https://leak-lookup.com/api/search", "method": "POST", - "requires_auth": false, + "requires_auth": true, "selectors": { "results": "$.message" }, "rate_limit": 1.0, - "headers": {}, + "headers": { + "X-API-Key": "{LEAK_LOOKUP_API_KEY}" + }, "payload_template": { "query": "{target}", "type": "email_address" }, - "api_key_slots": [], + "api_key_slots": [ + "{LEAK_LOOKUP_API_KEY}" + ], "input_type": "email", "output_type": [ "email" diff --git a/sources/scylla_sh_search.json b/sources/scylla_sh_search.json deleted file mode 100644 index f44ed6c..0000000 --- a/sources/scylla_sh_search.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "scylla_sh_search", - "category": "breaches", - "endpoint": "https://scylla.so/search?q={target}", - "method": "GET", - "requires_auth": false, - "selectors": { - "results": "$.*" - }, - "rate_limit": 1.0, - "headers": {}, - "api_key_slots": [], - "input_type": "email", - "output_type": [ - "email", - "domain" - ], - "normalization_map": {}, - "tags": [ - "passive", - "stealth" - ], - "health_check_url": "https://scylla.so", - "expected_status": 200, - "reliability_score": 2, - "is_volatile": true, - "bypass_required": [ - "cloudflare" - ], - "user_agent_type": "browser", - "backup_endpoints": [ - "https://scylla.so/api/search?q={target}" - ], - "confidence": 0.55 -} \ No newline at end of file diff --git a/sources/twitter_v2.json b/sources/twitter_v2.json index baed8b2..129a210 100644 --- a/sources/twitter_v2.json +++ b/sources/twitter_v2.json @@ -24,7 +24,7 @@ ], "health_check_url": "https://api.twitter.com", "expected_status": 200, - "reliability_score": 4, + "reliability_score": 1, "backup_endpoints": [], - "confidence": 0.85 + "confidence": 0.4 } \ No newline at end of file diff --git a/sources/vigilante_pw.json b/sources/vigilante_pw.json deleted file mode 100644 index 4c9cd8b..0000000 --- a/sources/vigilante_pw.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "vigilante_pw", - "category": "breaches", - "endpoint": "https://vigilante.pw/api/search?q={target}", - "method": "GET", - "requires_auth": false, - "selectors": { - "results": "$.results" - }, - "rate_limit": 1.0, - "headers": {}, - "api_key_slots": [], - "input_type": "email", - "output_type": [ - "email" - ], - "normalization_map": {}, - "tags": [ - "passive", - "stealth" - ], - "health_check_url": "https://vigilante.pw", - "expected_status": 200, - "reliability_score": 2, - "is_volatile": true, - "backup_endpoints": [], - "confidence": 0.55 -} \ No newline at end of file