speedn commited on
Commit
e6e7538
·
verified ·
1 Parent(s): abbe0dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -441
app.py CHANGED
@@ -1,450 +1,172 @@
1
- import os, re, json, urllib.parse
2
- from typing import Dict, Any, List, Optional, Tuple
3
-
4
  import gradio as gr
 
5
  import requests
6
-
7
- APP_TITLE = "🔎 Query Builder (Educational)"
8
- APP_DESC = """
9
- Type what you want (e.g., *"find RDP in Germany for Org Contoso"*) and/or use the structured fields.
10
- This builds **query strings only** for training: **no live scans, no API calls to data sources**.
11
-
12
- **Platforms:** Shodan, ZoomEye, Google (dorks)
13
- **LLM parsing:** Optional — choose a model from the dropdown. Token box appears only when needed.
14
- """
15
-
16
- # ----------------------------
17
- # Heuristic NL parsing (no LLM)
18
- # ----------------------------
19
-
20
- COUNTRY_NAME_TO_ISO2 = {
21
- "united states": "US", "usa": "US", "us": "US", "america": "US",
22
- "germany": "DE", "deutschland": "DE",
23
- "japan": "JP",
24
- "brazil": "BR",
25
- "india": "IN", # NOTE: removed bare "in" to avoid preposition collision
26
- "united kingdom": "GB", "uk": "GB", "britain": "GB",
27
- "france": "FR",
28
- "canada": "CA",
29
- "australia": "AU",
30
- "netherlands": "NL",
31
- "spain": "ES",
32
- "italy": "IT",
33
- "sweden": "SE",
34
- "switzerland": "CH",
35
- }
36
-
37
- US_STATE_ABBREV = {
38
- "AL","AK","AZ","AR","CA","CO","CT","DE","FL","GA","HI","ID","IL","IN","IA",
39
- "KS","KY","LA","ME","MD","MA","MI","MN","MS","MO","MT","NE","NV","NH","NJ",
40
- "NM","NY","NC","ND","OH","OK","OR","PA","RI","SC","SD","TN","TX","UT","VT",
41
- "VA","WA","WV","WI","WY","DC"
42
- }
43
- US_STATE_NAMES = {
44
- "alabama","alaska","arizona","arkansas","california","colorado","connecticut","delaware",
45
- "florida","georgia","hawaii","idaho","illinois","indiana","iowa","kansas","kentucky",
46
- "louisiana","maine","maryland","massachusetts","michigan","minnesota","mississippi",
47
- "missouri","montana","nebraska","nevada","new hampshire","new jersey","new mexico",
48
- "new york","north carolina","north dakota","ohio","oklahoma","oregon","pennsylvania",
49
- "rhode island","south carolina","south dakota","tennessee","texas","utah","vermont",
50
- "virginia","washington","west virginia","wisconsin","wyoming","district of columbia","dc"
51
- }
52
-
53
- # tokens we don't want to leak into queries
54
- STOPWORDS = {
55
- "find","show","in","the","and","or","for","of","with",
56
- "zip","zipcode","code","ks","us" # cut noise like "zip code 67401", "KS"
57
- }
58
-
59
- SERVICE_HINTS = {
60
- "rdp": (3389, "Remote Desktop"),
61
- "ssh": (22, "OpenSSH"),
62
- "ftp": (21, "FTP"),
63
- "mongodb": (27017, "MongoDB"),
64
- "mysql": (3306, "MySQL"),
65
- "postgres": (5432, "PostgreSQL"),
66
- "jenkins": (8080, "Jenkins"),
67
- "elasticsearch": (9200, "Elasticsearch"),
68
- "kibana": (5601, "Kibana"),
69
- "redis": (6379, "Redis"),
70
- "rabbitmq": (15672, "RabbitMQ"),
71
- "rdp windows": (3389, "Windows RDP"),
72
- }
73
-
74
- def guess_country_iso2(text: str) -> Optional[str]:
75
- t = text.lower()
76
-
77
- # explicit "country:XX"
78
- m = re.search(r"\bcountry\s*:\s*([A-Za-z]{2})\b", t)
79
- if m:
80
- return m.group(1).upper()
81
-
82
- # country names (no "in" shortcut!)
83
- for name, iso2 in COUNTRY_NAME_TO_ISO2.items():
84
- if re.search(rf"\b{name}\b", t):
85
- return iso2
86
-
87
- # US states imply US
88
- if re.search(r"\b(" + "|".join(US_STATE_NAMES) + r")\b", t):
89
- return "US"
90
- if re.search(r"\b(" + "|".join(US_STATE_ABBREV) + r")\b", t):
91
- return "US"
92
- return None
93
-
94
- def guess_port(text: str) -> Optional[int]:
95
- m = re.search(r"\bport\s*[:=]?\s*(\d{1,5})\b", text, flags=re.I)
96
- return int(m.group(1)) if m else None
97
-
98
- def guess_service(text: str) -> Tuple[Optional[str], Optional[int]]:
99
- t = text.lower()
100
- for k, (p, _) in SERVICE_HINTS.items():
101
- if k in t:
102
- return (k, p)
103
- return (None, None)
104
-
105
- def guess_org(text: str) -> Optional[str]:
106
- m = re.search(r"\borg(?:anization)?\s*[:=]?\s*([A-Za-z0-9 _\-.&]+)", text, flags=re.I)
107
- return m.group(1).strip() if m else None
108
-
109
- def guess_asn(text: str) -> Optional[str]:
110
- m = re.search(r"\bAS(\d{1,10})\b", text, flags=re.I)
111
- return f"AS{m.group(1)}" if m else None
112
-
113
- def guess_city(text: str) -> Optional[str]:
114
- m = re.search(r"\bcity\s*[:=]?\s*([A-Za-z \-'.]+)\b", text, flags=re.I)
115
- return m.group(1).strip() if m else None
116
-
117
- def guess_os(text: str) -> Optional[str]:
118
- m = re.search(r"\b(windows\s?\d+|ubuntu|debian|centos|linux|macos|ios|android)\b", text, flags=re.I)
119
- return m.group(1) if m else None
120
-
121
- def guess_product(text: str) -> Optional[str]:
122
- for k, (_, prod) in SERVICE_HINTS.items():
123
- if k in text.lower():
124
- return prod
125
- m = re.search(r"\bproduct\s*[:=]?\s*([A-Za-z0-9 _\-.]+)", text, flags=re.I)
126
- return m.group(1).strip() if m else None
127
-
128
- def guess_title(text: str) -> Optional[str]:
129
- m = re.search(r'(?:title|intitle)\s*[:=]?\s*"([^"]+)"', text, flags=re.I)
130
- return m.group(1) if m else None
131
-
132
- def extract_zip(text: str) -> Optional[str]:
133
- """Return a 5-digit US ZIP if present; used only for display/JSON (not added to queries)."""
134
- m = re.search(r"\b(?:zip(?:\s*code)?|zipcode)?\s*[:=]?\s*(\d{5})(?:-\d{4})?\b", text, flags=re.I)
135
- return m.group(1) if m else None
136
-
137
- def nl_to_structured_heuristic(nl: str) -> Dict[str, Any]:
138
- iso2 = guess_country_iso2(nl) or ""
139
- port = guess_port(nl)
140
- _, svc_port = guess_service(nl)
141
- org = guess_org(nl) or ""
142
- asn = guess_asn(nl) or ""
143
- city = guess_city(nl) or ""
144
- os_name = guess_os(nl) or ""
145
- product = guess_product(nl) or ""
146
- title = guess_title(nl) or ""
147
- postal = extract_zip(nl) or ""
148
-
149
- if port is None and svc_port:
150
- port = svc_port
151
-
152
- # harvest terms but drop stopwords and very short noise
153
- raw_terms = re.findall(r"[A-Za-z0-9_.:/+-]{3,}", nl)
154
- terms = []
155
- for w in raw_terms:
156
- lw = w.lower()
157
- if lw in STOPWORDS:
158
- continue
159
- terms.append(w)
160
- terms = list(dict.fromkeys(terms))[:12]
161
-
162
- return {
163
- "terms": terms,
164
- "country_code": iso2,
165
- "city": city,
166
- "port": port,
167
- "product": product,
168
- "os": os_name,
169
- "org": org,
170
- "asn": asn,
171
- "title": title,
172
- "postal": postal, # kept for transparency; not injected into queries
173
- "filetype": "",
174
- "site": "",
175
- "after": "",
176
- "before": "",
177
- }
178
-
179
- # ----------------------------
180
- # LLM parsing via HF Inference API (model chosen in UI)
181
- # ----------------------------
182
-
183
- MODEL_CHOICES = {
184
- "No LLM (fastest, heuristic only)": {"id": "", "requires_token": False},
185
- "LLM 1 (token required) — TinyLlama-1.1B-Chat": {"id": "TinyLlama/TinyLlama-1.1B-Chat-v1.0", "requires_token": True},
186
- "LLM 2 (token required) — Phi-3-mini-4k": {"id": "microsoft/Phi-3-mini-4k-instruct", "requires_token": True},
187
- "LLM 3 (token required) — Qwen2.5-7B-Instruct": {"id": "Qwen/Qwen2.5-7B-Instruct", "requires_token": True},
188
- }
189
-
190
- PROMPT = """You are a JSON-only extractor. Convert the user's intent into fields for building search queries.
191
-
192
- Return a single JSON object with keys:
193
- terms (list of up to 10 short tokens),
194
- country_code (ISO-2 or empty),
195
- city (string),
196
- port (int or null),
197
- product (string),
198
- os (string),
199
- org (string),
200
- asn (like "AS15169" or empty),
201
- title (string),
202
- postal (string),
203
- filetype (string),
204
- site (string),
205
- after (YYYY-MM-DD or empty),
206
- before (YYYY-MM-DD or empty).
207
-
208
- User: {text}
209
- JSON:"""
210
-
211
- def call_hf_inference(model_id: str, token: str, text: str, timeout: int = 25) -> Optional[Dict[str, Any]]:
212
  try:
213
- headers = {"Authorization": f"Bearer {token}"} if token else {}
214
- payload = {"inputs": PROMPT.format(text=text),
215
- "parameters": {"max_new_tokens": 256, "temperature": 0.2, "return_full_text": False}}
216
- resp = requests.post(f"https://api-inference.huggingface.co/models/{model_id}",
217
- headers=headers, json=payload, timeout=timeout)
218
- resp.raise_for_status()
219
- data = resp.json()
220
- gen = (data[0]["generated_text"] if isinstance(data, list) and data and "generated_text" in data[0]
221
- else data.get("generated_text") if isinstance(data, dict) else json.dumps(data))
222
- m = re.search(r"\{.*\}", gen, flags=re.S)
223
- return json.loads(m.group(0)) if m else None
224
- except Exception:
225
- return None
226
-
227
- def nl_to_structured(nl: str, model_label: str, token: str) -> Dict[str, Any]:
228
- baseline = nl_to_structured_heuristic(nl)
229
- model_info = MODEL_CHOICES.get(model_label, {"id": "", "requires_token": False})
230
- model_id = model_info["id"]
231
- if not model_id:
232
- return baseline
233
- if model_info["requires_token"] and not token:
234
- return baseline
235
- parsed = call_hf_inference(model_id, token, nl)
236
- if isinstance(parsed, dict):
237
- merged = baseline.copy()
238
- for k in merged.keys():
239
- if k in parsed and parsed[k] not in (None, "", []):
240
- merged[k] = parsed[k]
241
- return merged
242
- return baseline
243
-
244
- # ----------------------------
245
- # Query builders
246
- # ----------------------------
247
-
248
- def build_shodan(params: Dict[str, Any]) -> str:
249
- parts = []
250
- terms = [t for t in params["terms"] if t.lower() not in STOPWORDS]
251
- parts += terms
252
- if params.get("country_code"): parts.append(f"country:{params['country_code']}")
253
- if params.get("city"): parts.append(f"city:{json.dumps(params['city'])}")
254
- if params.get("port"): parts.append(f"port:{params['port']}")
255
- if params.get("product"): parts.append(f'product:{json.dumps(params["product"])}')
256
- if params.get("os"): parts.append(f'os:{json.dumps(params["os"])}')
257
- if params.get("org"): parts.append(f'org:{json.dumps(params["org"])}')
258
- if params.get("asn"): parts.append(f"asn:{params['asn']}")
259
- if params.get("title"): parts.append(f'http.title:{json.dumps(params["title"])}')
260
- # Deliberately *not* inserting ZIP/postal to avoid unsupported filters
261
- return " ".join(parts).strip()
262
-
263
- def build_zoomeye(params: Dict[str, Any]) -> str:
264
- parts = []
265
- terms = [t for t in params["terms"] if t.lower() not in STOPWORDS]
266
- parts += terms
267
- if params.get("product"): parts.append(f'app:{json.dumps(params["product"])}')
268
- if params.get("port"): parts.append(f"port:{params['port']}")
269
- if params.get("os"): parts.append(f'os:{json.dumps(params["os"])}')
270
- if params.get("country_code"): parts.append(f"country:{params['country_code']}")
271
- if params.get("city"): parts.append(f'city:{json.dumps(params["city"])}')
272
- if params.get("org"): parts.append(f'org:{json.dumps(params["org"])}')
273
- if params.get("asn"): parts.append(f"asn:{params['asn']}")
274
- return " ".join(parts).strip()
275
-
276
- def build_google(params: Dict[str, Any]) -> str:
277
- parts = []
278
- if params.get("title"): parts.append(f'intitle:{json.dumps(params["title"])}')
279
- if params.get("site"): parts.append(f"site:{params['site']}")
280
- if params.get("filetype"): parts.append(f"filetype:{params['filetype']}")
281
- if params.get("after"): parts.append(f"after:{params['after']}")
282
- if params.get("before"): parts.append(f"before:{params['before']}")
283
- extras = []
284
- if params.get("product"): extras.append(params["product"])
285
- if params.get("os"): extras.append(params["os"])
286
- # include ZIP as plain term for Google only, if present
287
- if params.get("postal"): extras.append(params["postal"])
288
- for t in params["terms"][:8]:
289
- extras.append(t)
290
- parts += [json.dumps(x) if re.search(r"\s", x) else x for x in extras if x]
291
- return " ".join(parts).strip()
292
-
293
- def to_click_link(base: str, q: str) -> str:
294
- return f"{base}{urllib.parse.quote_plus(q)}" if q else ""
295
-
296
- # ----------------------------
297
- # Gradio callbacks
298
- # ----------------------------
299
-
300
- def build_queries(nl: str,
301
- country_code: str, city: str, port: str,
302
- product: str, os_name: str, org: str, asn: str,
303
- title: str, site: str, filetype: str, after: str, before: str,
304
- enable_shodan: bool, enable_zoomeye: bool, enable_google: bool,
305
- model_label: str, hf_token: str) -> Dict[str, str]:
306
-
307
- parsed = nl_to_structured(nl or "", model_label, hf_token or "")
308
-
309
- # UI overrides (explicit beats inferred)
310
- if country_code: parsed["country_code"] = country_code
311
- if city: parsed["city"] = city
312
- if port.strip().isdigit(): parsed["port"] = int(port.strip())
313
- if product: parsed["product"] = product
314
- if os_name: parsed["os"] = os_name
315
- if org: parsed["org"] = org
316
- if asn: parsed["asn"] = asn.upper() if not asn.upper().startswith("AS") else asn.upper()
317
- if title: parsed["title"] = title
318
- if site: parsed["site"] = site
319
- if filetype: parsed["filetype"] = filetype
320
- if after: parsed["after"] = after
321
- if before: parsed["before"] = before
322
-
323
- out = {}
324
- if enable_shodan:
325
- shodan_q = build_shodan(parsed)
326
- out["Shodan Query"] = shodan_q
327
- out["Open in Shodan"] = to_click_link("https://www.shodan.io/search?query=", shodan_q)
328
- if enable_zoomeye:
329
- zq = build_zoomeye(parsed)
330
- out["ZoomEye Query"] = zq
331
- out["Open in ZoomEye"] = to_click_link("https://www.zoomeye.org/searchResult?q=", zq)
332
- if enable_google:
333
- gq = build_google(parsed)
334
- out["Google Dork"] = gq
335
- out["Open in Google"] = to_click_link("https://www.google.com/search?q=", gq)
336
-
337
- out["Parsed (JSON)"] = json.dumps(parsed, indent=2)
338
- return out
339
-
340
- def on_model_change(selected_label: str):
341
- """Show token box only when a model that requires it is chosen. Clear token when not needed."""
342
- requires = MODEL_CHOICES.get(selected_label, {"requires_token": False})["requires_token"]
343
- if requires:
344
- return gr.update(visible=True), gr.update(value="")
345
- else:
346
- return gr.update(visible=False), gr.update(value="")
347
-
348
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
349
- gr.Markdown(f"# {APP_TITLE}")
350
- gr.Markdown(APP_DESC)
351
-
352
- with gr.Row():
353
- with gr.Column(scale=3):
354
- nl = gr.Textbox(label="Natural language", placeholder='e.g., "Find RDP in Germany for Org Contoso"', lines=3)
355
-
356
- with gr.Accordion("Structured inputs (optional overrides)", open=False):
357
- country = gr.Dropdown(
358
- label="Country (ISO-2)",
359
- choices=["", "US","DE","JP","BR","IN","GB","FR","CA","AU","NL","ES","IT","SE","CH"],
360
- value="",
361
- allow_custom_value=False
362
- )
363
- city = gr.Textbox(label="City")
364
- port = gr.Textbox(label="Port (number)")
365
- product = gr.Textbox(label="Product/App (e.g., Jenkins, MongoDB)")
366
- os_name = gr.Textbox(label="OS (e.g., Windows 10, Ubuntu)")
367
- org = gr.Textbox(label="Org")
368
- asn = gr.Textbox(label="ASN (e.g., AS15169)")
369
- title = gr.Textbox(label='Page Title contains (exact match)')
370
- with gr.Row():
371
- site = gr.Textbox(label="site: (Google only)")
372
- filetype = gr.Textbox(label="filetype: (Google only)")
373
- with gr.Row():
374
- after = gr.Textbox(label="after: YYYY-MM-DD (Google)")
375
- before = gr.Textbox(label="before: YYYY-MM-DD (Google)")
376
-
377
- gr.Markdown("### LLM (optional)")
378
- model_label = gr.Dropdown(
379
- label="Model",
380
- choices=list(MODEL_CHOICES.keys()),
381
- value="No LLM (fastest, heuristic only)",
382
- info="Choose an LLM to improve NL parsing. Token required for LLM options.",
383
- allow_custom_value=False
384
- )
385
- hf_token = gr.Textbox(
386
- label="Hugging Face API Token",
387
- type="password",
388
- placeholder="hf_...",
389
- visible=False
390
- )
391
-
392
- with gr.Row():
393
- enable_shodan = gr.Checkbox(value=True, label="Build Shodan query")
394
- enable_zoomeye = gr.Checkbox(value=True, label="Build ZoomEye query")
395
- enable_google = gr.Checkbox(value=True, label="Build Google dork")
396
-
397
- build_btn = gr.Button("Build Queries")
398
-
399
- with gr.Column(scale=4):
400
- # OUTPUTS ARE NOW READ-ONLY (non-editable) BUT COPYABLE
401
- shodan_out = gr.Textbox(label="Shodan Query", show_copy_button=True, interactive=False, lines=2)
402
- shodan_link = gr.Markdown()
403
- zoomeye_out = gr.Textbox(label="ZoomEye Query", show_copy_button=True, interactive=False, lines=2)
404
- zoomeye_link = gr.Markdown()
405
- google_out = gr.Textbox(label="Google Dork", show_copy_button=True, interactive=False, lines=2)
406
- google_link = gr.Markdown()
407
- parsed_json = gr.Code(label="Parsed (JSON)", interactive=False)
408
-
409
- model_label.change(on_model_change, inputs=[model_label], outputs=[hf_token, hf_token])
410
-
411
- def _on_build(nl, country, city, port, product, os_name, org, asn, title, site, filetype, after, before,
412
- en_shodan, en_zoomeye, en_google, model_label, hf_token):
413
- d = build_queries(
414
- nl, country, city, port, product, os_name, org, asn, title, site, filetype, after, before,
415
- en_shodan, en_zoomeye, en_google, model_label, hf_token
416
  )
417
- return (
418
- d.get("Shodan Query",""), f"[Open in Shodan]({d['Open in Shodan']})" if "Open in Shodan" in d else "",
419
- d.get("ZoomEye Query",""), f"[Open in ZoomEye]({d['Open in ZoomEye']})" if "Open in ZoomEye" in d else "",
420
- d.get("Google Dork",""), f"[Open in Google]({d['Open in Google']})" if "Open in Google" in d else "",
421
- d.get("Parsed (JSON)","{}")
 
 
 
 
 
 
 
 
 
 
 
 
422
  )
423
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  build_btn.click(
425
- _on_build,
426
- inputs=[nl, country, city, port, product, os_name, org, asn, title, site, filetype, after, before,
427
- enable_shodan, enable_zoomeye, enable_google, model_label, hf_token],
428
- outputs=[shodan_out, shodan_link, zoomeye_out, zoomeye_link, google_out, google_link, parsed_json]
429
  )
430
-
431
- nl.change(
432
- _on_build,
433
- inputs=[nl, country, city, port, product, os_name, org, asn, title, site, filetype, after, before,
434
- enable_shodan, enable_zoomeye, enable_google, model_label, hf_token],
435
- outputs=[shodan_out, shodan_link, zoomeye_out, zoomeye_link, google_out, google_link, parsed_json]
 
 
 
 
 
 
 
 
 
 
 
 
 
436
  )
437
 
438
- gr.Markdown("""
439
- ---
440
- **Notes & Docs**
441
-
442
- - Shodan filters: `country:DE`, `port:3389`, `org:"Contoso"`, `asn:AS15169`, `http.title:"XYZ"`.
443
- - ZoomEye fields: `app:"Jenkins"`, `port:8080`, `os:"Windows"`, `country:DE`, `city:"Berlin"`.
444
- - Google dorks: `site:`, `filetype:`, `inurl:`, `intitle:`, `after:`, `before:`.
445
-
446
- **Safety:** This Space creates strings only. No live scanning. Respect platform ToS and laws.
447
- """)
448
-
449
- if __name__ == "__main__":
450
- demo.launch()
 
 
 
 
1
  import gradio as gr
2
+ import orjson
3
  import requests
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Dict, List, Optional
7
+ from loguru import logger
8
+ from registry import Registry
9
+ from config import DATA_DIR, HF_TOKEN, SHODAN_API_KEY, ZOOMEYE_API_KEY, ENABLE_EXECUTE, RATE_LIMIT_PER_MIN, OWNER_PIN, LLM_MODE, ALLOWED_MODELS, THEME, COUNTRIES
10
+
11
+ DATA_DIR.mkdir(exist_ok=True)
12
+ logger.add(DATA_DIR / "app.log", rotation="1 MB", retention="7 days")
13
+ registry = Registry()
14
+ session_counts: Dict[str, List[float]] = {}
15
+
16
+ def rate_limit(session_id: str) -> bool:
17
+ now = time.time()
18
+ if session_id not in session_counts:
19
+ session_counts[session_id] = []
20
+ session_counts[session_id] = [t for t in session_counts[session_id] if now - t < 60]
21
+ if len(session_counts[session_id]) >= RATE_LIMIT_PER_MIN:
22
+ return False
23
+ session_counts[session_id].append(now)
24
+ return True
25
+
26
+ def build_query(query: str, platform: str, session_id: str) -> str:
27
+ if not rate_limit(session_id):
28
+ return "Rate limit exceeded. Please try again later."
29
+ parsed = registry.parse_query(query)
30
+ intent = parsed["intents"][0] if parsed["intents"] else None
31
+ service = parsed["services"][0] if parsed["services"] else None
32
+ base_query = []
33
+ if intent and intent in registry.store.intents:
34
+ templates = registry.store.intents[intent].platform_templates.get(platform, [])
35
+ defaults = registry.store.intents[intent].defaults
36
+ for template in templates:
37
+ formatted = template.format(**defaults)
38
+ base_query.append(formatted)
39
+ elif service:
40
+ for s in registry.store.services:
41
+ if s["name"] == service:
42
+ base_query.append(f'port:{s["port"]}' if s.get("port") else f'product:"{s["name"]}"')
43
+ for key, value in parsed["filters"].items():
44
+ if key in registry.store.operators:
45
+ base_query.append(f'{key}:"{value}"')
46
+ sanitized_query = registry.sanitize_operators(" ".join(base_query + parsed["operators"]))
47
+ logger.info(f"Built {platform} query: {sanitized_query}")
48
+ return sanitized_query
49
+
50
+ def exec_shodan(query: str, session_id: str) -> str:
51
+ if not ENABLE_EXECUTE or not SHODAN_API_KEY:
52
+ return "Shodan execution is disabled or API key is missing."
53
+ if not rate_limit(session_id):
54
+ return "Rate limit exceeded. Please try again later."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  try:
56
+ response = requests.get(
57
+ f"https://api.shodan.io/shodan/host/search?key={SHODAN_API_KEY}&query={query}",
58
+ timeout=10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  )
60
+ response.raise_for_status()
61
+ data = response.json()
62
+ logger.info(f"Shodan query executed: {query}")
63
+ return orjson.dumps(data, option=orjson.OPT_INDENT_2).decode()
64
+ except Exception as e:
65
+ logger.error(f"Shodan query failed: {e}")
66
+ return f"Error: {e}"
67
+
68
+ def exec_zoomeye(query: str, session_id: str) -> str:
69
+ if not ENABLE_EXECUTE or not ZOOMEYE_API_KEY:
70
+ return "ZoomEye execution is disabled or API key is missing."
71
+ if not rate_limit(session_id):
72
+ return "Rate limit exceeded. Please try again later."
73
+ try:
74
+ response = requests.get(
75
+ f"https://api.zoomeye.org/host/search?query={query}&key={ZOOMEYE_API_KEY}",
76
+ timeout=10
77
  )
78
+ response.raise_for_status()
79
+ data = response.json()
80
+ logger.info(f"ZoomEye query executed: {query}")
81
+ return orjson.dumps(data, option=orjson.OPT_INDENT_2).decode()
82
+ except Exception as e:
83
+ logger.error(f"ZoomEye query failed: {e}")
84
+ return f"Error: {e}"
85
+
86
+ def query_llm(query: str, model: str) -> str:
87
+ if model == "No LLM" or not HF_TOKEN:
88
+ return "LLM is disabled or HF token is missing."
89
+ try:
90
+ response = requests.post(
91
+ f"https://api-inference.huggingface.co/models/{model}",
92
+ headers={"Authorization": f"Bearer {HF_TOKEN}"},
93
+ json={"inputs": query},
94
+ timeout=10
95
+ )
96
+ response.raise_for_status()
97
+ data = response.json()
98
+ logger.info(f"LLM query executed: {query} with model {model}")
99
+ return data[0]["generated_text"] if isinstance(data, list) else str(data)
100
+ except Exception as e:
101
+ logger.error(f"LLM query failed: {e}")
102
+ return f"Error: {e}"
103
+
104
+ def process_batch(queries: str, platform: str, session_id: str) -> List[Dict[str, str]]:
105
+ results = []
106
+ for query in queries.split("\n"):
107
+ query = query.strip()
108
+ if query:
109
+ built_query = build_query(query, platform, session_id)
110
+ results.append({"query": query, "built_query": built_query})
111
+ return results
112
+
113
+ def ingest_data(url: str, pin: str) -> str:
114
+ if pin != OWNER_PIN:
115
+ return "Invalid owner PIN."
116
+ registry.ingest_from_url(url)
117
+ return f"Data ingested from {url}"
118
+
119
+ with gr.Blocks(theme=THEME, css=".gradio-container { background-color: black; color: crimson; }") as app:
120
+ gr.Markdown("# DorkForge v3.2")
121
+ with gr.Row():
122
+ with gr.Column():
123
+ query_input = gr.Textbox(label="Enter Query", placeholder="e.g., Grafana in Germany port:3000")
124
+ platform = gr.Dropdown(choices=["google", "shodan", "zoomeye"], label="Platform", value="google")
125
+ build_btn = gr.Button("Build Query")
126
+ execute_btn = gr.Button("Execute Query")
127
+ query_output = gr.Textbox(label="Built Query", interactive=False)
128
+ result_output = gr.JSON(label="Query Result")
129
+ with gr.Column():
130
+ batch_input = gr.Textbox(label="Batch Queries (one per line)", lines=5)
131
+ batch_platform = gr.Dropdown(choices=["google", "shodan", "zoomeye"], label="Batch Platform", value="google")
132
+ batch_btn = gr.Button("Process Batch")
133
+ batch_output = gr.Dataframe(label="Batch Results")
134
+ with gr.Row():
135
+ with gr.Column():
136
+ llm_model = gr.Dropdown(choices=ALLOWED_MODELS, label="LLM Model", value=LLM_MODE)
137
+ llm_btn = gr.Button("Query LLM")
138
+ llm_output = gr.Textbox(label="LLM Response", interactive=False)
139
+ with gr.Row():
140
+ with gr.Accordion("Owner Controls", open=False):
141
+ ingest_url = gr.Textbox(label="Ingest URL", placeholder="e.g., https://example.com/data.json")
142
+ owner_pin = gr.Textbox(label="Owner PIN", type="password")
143
+ ingest_btn = gr.Button("Ingest Data")
144
+ ingest_output = gr.Textbox(label="Ingest Result", interactive=False)
145
+
146
  build_btn.click(
147
+ fn=build_query,
148
+ inputs=[query_input, platform, gr.State(value="session_id")],
149
+ outputs=query_output
 
150
  )
151
+ execute_btn.click(
152
+ fn=lambda q, p, s: exec_shodan(q, s) if p == "shodan" else exec_zoomeye(q, s),
153
+ inputs=[query_output, platform, gr.State(value="session_id")],
154
+ outputs=result_output
155
+ )
156
+ batch_btn.click(
157
+ fn=process_batch,
158
+ inputs=[batch_input, batch_platform, gr.State(value="session_id")],
159
+ outputs=batch_output
160
+ )
161
+ llm_btn.click(
162
+ fn=query_llm,
163
+ inputs=[query_input, llm_model],
164
+ outputs=llm_output
165
+ )
166
+ ingest_btn.click(
167
+ fn=ingest_data,
168
+ inputs=[ingest_url, owner_pin],
169
+ outputs=ingest_output
170
  )
171
 
172
+ app.launch()