CrypticallyRequie commited on
Commit
56921f3
·
verified ·
1 Parent(s): 7af0e1f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1012 -938
app.py CHANGED
@@ -1,426 +1,984 @@
1
  """
2
- ShadowWatch - Dark Web Intelligence Platform
3
- MCP-enabled Gradio Space for threat intelligence and monitoring
 
 
 
 
 
 
 
4
 
5
  By Cogensec | ARGUS Platform
6
  """
7
 
8
  import gradio as gr
9
- import json
 
10
  import hashlib
11
- import random
12
- import time
 
 
13
  from datetime import datetime, timedelta
14
- from typing import Optional
 
 
 
 
15
 
16
  # ============================================================================
17
- # MCP TOOLS - These become callable tools for LLM clients
18
  # ============================================================================
19
 
20
- def deep_scan(target: str, scan_type: str = "comprehensive") -> dict:
21
- """Perform a deep scan of dark web sources for threat intelligence.
22
 
23
- Crawls marketplaces, forums, paste sites, and channels for mentions
24
- of the specified target (domain, company, email pattern, etc.)
 
 
 
 
 
 
 
 
 
 
25
 
26
- Args:
27
- target: The target to scan for (domain, company name, email pattern, IP range)
28
- scan_type: Type of scan - "quick" (5 sources), "standard" (25 sources), or "comprehensive" (50+ sources)
 
 
 
 
 
 
 
 
 
 
 
29
 
30
- Returns:
31
- JSON object with scan_id, sources_crawled, findings summary,
32
- threat_indicators, and recommended_actions
33
- """
34
- scan_id = hashlib.sha256(f"{target}{datetime.now().isoformat()}".encode()).hexdigest()[:12]
 
 
 
 
 
 
 
 
 
 
35
 
36
- sources = {
37
- "quick": {"marketplaces": 2, "forums": 2, "paste_sites": 1, "channels": 0},
38
- "standard": {"marketplaces": 8, "forums": 10, "paste_sites": 5, "channels": 2},
39
- "comprehensive": {"marketplaces": 15, "forums": 20, "paste_sites": 10, "channels": 8}
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- source_counts = sources.get(scan_type, sources["standard"])
43
- total_sources = sum(source_counts.values())
44
-
45
- # Simulated findings based on target
46
- findings = []
47
- threat_level = "low"
48
-
49
- if "@" in target or "." in target:
50
- # Email or domain - check for credential exposure
51
- leak_count = random.randint(0, 500)
52
- if leak_count > 0:
53
- findings.append({
54
- "type": "credential_exposure",
55
- "source": "dark_market_db",
56
- "count": leak_count,
57
- "severity": "high" if leak_count > 100 else "medium",
58
- "details": f"Found {leak_count} credential records matching pattern"
59
- })
60
- threat_level = "high" if leak_count > 100 else "elevated"
61
-
62
- mention_count = random.randint(0, 50)
63
- if mention_count > 10:
64
- findings.append({
65
- "type": "chatter_mention",
66
- "source": "forum_analysis",
67
- "count": mention_count,
68
- "severity": "medium",
69
- "details": f"Target mentioned in {mention_count} forum threads"
70
- })
71
- if threat_level == "low":
72
- threat_level = "moderate"
73
-
74
- paste_hits = random.randint(0, 20)
75
- if paste_hits > 0:
76
- findings.append({
77
- "type": "paste_site_exposure",
78
- "source": "paste_monitoring",
79
- "count": paste_hits,
80
- "severity": "high" if paste_hits > 5 else "low",
81
- "details": f"Found {paste_hits} paste entries containing target data"
82
- })
83
 
84
- return {
85
- "scan_id": scan_id,
86
- "target": target,
87
- "scan_type": scan_type,
88
- "timestamp": datetime.now().isoformat(),
89
- "sources_crawled": {
90
- "total": total_sources,
91
- "breakdown": source_counts
92
- },
93
- "threat_level": threat_level,
94
- "findings_count": len(findings),
95
- "findings": findings,
96
- "recommended_actions": [
97
- "Review exposed credentials and force password resets",
98
- "Enable monitoring alerts for this target",
99
- "Consider takedown requests for sensitive exposures"
100
- ] if findings else ["No immediate action required", "Continue routine monitoring"]
101
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
 
104
- def credential_trace(identifier: str, search_depth: str = "standard") -> dict:
105
- """Trace credential exposure across dark web databases and breach compilations.
106
-
107
- Searches known breach databases, combolists, and credential markets
108
- for exposure of the specified email, username, or domain pattern.
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
- Args:
111
- identifier: Email address, username, or domain pattern to trace
112
- search_depth: "surface" (recent breaches), "standard" (2 years), or "deep" (all known)
 
 
 
113
 
114
- Returns:
115
- JSON object with exposure_summary, breach_list, risk_score,
116
- password_patterns detected, and remediation steps
117
- """
118
- trace_id = hashlib.sha256(f"trace_{identifier}".encode()).hexdigest()[:10]
119
-
120
- # Simulated breach data
121
- known_breaches = [
122
- {"name": "Collection #1", "date": "2019-01", "records": "773M"},
123
- {"name": "LinkedIn 2021", "date": "2021-06", "records": "700M"},
124
- {"name": "Facebook 2019", "date": "2019-04", "records": "533M"},
125
- {"name": "Exactis", "date": "2018-06", "records": "340M"},
126
- {"name": "Apollo", "date": "2018-07", "records": "126M"},
127
- ]
128
-
129
- # Randomly select some breaches for demo
130
- exposed_in = random.sample(known_breaches, k=random.randint(0, 4))
131
-
132
- risk_score = min(100, len(exposed_in) * 25 + random.randint(0, 20))
133
-
134
- password_patterns = []
135
- if exposed_in:
136
- password_patterns = [
137
- {"pattern": "plaintext", "instances": random.randint(1, 5)},
138
- {"pattern": "md5_hash", "instances": random.randint(0, 3)},
139
- {"pattern": "bcrypt", "instances": random.randint(0, 2)},
140
- ]
141
- password_patterns = [p for p in password_patterns if p["instances"] > 0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
 
 
 
 
 
 
143
  return {
144
- "trace_id": trace_id,
145
- "identifier": identifier,
146
- "search_depth": search_depth,
147
- "timestamp": datetime.now().isoformat(),
148
- "exposure_summary": {
149
- "total_breaches": len(exposed_in),
150
- "earliest_exposure": exposed_in[0]["date"] if exposed_in else None,
151
- "risk_score": risk_score,
152
- "risk_level": "critical" if risk_score > 75 else "high" if risk_score > 50 else "moderate" if risk_score > 25 else "low"
153
- },
154
- "breach_list": exposed_in,
155
- "password_patterns": password_patterns,
156
- "remediation": [
157
- "Immediately change passwords on all associated accounts",
158
- "Enable 2FA/MFA on all critical services",
159
- "Monitor for unauthorized access attempts",
160
- "Consider identity monitoring services"
161
- ] if exposed_in else ["No known exposures found", "Maintain strong password hygiene"]
162
  }
163
 
164
 
165
- def chatter_analysis(keywords: str, timeframe_hours: int = 24) -> dict:
166
- """Analyze dark web chatter for specific keywords or entities.
 
 
 
 
 
 
 
167
 
168
- Monitors forums, markets, and communication channels for discussions
169
- involving the specified keywords to detect emerging threats.
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
- Args:
172
- keywords: Comma-separated keywords to monitor (company names, product names, executives)
173
- timeframe_hours: How far back to analyze (1-168 hours)
174
-
175
- Returns:
176
- JSON object with mention_count, sentiment_analysis, threat_indicators,
177
- top_sources, and sample_contexts
178
- """
179
- keyword_list = [k.strip() for k in keywords.split(",")]
180
- analysis_id = hashlib.sha256(f"chatter_{keywords}".encode()).hexdigest()[:10]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
- mention_count = random.randint(5, 200)
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
- # Simulated sentiment breakdown
185
- sentiment = {
186
- "hostile": random.randint(0, 30),
187
- "suspicious": random.randint(10, 40),
188
- "neutral": random.randint(20, 50),
189
- "commercial": random.randint(5, 25) # Selling data/access
 
 
 
 
 
 
190
  }
191
 
192
- threat_indicators = []
193
- if sentiment["hostile"] > 20:
194
- threat_indicators.append({
195
- "type": "targeted_discussion",
196
- "severity": "high",
197
- "detail": "Elevated hostile mentions detected"
198
- })
199
- if sentiment["commercial"] > 15:
200
- threat_indicators.append({
201
- "type": "data_sale_indicators",
202
- "severity": "critical",
203
- "detail": "Potential sale of company data/access detected"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  })
205
 
206
- top_sources = [
207
- {"source": "RaidForums successor", "mentions": random.randint(10, 50)},
208
- {"source": "Telegram channels", "mentions": random.randint(5, 30)},
209
- {"source": "Russian forums", "mentions": random.randint(2, 20)},
210
- ]
 
 
 
 
 
 
 
 
211
 
212
- return {
213
- "analysis_id": analysis_id,
214
- "keywords": keyword_list,
215
- "timeframe_hours": timeframe_hours,
216
- "timestamp": datetime.now().isoformat(),
217
- "mention_count": mention_count,
218
- "trend": f"+{random.randint(5, 80)}%" if random.random() > 0.3 else f"-{random.randint(5, 30)}%",
219
- "sentiment_breakdown": sentiment,
220
- "threat_indicators": threat_indicators,
221
- "threat_level": "critical" if len(threat_indicators) > 1 else "elevated" if threat_indicators else "normal",
222
- "top_sources": top_sources,
223
- "recommended_actions": [
224
- "Increase monitoring frequency",
225
- "Alert security team",
226
- "Prepare incident response"
227
- ] if threat_indicators else ["Continue routine monitoring"]
228
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
 
231
- def identity_alert(name: str, additional_identifiers: Optional[str] = None) -> dict:
232
- """Check for identity exposure and impersonation attempts.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
- Searches for PII exposure, fake profiles, impersonation attempts,
235
- and social engineering setups targeting the specified identity.
 
 
 
 
 
 
 
 
 
 
236
 
237
  Args:
238
- name: Full name of the person to check
239
- additional_identifiers: Optional comma-separated identifiers (email, phone, social handles)
240
 
241
  Returns:
242
- JSON object with exposure_findings, impersonation_alerts,
243
- social_engineering_indicators, and protection_recommendations
244
  """
245
- alert_id = hashlib.sha256(f"identity_{name}".encode()).hexdigest()[:10]
 
 
 
246
 
247
- identifiers = [name]
248
- if additional_identifiers:
249
- identifiers.extend([i.strip() for i in additional_identifiers.split(",")])
250
 
251
- exposure_findings = []
 
 
 
 
 
 
 
252
 
253
- # Check for PII exposure
254
- if random.random() > 0.4:
255
- exposure_findings.append({
256
- "type": "pii_exposure",
257
- "data_types": random.sample(["email", "phone", "address", "ssn_partial", "dob"], k=random.randint(1, 3)),
258
- "source": "data_broker_leak",
259
- "severity": "high"
260
- })
261
 
262
- # Check for impersonation
263
- impersonation_alerts = []
264
- if random.random() > 0.6:
265
- impersonation_alerts.append({
266
- "platform": random.choice(["LinkedIn", "Twitter", "Facebook", "Instagram"]),
267
- "type": "fake_profile",
268
- "confidence": f"{random.randint(70, 95)}%",
269
- "created": (datetime.now() - timedelta(days=random.randint(1, 90))).strftime("%Y-%m-%d")
270
- })
271
 
272
- # Social engineering indicators
273
- se_indicators = []
274
- if random.random() > 0.5:
275
- se_indicators.append({
276
- "type": "phishing_domain",
277
- "detail": f"Domain registered resembling target organization",
278
- "registered": (datetime.now() - timedelta(days=random.randint(1, 30))).strftime("%Y-%m-%d")
279
- })
280
 
281
- risk_score = len(exposure_findings) * 30 + len(impersonation_alerts) * 25 + len(se_indicators) * 20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
 
283
- return {
284
- "alert_id": alert_id,
285
- "identity": name,
286
- "identifiers_checked": identifiers,
287
- "timestamp": datetime.now().isoformat(),
288
- "risk_score": min(100, risk_score),
289
- "exposure_findings": exposure_findings,
290
- "impersonation_alerts": impersonation_alerts,
291
- "social_engineering_indicators": se_indicators,
292
- "protection_recommendations": [
293
- "Set up identity monitoring alerts",
294
- "Report fake profiles for takedown",
295
- "Brief the individual on social engineering risks",
296
- "Consider executive protection services"
297
- ] if (exposure_findings or impersonation_alerts) else ["No immediate threats detected", "Continue periodic monitoring"]
298
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
 
301
- def generate_threat_report(organization: str, report_type: str = "executive") -> str:
302
- """Generate a comprehensive threat intelligence report.
303
 
304
- Creates a detailed report on the current threat landscape for
305
- the specified organization based on ShadowWatch monitoring data.
 
306
 
307
  Args:
308
- organization: Organization name to generate report for
309
- report_type: "executive" (summary), "technical" (detailed), or "incident" (specific event)
310
 
311
  Returns:
312
- Formatted markdown threat intelligence report
313
  """
314
- report_id = hashlib.sha256(f"report_{organization}{datetime.now().isoformat()}".encode()).hexdigest()[:8]
 
 
 
315
 
316
- report = f"""# 🛡️ SHADOWWATCH THREAT INTELLIGENCE REPORT
317
-
318
- **Organization:** {organization}
319
- **Report ID:** {report_id.upper()}
320
- **Classification:** CONFIDENTIAL
321
- **Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}
322
- **Report Type:** {report_type.upper()}
323
-
324
- ---
325
-
326
- ## EXECUTIVE SUMMARY
327
-
328
- ShadowWatch has been monitoring dark web activity related to **{organization}** across 47 marketplaces, 156 forums, and 89 communication channels.
329
-
330
- ### Current Threat Level: ⚠️ ELEVATED
331
-
332
- | Metric | Value | Trend |
333
- |--------|-------|-------|
334
- | Total Mentions (24h) | 127 | +23% |
335
- | Credential Exposures | 2,847 | +156 |
336
- | Active Threat Actors | 3 | Stable |
337
- | Data Sale Listings | 1 | NEW |
338
-
339
- ---
340
-
341
- ## ACTIVE THREATS
342
-
343
- ### 🔴 CRITICAL: Credential Database Listing
344
-
345
- **Detected:** {(datetime.now() - timedelta(hours=random.randint(1, 12))).strftime('%Y-%m-%d %H:%M')} UTC
346
- **Source:** Underground marketplace (Tier 1)
347
- **Details:** Threat actor "darkphantom_x" listed database claiming to contain {organization} employee credentials. Listing indicates 26,752 records including email/password combinations.
348
-
349
- **Recommended Actions:**
350
- - Immediate password reset for all employees
351
- - Enable MFA enforcement
352
- - Monitor for unauthorized access attempts
353
-
354
- ### 🟡 WARNING: Increased Forum Chatter
355
-
356
- **Detected:** Last 48 hours
357
- **Sources:** 3 Russian-language forums, 2 English forums
358
- **Details:** 72% increase in mentions of {organization} in hacking forums. Discussion topics include network reconnaissance and vulnerability scanning.
359
-
360
- ### 🟢 MONITORING: Phishing Infrastructure
361
-
362
- **Detected:** {(datetime.now() - timedelta(days=random.randint(1, 7))).strftime('%Y-%m-%d')}
363
- **Details:** 2 domains registered with similarity to {organization} primary domain. Currently parked but warrant monitoring.
364
-
365
- ---
366
-
367
- ## THREAT ACTOR PROFILES
368
-
369
- ### darkphantom_x
370
- - **First Seen:** 2023-04
371
- - **Reputation:** Established seller (47 positive reviews)
372
- - **Specialization:** Corporate credential dumps
373
- - **Risk Level:** HIGH
374
-
375
- ### APT-SHADOW-7 (Suspected Nation-State)
376
- - **Attribution:** Medium confidence
377
- - **Known Targets:** Financial sector, Government contractors
378
- - **TTPs:** Spearphishing, Supply chain compromise
379
- - **Risk Level:** CRITICAL
380
-
381
- ---
382
-
383
- ## RECOMMENDATIONS
384
-
385
- 1. **Immediate (24-48 hours)**
386
- - Force password resets for potentially exposed accounts
387
- - Brief security team on elevated threat status
388
- - Increase monitoring of authentication logs
389
-
390
- 2. **Short-term (1-2 weeks)**
391
- - Conduct phishing simulation to test employee awareness
392
- - Review and harden external-facing systems
393
- - Engage takedown services for fraudulent domains
394
-
395
- 3. **Ongoing**
396
- - Maintain elevated monitoring posture
397
- - Weekly threat briefings for security leadership
398
- - Consider threat intelligence sharing with industry peers
399
-
400
- ---
401
-
402
- ## MONITORING CONFIGURATION
403
-
404
- | Source Type | Count | Status |
405
- |-------------|-------|--------|
406
- | Dark Web Markets | 15 | ✅ Active |
407
- | Hacking Forums | 23 | ✅ Active |
408
- | Paste Sites | 12 | ✅ Active |
409
- | Telegram Channels | 8 | ✅ Active |
410
- | IRC Channels | 4 | ✅ Active |
411
-
412
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
 
414
- *Report generated by ShadowWatch | Cogensec ARGUS Platform*
415
- *For questions: intel@cogensec.ai | 24/7 SOC: +1-XXX-XXX-XXXX*
416
 
417
- **CONFIDENTIAL - DO NOT DISTRIBUTE**
418
- """
419
- return report
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
 
421
 
422
  # ============================================================================
423
- # CUSTOM CSS - Matching the Dark Web Intelligence aesthetic
424
  # ============================================================================
425
 
426
  custom_css = """
@@ -429,23 +987,13 @@ custom_css = """
429
  :root {
430
  --sw-bg-dark: #0a0f1a;
431
  --sw-bg-card: #111827;
432
- --sw-bg-card-hover: #1a2332;
433
  --sw-border: #1e3a5f;
434
  --sw-cyan: #00ffd5;
435
- --sw-cyan-dim: #00b396;
436
- --sw-yellow: #fbbf24;
437
- --sw-red: #ef4444;
438
- --sw-orange: #f97316;
439
- --sw-green: #22c55e;
440
- --sw-text: #e2e8f0;
441
- --sw-text-dim: #64748b;
442
  }
443
 
444
- /* Global styles */
445
  .gradio-container {
446
  background: var(--sw-bg-dark) !important;
447
  font-family: 'Inter', sans-serif !important;
448
- max-width: 100% !important;
449
  }
450
 
451
  .dark {
@@ -454,270 +1002,13 @@ custom_css = """
454
  --border-color-primary: var(--sw-border) !important;
455
  }
456
 
457
- /* Header styling */
458
- .header-bar {
459
- background: linear-gradient(90deg, var(--sw-bg-card) 0%, #0d1520 100%);
460
- border: 1px solid var(--sw-border);
461
- border-radius: 8px;
462
- padding: 16px 24px;
463
- margin-bottom: 20px;
464
- display: flex;
465
- justify-content: space-between;
466
- align-items: center;
467
- }
468
-
469
- .logo-section {
470
- display: flex;
471
- align-items: center;
472
- gap: 12px;
473
- }
474
-
475
- .logo-icon {
476
- width: 40px;
477
- height: 40px;
478
- background: var(--sw-cyan);
479
- border-radius: 8px;
480
- display: flex;
481
- align-items: center;
482
- justify-content: center;
483
- }
484
-
485
- .logo-text {
486
- font-family: 'JetBrains Mono', monospace;
487
- font-size: 24px;
488
- font-weight: 700;
489
- color: white;
490
- letter-spacing: 2px;
491
- }
492
-
493
- .logo-subtitle {
494
- font-size: 11px;
495
- color: var(--sw-text-dim);
496
- letter-spacing: 3px;
497
- text-transform: uppercase;
498
- }
499
-
500
- .status-indicators {
501
- display: flex;
502
- gap: 24px;
503
- align-items: center;
504
- font-family: 'JetBrains Mono', monospace;
505
- font-size: 12px;
506
- }
507
-
508
- .status-item {
509
- display: flex;
510
- align-items: center;
511
- gap: 8px;
512
- }
513
-
514
- .status-dot {
515
- width: 8px;
516
- height: 8px;
517
- border-radius: 50%;
518
- background: var(--sw-green);
519
- box-shadow: 0 0 8px var(--sw-green);
520
- animation: pulse 2s infinite;
521
- }
522
-
523
- @keyframes pulse {
524
- 0%, 100% { opacity: 1; }
525
- 50% { opacity: 0.5; }
526
- }
527
-
528
- .status-label {
529
- color: var(--sw-text-dim);
530
- }
531
-
532
- .status-value {
533
- color: var(--sw-cyan);
534
- }
535
-
536
- .killswitch-btn {
537
- background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
538
- color: white;
539
- border: none;
540
- padding: 8px 16px;
541
- border-radius: 6px;
542
- font-family: 'JetBrains Mono', monospace;
543
- font-weight: 600;
544
- cursor: pointer;
545
- display: flex;
546
- align-items: center;
547
- gap: 8px;
548
- }
549
-
550
- /* Card styling */
551
- .info-card {
552
- background: var(--sw-bg-card);
553
- border: 1px solid var(--sw-border);
554
- border-radius: 8px;
555
- padding: 20px;
556
- margin-bottom: 16px;
557
- }
558
-
559
- .card-header {
560
- font-family: 'JetBrains Mono', monospace;
561
- font-size: 14px;
562
- font-weight: 600;
563
- color: var(--sw-cyan);
564
- letter-spacing: 2px;
565
- text-transform: uppercase;
566
- margin-bottom: 16px;
567
- display: flex;
568
- align-items: center;
569
- gap: 8px;
570
- }
571
-
572
- /* Terminal styling */
573
- .terminal-output {
574
- background: #000 !important;
575
- border: 1px solid var(--sw-border);
576
- border-radius: 6px;
577
- padding: 16px;
578
- font-family: 'JetBrains Mono', monospace !important;
579
- font-size: 13px;
580
- line-height: 1.6;
581
- color: var(--sw-green);
582
- max-height: 300px;
583
- overflow-y: auto;
584
- }
585
-
586
- .terminal-output .error {
587
- color: var(--sw-red);
588
- }
589
-
590
- .terminal-output .warning {
591
- color: var(--sw-yellow);
592
- }
593
-
594
- .terminal-output .info {
595
- color: var(--sw-cyan);
596
- }
597
-
598
- /* Threat level meter */
599
- .threat-meter {
600
- background: var(--sw-bg-card);
601
- border: 1px solid var(--sw-border);
602
- border-radius: 8px;
603
- padding: 20px;
604
- }
605
-
606
- .threat-bar {
607
- height: 8px;
608
- background: linear-gradient(90deg, var(--sw-green) 0%, var(--sw-yellow) 50%, var(--sw-red) 100%);
609
- border-radius: 4px;
610
- margin: 12px 0;
611
- position: relative;
612
- }
613
-
614
- .threat-indicator {
615
- position: absolute;
616
- top: -4px;
617
- width: 16px;
618
- height: 16px;
619
- background: white;
620
- border-radius: 50%;
621
- border: 2px solid var(--sw-bg-dark);
622
- transform: translateX(-50%);
623
- }
624
-
625
- .threat-labels {
626
- display: flex;
627
- justify-content: space-between;
628
- font-size: 11px;
629
- color: var(--sw-text-dim);
630
- text-transform: uppercase;
631
- letter-spacing: 1px;
632
- }
633
-
634
- /* Alert badges */
635
- .badge {
636
- display: inline-block;
637
- padding: 4px 12px;
638
- border-radius: 4px;
639
- font-family: 'JetBrains Mono', monospace;
640
- font-size: 11px;
641
- font-weight: 600;
642
- letter-spacing: 1px;
643
- }
644
-
645
- .badge-critical {
646
- background: var(--sw-red);
647
- color: white;
648
- }
649
-
650
- .badge-warning {
651
- background: var(--sw-orange);
652
- color: white;
653
- }
654
-
655
- .badge-elevated {
656
- background: var(--sw-yellow);
657
- color: black;
658
- }
659
-
660
- .badge-normal {
661
- background: var(--sw-green);
662
- color: white;
663
- }
664
-
665
- /* Button styling */
666
- .action-btn {
667
- background: var(--sw-bg-card) !important;
668
- border: 1px solid var(--sw-border) !important;
669
- color: var(--sw-text) !important;
670
- font-family: 'JetBrains Mono', monospace !important;
671
- padding: 12px 20px !important;
672
- border-radius: 6px !important;
673
- transition: all 0.2s !important;
674
- }
675
-
676
- .action-btn:hover {
677
- background: var(--sw-bg-card-hover) !important;
678
- border-color: var(--sw-cyan) !important;
679
- }
680
-
681
- .primary-btn {
682
- background: linear-gradient(135deg, var(--sw-cyan-dim) 0%, #007a6a 100%) !important;
683
- border: none !important;
684
- color: white !important;
685
- }
686
-
687
- /* Input styling */
688
- .input-field input, .input-field textarea {
689
- background: var(--sw-bg-dark) !important;
690
- border: 1px solid var(--sw-border) !important;
691
- color: var(--sw-text) !important;
692
- font-family: 'JetBrains Mono', monospace !important;
693
- }
694
-
695
- .input-field input:focus, .input-field textarea:focus {
696
- border-color: var(--sw-cyan) !important;
697
- box-shadow: 0 0 0 2px rgba(0, 255, 213, 0.1) !important;
698
- }
699
-
700
- /* Tab styling */
701
- .tabs {
702
- border: none !important;
703
- }
704
-
705
- .tab-nav {
706
- background: var(--sw-bg-card) !important;
707
- border: 1px solid var(--sw-border) !important;
708
- border-radius: 8px !important;
709
- padding: 4px !important;
710
- margin-bottom: 20px !important;
711
- }
712
 
713
  .tab-nav button {
714
  font-family: 'JetBrains Mono', monospace !important;
715
  font-size: 12px !important;
716
  letter-spacing: 1px !important;
717
  text-transform: uppercase !important;
718
- color: var(--sw-text-dim) !important;
719
- border-radius: 6px !important;
720
- padding: 12px 20px !important;
721
  }
722
 
723
  .tab-nav button.selected {
@@ -725,90 +1016,27 @@ custom_css = """
725
  color: var(--sw-bg-dark) !important;
726
  }
727
 
728
- /* JSON output styling */
729
- .json-output {
730
- background: #000 !important;
731
- border: 1px solid var(--sw-border) !important;
732
- border-radius: 6px !important;
733
- font-family: 'JetBrains Mono', monospace !important;
734
- }
735
-
736
- /* Markdown styling */
737
- .markdown-body {
738
- background: var(--sw-bg-card) !important;
739
- color: var(--sw-text) !important;
740
- font-family: 'Inter', sans-serif !important;
741
- }
742
-
743
- .markdown-body h1, .markdown-body h2, .markdown-body h3 {
744
- color: var(--sw-cyan) !important;
745
  font-family: 'JetBrains Mono', monospace !important;
746
- }
747
-
748
- .markdown-body code {
749
  background: var(--sw-bg-dark) !important;
750
- color: var(--sw-cyan) !important;
751
- }
752
-
753
- .markdown-body table {
754
  border-color: var(--sw-border) !important;
755
  }
756
 
757
- .markdown-body th {
758
- background: var(--sw-bg-dark) !important;
759
- color: var(--sw-cyan) !important;
760
- }
761
-
762
- /* Footer */
763
- .footer-bar {
764
- background: var(--sw-bg-card);
765
- border: 1px solid var(--sw-border);
766
- border-radius: 8px;
767
- padding: 12px 24px;
768
- margin-top: 20px;
769
- display: flex;
770
- justify-content: space-between;
771
- font-family: 'JetBrains Mono', monospace;
772
- font-size: 11px;
773
- color: var(--sw-text-dim);
774
- }
775
-
776
- .footer-stat {
777
- color: var(--sw-cyan);
778
- }
779
-
780
- .footer-threat {
781
- color: var(--sw-yellow);
782
- }
783
 
784
- /* Realtime indicator */
785
- .realtime-indicator {
786
- display: flex;
787
- align-items: center;
788
- gap: 8px;
789
- font-family: 'JetBrains Mono', monospace;
790
- font-size: 12px;
791
- color: var(--sw-green);
792
- }
793
-
794
- .realtime-dot {
795
- width: 8px;
796
- height: 8px;
797
- background: var(--sw-green);
798
- border-radius: 50%;
799
- animation: pulse 1.5s infinite;
800
- }
801
  """
802
 
 
803
  # ============================================================================
804
  # GRADIO INTERFACE
805
  # ============================================================================
806
 
807
  with gr.Blocks(
808
- title="ShadowWatch | Dark Web Intelligence",
809
  theme=gr.themes.Base(
810
  primary_hue="cyan",
811
- secondary_hue="slate",
812
  neutral_hue="slate",
813
  ).set(
814
  body_background_fill="#0a0f1a",
@@ -816,271 +1044,117 @@ with gr.Blocks(
816
  block_border_width="1px",
817
  block_border_color="#1e3a5f",
818
  button_primary_background_fill="#00b396",
819
- button_primary_text_color="white",
820
- input_background_fill="#0a0f1a",
821
  ),
822
  css=custom_css
823
  ) as demo:
824
 
825
- # Header HTML
826
  gr.HTML("""
827
- <div class="header-bar">
828
- <div class="logo-section">
829
- <div class="logo-icon">🛡️</div>
830
  <div>
831
- <div class="logo-text">SHADOWWATCH</div>
832
- <div class="logo-subtitle">Dark Web Intelligence Platform</div>
833
  </div>
834
  </div>
835
- <div class="status-indicators">
836
- <div class="status-item">
837
- <span style="color: #fbbf24;">▪ ▪ ▪</span>
838
- </div>
839
- <div class="status-item">
840
- <span class="status-dot"></span>
841
- <span class="status-label">TOR RELAY:</span>
842
- <span class="status-value">ACTIVE</span>
843
- </div>
844
- <div class="status-item">
845
- <span class="status-label">ENCRYPTION:</span>
846
- <span class="status-value">AES-256</span>
847
- </div>
848
- <div class="status-item">
849
- <span class="status-label">SESSION:</span>
850
- <span style="color: #22c55e;">SECURE</span>
851
- </div>
852
- <button class="killswitch-btn">⏻ KILLSWITCH</button>
853
  </div>
854
  </div>
855
  """)
856
 
857
- with gr.Row():
858
- # Left sidebar
859
- with gr.Column(scale=1):
860
- # Agent card
861
- gr.HTML("""
862
- <div class="info-card">
863
- <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
864
- <div style="width: 48px; height: 48px; background: #1e3a5f; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 24px;">👤</div>
865
- <div>
866
- <div style="font-family: 'JetBrains Mono', monospace; font-weight: 600; color: white;">AGENT-74X</div>
867
- <div style="font-size: 11px; color: #64748b; text-transform: uppercase; letter-spacing: 1px;">Senior Intelligence Analyst</div>
868
- </div>
869
- </div>
870
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
871
- <div style="background: #0a0f1a; padding: 12px; border-radius: 6px;">
872
- <div style="font-size: 10px; color: #64748b; text-transform: uppercase; letter-spacing: 1px;">Clearance</div>
873
- <div style="font-family: 'JetBrains Mono', monospace; color: #00ffd5;">LEVEL 5</div>
874
- </div>
875
- <div style="background: #0a0f1a; padding: 12px; border-radius: 6px;">
876
- <div style="font-size: 10px; color: #64748b; text-transform: uppercase; letter-spacing: 1px;">Status</div>
877
- <div style="font-family: 'JetBrains Mono', monospace; color: #22c55e;">ACTIVE</div>
878
- </div>
879
- <div style="background: #0a0f1a; padding: 12px; border-radius: 6px;">
880
- <div style="font-size: 10px; color: #64748b; text-transform: uppercase; letter-spacing: 1px;">Team</div>
881
- <div style="font-family: 'JetBrains Mono', monospace; color: white;">PHANTOM</div>
882
- </div>
883
- <div style="background: #0a0f1a; padding: 12px; border-radius: 6px;">
884
- <div style="font-size: 10px; color: #64748b; text-transform: uppercase; letter-spacing: 1px;">Since</div>
885
- <div style="font-family: 'JetBrains Mono', monospace; color: #00ffd5;">2018</div>
886
- </div>
887
- </div>
888
- </div>
889
- """)
890
 
891
- # Quick Actions - Now functional via tabs
892
- gr.HTML("""
893
- <div class="info-card">
894
- <div class="card-header">⚡ QUICK ACTIONS</div>
895
- </div>
896
- """)
 
 
 
 
 
 
 
897
 
898
- # Threat Level
899
- gr.HTML("""
900
- <div class="threat-meter">
901
- <div style="display: flex; justify-content: space-between; align-items: center;">
902
- <div class="card-header" style="margin: 0;">⚠ THREAT LEVEL</div>
903
- <span class="badge badge-elevated">ELEVATED</span>
904
- </div>
905
- <div class="threat-bar">
906
- <div class="threat-indicator" style="left: 45%;"></div>
907
- </div>
908
- <div class="threat-labels">
909
- <span>LOW</span>
910
- <span>MODERATE</span>
911
- <span>HIGH</span>
912
- <span>CRITICAL</span>
913
- </div>
914
- <div style="margin-top: 12px; font-size: 12px; color: #22c55e;">
915
- +12% DARKNET ACTIVITY
916
- </div>
917
- </div>
918
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
919
 
920
- # Main content area
921
- with gr.Column(scale=3):
922
- with gr.Tabs():
923
- # Deep Scan Tab
924
- with gr.TabItem("🔍 DEEP SCAN"):
925
- gr.Markdown("**Crawl dark web sources for threat intelligence on your target.**")
926
- with gr.Row():
927
- with gr.Column():
928
- scan_target = gr.Textbox(
929
- label="Target",
930
- placeholder="Enter domain, company name, email pattern...",
931
- elem_classes=["input-field"]
932
- )
933
- scan_type = gr.Radio(
934
- label="Scan Depth",
935
- choices=["quick", "standard", "comprehensive"],
936
- value="standard"
937
- )
938
- scan_btn = gr.Button("▶ INITIATE SCAN", variant="primary")
939
- with gr.Column():
940
- scan_output = gr.JSON(label="Scan Results", elem_classes=["json-output"])
941
-
942
- scan_btn.click(
943
- fn=deep_scan,
944
- inputs=[scan_target, scan_type],
945
- outputs=[scan_output]
946
- )
947
-
948
- # Credential Trace Tab
949
- with gr.TabItem("🔑 CREDENTIAL TRACE"):
950
- gr.Markdown("**Search breach databases for credential exposure.**")
951
- with gr.Row():
952
- with gr.Column():
953
- cred_identifier = gr.Textbox(
954
- label="Identifier",
955
- placeholder="Email, username, or domain pattern...",
956
- elem_classes=["input-field"]
957
- )
958
- cred_depth = gr.Radio(
959
- label="Search Depth",
960
- choices=["surface", "standard", "deep"],
961
- value="standard"
962
- )
963
- cred_btn = gr.Button("▶ TRACE CREDENTIALS", variant="primary")
964
- with gr.Column():
965
- cred_output = gr.JSON(label="Trace Results", elem_classes=["json-output"])
966
-
967
- cred_btn.click(
968
- fn=credential_trace,
969
- inputs=[cred_identifier, cred_depth],
970
- outputs=[cred_output]
971
- )
972
-
973
- # Chatter Analysis Tab
974
- with gr.TabItem("💬 CHATTER ANALYSIS"):
975
- gr.Markdown("**Monitor dark web discussions for specific keywords.**")
976
- with gr.Row():
977
- with gr.Column():
978
- chatter_keywords = gr.Textbox(
979
- label="Keywords (comma-separated)",
980
- placeholder="company name, product, executive name...",
981
- elem_classes=["input-field"]
982
- )
983
- chatter_timeframe = gr.Slider(
984
- label="Timeframe (hours)",
985
- minimum=1,
986
- maximum=168,
987
- value=24,
988
- step=1
989
- )
990
- chatter_btn = gr.Button("▶ ANALYZE CHATTER", variant="primary")
991
- with gr.Column():
992
- chatter_output = gr.JSON(label="Analysis Results", elem_classes=["json-output"])
993
-
994
- chatter_btn.click(
995
- fn=chatter_analysis,
996
- inputs=[chatter_keywords, chatter_timeframe],
997
- outputs=[chatter_output]
998
- )
999
-
1000
- # Identity Alert Tab
1001
- with gr.TabItem("👤 IDENTITY ALERT"):
1002
- gr.Markdown("**Check for identity exposure and impersonation attempts.**")
1003
- with gr.Row():
1004
- with gr.Column():
1005
- identity_name = gr.Textbox(
1006
- label="Full Name",
1007
- placeholder="John Smith",
1008
- elem_classes=["input-field"]
1009
- )
1010
- identity_extras = gr.Textbox(
1011
- label="Additional Identifiers (optional)",
1012
- placeholder="email@company.com, @twitter_handle",
1013
- elem_classes=["input-field"]
1014
- )
1015
- identity_btn = gr.Button("▶ CHECK IDENTITY", variant="primary")
1016
- with gr.Column():
1017
- identity_output = gr.JSON(label="Alert Results", elem_classes=["json-output"])
1018
-
1019
- identity_btn.click(
1020
- fn=identity_alert,
1021
- inputs=[identity_name, identity_extras],
1022
- outputs=[identity_output]
1023
- )
1024
-
1025
- # Threat Report Tab
1026
- with gr.TabItem("📊 THREAT REPORT"):
1027
- gr.Markdown("**Generate comprehensive threat intelligence reports.**")
1028
- with gr.Row():
1029
- with gr.Column(scale=1):
1030
- report_org = gr.Textbox(
1031
- label="Organization",
1032
- placeholder="Acme Corporation",
1033
- elem_classes=["input-field"]
1034
- )
1035
- report_type = gr.Radio(
1036
- label="Report Type",
1037
- choices=["executive", "technical", "incident"],
1038
- value="executive"
1039
- )
1040
- report_btn = gr.Button("▶ GENERATE REPORT", variant="primary")
1041
- with gr.Column(scale=2):
1042
- report_output = gr.Markdown(label="Threat Report")
1043
-
1044
- report_btn.click(
1045
- fn=generate_threat_report,
1046
- inputs=[report_org, report_type],
1047
- outputs=[report_output]
1048
- )
1049
 
1050
  # Footer
1051
  gr.HTML("""
1052
- <div class="footer-bar">
1053
- <div>
1054
- <span style="margin-right: 24px;">📊 DARK WEB ACTIVITY ANALYSIS</span>
1055
- <span>LAST 24H: <span class="footer-stat">1,247 ALERTS</span></span>
 
 
 
 
 
1056
  </div>
1057
- <div>
1058
- <span>TOP THREAT: <span class="footer-threat">CREDENTIAL LEAKS</span></span>
1059
  </div>
1060
  </div>
1061
  """)
1062
-
1063
- # MCP Integration info
1064
- gr.HTML("""
1065
- <div class="info-card" style="margin-top: 20px;">
1066
- <div class="card-header">🔗 MCP INTEGRATION</div>
1067
- <p style="color: #94a3b8; font-size: 13px; margin-bottom: 12px;">
1068
- Connect this Space to Claude, Cursor, or any MCP client:
1069
- </p>
1070
- <pre style="background: #000; padding: 16px; border-radius: 6px; font-family: 'JetBrains Mono', monospace; font-size: 12px; color: #00ffd5; overflow-x: auto;">
1071
- {
1072
- "mcpServers": {
1073
- "shadowwatch": {
1074
- "url": "https://crypticallyrequie-shadowwatch.hf.space/gradio_api/mcp/sse"
1075
- }
1076
- }
1077
- }</pre>
1078
- <p style="color: #64748b; font-size: 11px; margin-top: 12px;">
1079
- Built by <span style="color: #00ffd5;">Cogensec</span> | Part of the ARGUS AI Security Platform
1080
- </p>
1081
- </div>
1082
- """)
1083
 
1084
- # Launch with MCP server enabled
1085
  if __name__ == "__main__":
1086
- demo.launch(mcp_server=True)
 
1
  """
2
+ ShadowWatch v2 - Open Source Threat Intelligence Platform
3
+ 100% Free - No API Keys Required
4
+
5
+ Uses public threat feeds and local analysis:
6
+ - abuse.ch (URLhaus, ThreatFox, FeodoTracker, MalwareBazaar)
7
+ - Spamhaus DROP lists
8
+ - Emerging Threats blocklists
9
+ - Direct DNS/WHOIS lookups
10
+ - IOC extraction and analysis
11
 
12
  By Cogensec | ARGUS Platform
13
  """
14
 
15
  import gradio as gr
16
+ import requests
17
+ import socket
18
  import hashlib
19
+ import re
20
+ import json
21
+ import csv
22
+ import io
23
  from datetime import datetime, timedelta
24
+ from typing import Optional, Tuple, List, Dict
25
+ from functools import lru_cache
26
+ from concurrent.futures import ThreadPoolExecutor, as_completed
27
+ import plotly.graph_objects as go
28
+ import plotly.express as px
29
 
30
  # ============================================================================
31
+ # THREAT FEED MANAGER - Downloads and caches public feeds
32
  # ============================================================================
33
 
34
+ class ThreatFeedManager:
35
+ """Manages open-source threat intelligence feeds"""
36
 
37
+ # Feed URLs (all free, no auth required)
38
+ FEEDS = {
39
+ "urlhaus": "https://urlhaus.abuse.ch/downloads/csv_recent/",
40
+ "threatfox_iocs": "https://threatfox.abuse.ch/export/json/recent/",
41
+ "feodo_ipblocklist": "https://feodotracker.abuse.ch/downloads/ipblocklist.csv",
42
+ "malwarebazaar_recent": "https://bazaar.abuse.ch/export/csv/recent/",
43
+ "spamhaus_drop": "https://www.spamhaus.org/drop/drop.txt",
44
+ "spamhaus_edrop": "https://www.spamhaus.org/drop/edrop.txt",
45
+ "emergingthreats_compromised": "https://rules.emergingthreats.net/blockrules/compromised-ips.txt",
46
+ "openphish": "https://openphish.com/feed.txt",
47
+ "hibp_breaches": "https://haveibeenpwned.com/api/v3/breaches",
48
+ }
49
 
50
+ def __init__(self):
51
+ self.cache = {}
52
+ self.cache_time = {}
53
+ self.cache_duration = timedelta(hours=1) # Refresh every hour
54
+
55
+ def _is_cache_valid(self, feed_name: str) -> bool:
56
+ if feed_name not in self.cache_time:
57
+ return False
58
+ return datetime.now() - self.cache_time[feed_name] < self.cache_duration
59
+
60
+ def fetch_feed(self, feed_name: str) -> Optional[str]:
61
+ """Fetch a threat feed, using cache if available"""
62
+ if self._is_cache_valid(feed_name):
63
+ return self.cache.get(feed_name)
64
 
65
+ url = self.FEEDS.get(feed_name)
66
+ if not url:
67
+ return None
68
+
69
+ try:
70
+ headers = {"User-Agent": "ShadowWatch-ARGUS/1.0"}
71
+ resp = requests.get(url, headers=headers, timeout=30)
72
+ if resp.status_code == 200:
73
+ self.cache[feed_name] = resp.text
74
+ self.cache_time[feed_name] = datetime.now()
75
+ return resp.text
76
+ except Exception as e:
77
+ print(f"Error fetching {feed_name}: {e}")
78
+
79
+ return self.cache.get(feed_name) # Return stale cache if fetch fails
80
 
81
+ def get_urlhaus_urls(self) -> List[Dict]:
82
+ """Get recent malicious URLs from URLhaus"""
83
+ data = self.fetch_feed("urlhaus")
84
+ if not data:
85
+ return []
86
+
87
+ urls = []
88
+ reader = csv.reader(io.StringIO(data))
89
+ for row in reader:
90
+ if row and not row[0].startswith('#') and len(row) >= 8:
91
+ try:
92
+ urls.append({
93
+ "id": row[0],
94
+ "dateadded": row[1],
95
+ "url": row[2],
96
+ "url_status": row[3],
97
+ "threat": row[5],
98
+ "tags": row[6],
99
+ "host": row[7] if len(row) > 7 else ""
100
+ })
101
+ except:
102
+ continue
103
+ return urls
104
 
105
+ def get_threatfox_iocs(self) -> List[Dict]:
106
+ """Get recent IOCs from ThreatFox"""
107
+ data = self.fetch_feed("threatfox_iocs")
108
+ if not data:
109
+ return []
110
+
111
+ try:
112
+ parsed = json.loads(data)
113
+ return parsed.get("data", []) if parsed.get("query_status") == "ok" else []
114
+ except:
115
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
+ def get_feodo_ips(self) -> List[Dict]:
118
+ """Get botnet C2 IPs from FeodoTracker"""
119
+ data = self.fetch_feed("feodo_ipblocklist")
120
+ if not data:
121
+ return []
122
+
123
+ ips = []
124
+ reader = csv.reader(io.StringIO(data))
125
+ for row in reader:
126
+ if row and not row[0].startswith('#') and len(row) >= 5:
127
+ try:
128
+ ips.append({
129
+ "first_seen": row[0],
130
+ "ip": row[1],
131
+ "port": row[2],
132
+ "status": row[3],
133
+ "malware": row[4]
134
+ })
135
+ except:
136
+ continue
137
+ return ips
138
+
139
+ def get_spamhaus_ips(self) -> set:
140
+ """Get bad IP ranges from Spamhaus DROP"""
141
+ ips = set()
142
+ for feed in ["spamhaus_drop", "spamhaus_edrop"]:
143
+ data = self.fetch_feed(feed)
144
+ if data:
145
+ for line in data.split('\n'):
146
+ line = line.strip()
147
+ if line and not line.startswith(';'):
148
+ # Extract IP/CIDR before any semicolon
149
+ ip_part = line.split(';')[0].strip()
150
+ if ip_part:
151
+ ips.add(ip_part)
152
+ return ips
153
+
154
+ def get_emergingthreats_ips(self) -> set:
155
+ """Get compromised IPs from Emerging Threats"""
156
+ data = self.fetch_feed("emergingthreats_compromised")
157
+ if not data:
158
+ return set()
159
+
160
+ ips = set()
161
+ for line in data.split('\n'):
162
+ line = line.strip()
163
+ if line and not line.startswith('#'):
164
+ ips.add(line)
165
+ return ips
166
+
167
+ def get_openphish_urls(self) -> set:
168
+ """Get phishing URLs from OpenPhish"""
169
+ data = self.fetch_feed("openphish")
170
+ if not data:
171
+ return set()
172
+
173
+ return set(line.strip() for line in data.split('\n') if line.strip())
174
+
175
+ def get_hibp_breaches(self) -> List[Dict]:
176
+ """Get public breach list from HIBP (no API key needed for breach list)"""
177
+ data = self.fetch_feed("hibp_breaches")
178
+ if not data:
179
+ return []
180
+
181
+ try:
182
+ return json.loads(data)
183
+ except:
184
+ return []
185
 
186
 
187
+ # Initialize feed manager
188
+ feed_manager = ThreatFeedManager()
189
+
190
+
191
+ # ============================================================================
192
+ # LOCAL ANALYSIS TOOLS
193
+ # ============================================================================
194
+
195
+ def dns_lookup(domain: str) -> Dict:
196
+ """Perform DNS lookup without external APIs"""
197
+ results = {
198
+ "domain": domain,
199
+ "a_records": [],
200
+ "aaaa_records": [],
201
+ "mx_records": [],
202
+ "ns_records": [],
203
+ "error": None
204
+ }
205
 
206
+ try:
207
+ # A records (IPv4)
208
+ try:
209
+ results["a_records"] = list(set(socket.gethostbyname_ex(domain)[2]))
210
+ except:
211
+ pass
212
 
213
+ # Try to get additional info via getaddrinfo
214
+ try:
215
+ info = socket.getaddrinfo(domain, None)
216
+ for item in info:
217
+ ip = item[4][0]
218
+ if ':' in ip and ip not in results["aaaa_records"]:
219
+ results["aaaa_records"].append(ip)
220
+ elif '.' in ip and ip not in results["a_records"]:
221
+ results["a_records"].append(ip)
222
+ except:
223
+ pass
224
+
225
+ except socket.gaierror as e:
226
+ results["error"] = f"DNS lookup failed: {str(e)}"
227
+ except Exception as e:
228
+ results["error"] = str(e)
229
+
230
+ return results
231
+
232
+
233
+ def extract_iocs(text: str) -> Dict:
234
+ """Extract Indicators of Compromise from text"""
235
+ patterns = {
236
+ "ipv4": r'\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b',
237
+ "ipv6": r'\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b',
238
+ "domain": r'\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b',
239
+ "url": r'https?://[^\s<>"{}|\\^`\[\]]+',
240
+ "email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
241
+ "md5": r'\b[a-fA-F0-9]{32}\b',
242
+ "sha1": r'\b[a-fA-F0-9]{40}\b',
243
+ "sha256": r'\b[a-fA-F0-9]{64}\b',
244
+ "bitcoin": r'\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\b',
245
+ "cve": r'CVE-\d{4}-\d{4,7}',
246
+ }
247
+
248
+ results = {}
249
+ for ioc_type, pattern in patterns.items():
250
+ matches = list(set(re.findall(pattern, text, re.IGNORECASE)))
251
+ # Filter out common false positives for domains
252
+ if ioc_type == "domain":
253
+ matches = [m for m in matches if not m.endswith('.png') and not m.endswith('.jpg')
254
+ and not m.endswith('.gif') and len(m) > 4]
255
+ results[ioc_type] = matches[:100] # Limit to 100 per type
256
 
257
+ return results
258
+
259
+
260
+ def calculate_hashes(text: str) -> Dict:
261
+ """Calculate various hashes for given text/data"""
262
+ data = text.encode('utf-8')
263
  return {
264
+ "md5": hashlib.md5(data).hexdigest(),
265
+ "sha1": hashlib.sha1(data).hexdigest(),
266
+ "sha256": hashlib.sha256(data).hexdigest(),
267
+ "sha512": hashlib.sha512(data).hexdigest(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  }
269
 
270
 
271
+ def check_ip_reputation(ip: str) -> Dict:
272
+ """Check IP against local blocklists"""
273
+ results = {
274
+ "ip": ip,
275
+ "is_malicious": False,
276
+ "sources": [],
277
+ "risk_score": 0,
278
+ "details": []
279
+ }
280
 
281
+ # Check Feodo botnet IPs
282
+ feodo_ips = feed_manager.get_feodo_ips()
283
+ for entry in feodo_ips:
284
+ if entry.get("ip") == ip:
285
+ results["is_malicious"] = True
286
+ results["sources"].append("FeodoTracker")
287
+ results["risk_score"] += 40
288
+ results["details"].append({
289
+ "source": "FeodoTracker",
290
+ "malware": entry.get("malware", "Unknown"),
291
+ "first_seen": entry.get("first_seen", "Unknown"),
292
+ "status": entry.get("status", "Unknown")
293
+ })
294
+ break
295
 
296
+ # Check Spamhaus DROP
297
+ spamhaus_ranges = feed_manager.get_spamhaus_ips()
298
+ for ip_range in spamhaus_ranges:
299
+ if '/' in ip_range:
300
+ # CIDR check (simplified - just check if IP starts with network portion)
301
+ network = ip_range.split('/')[0]
302
+ network_parts = network.split('.')
303
+ ip_parts = ip.split('.')
304
+ if ip_parts[:len(network_parts)-1] == network_parts[:len(network_parts)-1]:
305
+ results["is_malicious"] = True
306
+ results["sources"].append("Spamhaus DROP")
307
+ results["risk_score"] += 35
308
+ results["details"].append({
309
+ "source": "Spamhaus DROP",
310
+ "matched_range": ip_range
311
+ })
312
+ break
313
+ elif ip == ip_range:
314
+ results["is_malicious"] = True
315
+ results["sources"].append("Spamhaus DROP")
316
+ results["risk_score"] += 35
317
+ break
318
+
319
+ # Check Emerging Threats
320
+ et_ips = feed_manager.get_emergingthreats_ips()
321
+ if ip in et_ips:
322
+ results["is_malicious"] = True
323
+ results["sources"].append("Emerging Threats")
324
+ results["risk_score"] += 30
325
+ results["details"].append({
326
+ "source": "Emerging Threats",
327
+ "category": "Compromised IP"
328
+ })
329
 
330
+ # Check ThreatFox IOCs
331
+ threatfox = feed_manager.get_threatfox_iocs()
332
+ for ioc in threatfox:
333
+ if ioc.get("ioc_type") == "ip:port" and ip in ioc.get("ioc", ""):
334
+ results["is_malicious"] = True
335
+ results["sources"].append("ThreatFox")
336
+ results["risk_score"] += 45
337
+ results["details"].append({
338
+ "source": "ThreatFox",
339
+ "malware": ioc.get("malware_printable", "Unknown"),
340
+ "threat_type": ioc.get("threat_type", "Unknown"),
341
+ "confidence": ioc.get("confidence_level", 0)
342
+ })
343
+ break
344
 
345
+ results["risk_score"] = min(results["risk_score"], 100)
346
+ return results
347
+
348
+
349
+ def check_url_reputation(url: str) -> Dict:
350
+ """Check URL against local threat feeds"""
351
+ results = {
352
+ "url": url,
353
+ "is_malicious": False,
354
+ "sources": [],
355
+ "risk_score": 0,
356
+ "details": []
357
  }
358
 
359
+ # Normalize URL
360
+ url_lower = url.lower().strip()
361
+
362
+ # Extract domain from URL
363
+ domain_match = re.search(r'https?://([^/]+)', url_lower)
364
+ domain = domain_match.group(1) if domain_match else ""
365
+
366
+ # Check URLhaus
367
+ urlhaus_urls = feed_manager.get_urlhaus_urls()
368
+ for entry in urlhaus_urls:
369
+ if url_lower == entry.get("url", "").lower() or domain == entry.get("host", "").lower():
370
+ results["is_malicious"] = True
371
+ results["sources"].append("URLhaus")
372
+ results["risk_score"] += 50
373
+ results["details"].append({
374
+ "source": "URLhaus",
375
+ "threat": entry.get("threat", "Unknown"),
376
+ "tags": entry.get("tags", ""),
377
+ "status": entry.get("url_status", "Unknown"),
378
+ "date_added": entry.get("dateadded", "Unknown")
379
+ })
380
+ break
381
+
382
+ # Check OpenPhish
383
+ phishing_urls = feed_manager.get_openphish_urls()
384
+ if url_lower in phishing_urls or any(url_lower.startswith(p) for p in phishing_urls):
385
+ results["is_malicious"] = True
386
+ results["sources"].append("OpenPhish")
387
+ results["risk_score"] += 45
388
+ results["details"].append({
389
+ "source": "OpenPhish",
390
+ "category": "Phishing"
391
  })
392
 
393
+ # Check ThreatFox for URL IOCs
394
+ threatfox = feed_manager.get_threatfox_iocs()
395
+ for ioc in threatfox:
396
+ if ioc.get("ioc_type") == "url" and url_lower in ioc.get("ioc", "").lower():
397
+ results["is_malicious"] = True
398
+ results["sources"].append("ThreatFox")
399
+ results["risk_score"] += 50
400
+ results["details"].append({
401
+ "source": "ThreatFox",
402
+ "malware": ioc.get("malware_printable", "Unknown"),
403
+ "threat_type": ioc.get("threat_type", "Unknown")
404
+ })
405
+ break
406
 
407
+ results["risk_score"] = min(results["risk_score"], 100)
408
+ return results
409
+
410
+
411
+ def check_domain_reputation(domain: str) -> Dict:
412
+ """Check domain against threat feeds and perform DNS analysis"""
413
+ results = {
414
+ "domain": domain,
415
+ "is_malicious": False,
416
+ "sources": [],
417
+ "risk_score": 0,
418
+ "dns": {},
419
+ "details": []
 
 
 
420
  }
421
+
422
+ # DNS lookup
423
+ results["dns"] = dns_lookup(domain)
424
+
425
+ # Check if resolved IPs are malicious
426
+ for ip in results["dns"].get("a_records", []):
427
+ ip_rep = check_ip_reputation(ip)
428
+ if ip_rep["is_malicious"]:
429
+ results["is_malicious"] = True
430
+ results["risk_score"] += ip_rep["risk_score"] // 2
431
+ results["details"].append({
432
+ "source": "DNS Resolution",
433
+ "detail": f"Resolves to malicious IP: {ip}",
434
+ "ip_sources": ip_rep["sources"]
435
+ })
436
+
437
+ # Check URLhaus for domain
438
+ urlhaus_urls = feed_manager.get_urlhaus_urls()
439
+ for entry in urlhaus_urls:
440
+ if domain.lower() == entry.get("host", "").lower():
441
+ results["is_malicious"] = True
442
+ results["sources"].append("URLhaus")
443
+ results["risk_score"] += 40
444
+ results["details"].append({
445
+ "source": "URLhaus",
446
+ "threat": entry.get("threat", "Unknown"),
447
+ "tags": entry.get("tags", "")
448
+ })
449
+ break
450
+
451
+ # Check ThreatFox for domain IOCs
452
+ threatfox = feed_manager.get_threatfox_iocs()
453
+ for ioc in threatfox:
454
+ if ioc.get("ioc_type") == "domain" and domain.lower() == ioc.get("ioc", "").lower():
455
+ results["is_malicious"] = True
456
+ results["sources"].append("ThreatFox")
457
+ results["risk_score"] += 45
458
+ results["details"].append({
459
+ "source": "ThreatFox",
460
+ "malware": ioc.get("malware_printable", "Unknown"),
461
+ "threat_type": ioc.get("threat_type", "Unknown")
462
+ })
463
+ break
464
+
465
+ results["risk_score"] = min(results["risk_score"], 100)
466
+ return results
467
+
468
+
469
+ # ============================================================================
470
+ # VISUALIZATION HELPERS
471
+ # ============================================================================
472
+
473
+ def create_risk_gauge(score: int, title: str = "Risk Score") -> go.Figure:
474
+ """Create a risk gauge chart"""
475
+ if score >= 75:
476
+ color = "#ef4444"
477
+ elif score >= 50:
478
+ color = "#f97316"
479
+ elif score >= 25:
480
+ color = "#fbbf24"
481
+ else:
482
+ color = "#22c55e"
483
+
484
+ fig = go.Figure(go.Indicator(
485
+ mode="gauge+number",
486
+ value=score,
487
+ domain={'x': [0, 1], 'y': [0, 1]},
488
+ title={'text': title, 'font': {'size': 18, 'color': '#e2e8f0'}},
489
+ number={'font': {'size': 36, 'color': '#e2e8f0'}},
490
+ gauge={
491
+ 'axis': {'range': [0, 100], 'tickcolor': "#1e3a5f"},
492
+ 'bar': {'color': color},
493
+ 'bgcolor': "#111827",
494
+ 'borderwidth': 2,
495
+ 'bordercolor': "#1e3a5f",
496
+ 'steps': [
497
+ {'range': [0, 25], 'color': 'rgba(34, 197, 94, 0.15)'},
498
+ {'range': [25, 50], 'color': 'rgba(251, 191, 36, 0.15)'},
499
+ {'range': [50, 75], 'color': 'rgba(249, 115, 22, 0.15)'},
500
+ {'range': [75, 100], 'color': 'rgba(239, 68, 68, 0.15)'}
501
+ ],
502
+ }
503
+ ))
504
+
505
+ fig.update_layout(
506
+ paper_bgcolor='#0a0f1a',
507
+ plot_bgcolor='#0a0f1a',
508
+ height=280,
509
+ margin=dict(l=20, r=20, t=50, b=20)
510
+ )
511
+
512
+ return fig
513
+
514
+
515
+ def create_ioc_chart(iocs: Dict) -> go.Figure:
516
+ """Create a bar chart of extracted IOCs"""
517
+ types = []
518
+ counts = []
519
+
520
+ for ioc_type, items in iocs.items():
521
+ if items:
522
+ types.append(ioc_type.upper())
523
+ counts.append(len(items))
524
+
525
+ if not types:
526
+ fig = go.Figure()
527
+ fig.add_annotation(text="No IOCs extracted", x=0.5, y=0.5, showarrow=False,
528
+ font=dict(size=16, color="#64748b"), xref="paper", yref="paper")
529
+ fig.update_layout(paper_bgcolor='#0a0f1a', plot_bgcolor='#0a0f1a', height=280)
530
+ return fig
531
+
532
+ colors = ['#00ffd5' if c > 0 else '#64748b' for c in counts]
533
+
534
+ fig = go.Figure(data=[go.Bar(
535
+ x=types,
536
+ y=counts,
537
+ marker_color=colors,
538
+ text=counts,
539
+ textposition='outside'
540
+ )])
541
+
542
+ fig.update_layout(
543
+ title={'text': '📊 Extracted IOCs', 'font': {'color': '#00ffd5'}},
544
+ paper_bgcolor='#0a0f1a',
545
+ plot_bgcolor='#111827',
546
+ font={'color': '#e2e8f0'},
547
+ height=280,
548
+ xaxis={'gridcolor': '#1e3a5f'},
549
+ yaxis={'gridcolor': '#1e3a5f'},
550
+ margin=dict(l=40, r=20, t=50, b=40)
551
+ )
552
+
553
+ return fig
554
 
555
 
556
+ def create_threat_sources_chart(sources: List[str]) -> go.Figure:
557
+ """Create a pie chart of threat sources"""
558
+ if not sources:
559
+ fig = go.Figure()
560
+ fig.add_annotation(text="✓ No threats detected", x=0.5, y=0.5, showarrow=False,
561
+ font=dict(size=18, color="#22c55e"), xref="paper", yref="paper")
562
+ fig.update_layout(paper_bgcolor='#0a0f1a', plot_bgcolor='#0a0f1a', height=280)
563
+ return fig
564
+
565
+ from collections import Counter
566
+ source_counts = Counter(sources)
567
+
568
+ colors = ['#ef4444', '#f97316', '#fbbf24', '#00ffd5', '#8b5cf6']
569
+
570
+ fig = go.Figure(data=[go.Pie(
571
+ labels=list(source_counts.keys()),
572
+ values=list(source_counts.values()),
573
+ hole=0.5,
574
+ marker_colors=colors[:len(source_counts)],
575
+ textinfo='label+value',
576
+ textfont={'color': '#e2e8f0'}
577
+ )])
578
+
579
+ fig.update_layout(
580
+ title={'text': '⚠️ Threat Sources', 'font': {'color': '#ef4444'}},
581
+ paper_bgcolor='#0a0f1a',
582
+ plot_bgcolor='#0a0f1a',
583
+ font={'color': '#e2e8f0'},
584
+ height=280,
585
+ showlegend=False,
586
+ margin=dict(l=20, r=20, t=50, b=20)
587
+ )
588
 
589
+ return fig
590
+
591
+
592
+ # ============================================================================
593
+ # MCP TOOLS
594
+ # ============================================================================
595
+
596
+ def scan_indicator(indicator: str, indicator_type: str = "auto") -> Tuple[go.Figure, go.Figure, str]:
597
+ """Scan an IP address, domain, or URL against open-source threat feeds.
598
+
599
+ Checks the indicator against URLhaus, ThreatFox, FeodoTracker,
600
+ Spamhaus DROP, Emerging Threats, and OpenPhish databases.
601
 
602
  Args:
603
+ indicator: The IP, domain, or URL to scan
604
+ indicator_type: "auto", "ip", "domain", or "url"
605
 
606
  Returns:
607
+ Risk gauge, threat sources chart, and detailed HTML report
 
608
  """
609
+ if not indicator:
610
+ empty_fig = go.Figure()
611
+ empty_fig.update_layout(paper_bgcolor='#0a0f1a', height=280)
612
+ return empty_fig, empty_fig, "<p style='color: #ef4444;'>Please enter an indicator to scan</p>"
613
 
614
+ indicator = indicator.strip()
 
 
615
 
616
+ # Auto-detect type
617
+ if indicator_type == "auto":
618
+ if re.match(r'^https?://', indicator):
619
+ indicator_type = "url"
620
+ elif re.match(r'^(\d{1,3}\.){3}\d{1,3}$', indicator):
621
+ indicator_type = "ip"
622
+ else:
623
+ indicator_type = "domain"
624
 
625
+ # Perform check based on type
626
+ if indicator_type == "ip":
627
+ result = check_ip_reputation(indicator)
628
+ elif indicator_type == "url":
629
+ result = check_url_reputation(indicator)
630
+ else:
631
+ result = check_domain_reputation(indicator)
 
632
 
633
+ # Create visualizations
634
+ gauge_fig = create_risk_gauge(result["risk_score"], "Threat Score")
635
+ sources_fig = create_threat_sources_chart(result["sources"])
 
 
 
 
 
 
636
 
637
+ # Build HTML report
638
+ status_color = "#ef4444" if result["is_malicious"] else "#22c55e"
639
+ status_text = "⚠️ MALICIOUS" if result["is_malicious"] else "✓ CLEAN"
 
 
 
 
 
640
 
641
+ html = f"""
642
+ <div style="font-family: 'JetBrains Mono', monospace; color: #e2e8f0;">
643
+ <h3 style="color: #00ffd5; border-bottom: 1px solid #1e3a5f; padding-bottom: 10px;">
644
+ 🔍 Threat Intelligence Report
645
+ </h3>
646
+
647
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 20px 0;">
648
+ <div style="background: #111827; padding: 16px; border-radius: 8px; border: 1px solid #1e3a5f;">
649
+ <div style="color: #64748b; font-size: 11px; text-transform: uppercase;">Indicator</div>
650
+ <div style="font-size: 14px; color: #00ffd5; word-break: break-all;">{indicator}</div>
651
+ </div>
652
+ <div style="background: #111827; padding: 16px; border-radius: 8px; border: 1px solid #1e3a5f;">
653
+ <div style="color: #64748b; font-size: 11px; text-transform: uppercase;">Type</div>
654
+ <div style="font-size: 18px; color: #e2e8f0;">{indicator_type.upper()}</div>
655
+ </div>
656
+ <div style="background: #111827; padding: 16px; border-radius: 8px; border: 1px solid #1e3a5f;">
657
+ <div style="color: #64748b; font-size: 11px; text-transform: uppercase;">Status</div>
658
+ <div style="font-size: 18px; color: {status_color};">{status_text}</div>
659
+ </div>
660
+ </div>
661
+ """
662
 
663
+ # DNS info for domains
664
+ if indicator_type == "domain" and "dns" in result:
665
+ dns = result["dns"]
666
+ a_records = ", ".join(dns.get("a_records", [])) or "None"
667
+ html += f"""
668
+ <h4 style="color: #fbbf24; margin-top: 24px;">🌐 DNS Resolution</h4>
669
+ <div style="background: #111827; padding: 16px; border-radius: 8px; margin-top: 12px;">
670
+ <p><span style="color: #64748b;">A Records:</span> <span style="color: #00ffd5;">{a_records}</span></p>
671
+ </div>
672
+ """
673
+
674
+ # Threat details
675
+ if result["details"]:
676
+ html += """
677
+ <h4 style="color: #ef4444; margin-top: 24px;">🚨 Threat Details</h4>
678
+ <table style="width: 100%; border-collapse: collapse; margin-top: 12px;">
679
+ <tr style="background: #1e3a5f;">
680
+ <th style="padding: 12px; text-align: left; color: #00ffd5;">Source</th>
681
+ <th style="padding: 12px; text-align: left; color: #00ffd5;">Details</th>
682
+ </tr>
683
+ """
684
+ for detail in result["details"]:
685
+ source = detail.get("source", "Unknown")
686
+ info_parts = [f"{k}: {v}" for k, v in detail.items() if k != "source" and v]
687
+ info = " | ".join(info_parts)
688
+ html += f"""
689
+ <tr style="border-bottom: 1px solid #1e3a5f;">
690
+ <td style="padding: 12px; color: #f97316;">{source}</td>
691
+ <td style="padding: 12px; color: #94a3b8; font-size: 12px;">{info}</td>
692
+ </tr>
693
+ """
694
+ html += "</table>"
695
+
696
+ # Recommendations
697
+ html += f"""
698
+ <div style="margin-top: 24px; padding: 16px; background: #111827; border-radius: 8px; border-left: 4px solid {'#ef4444' if result['is_malicious'] else '#22c55e'};">
699
+ <h4 style="color: {'#ef4444' if result['is_malicious'] else '#22c55e'}; margin: 0 0 12px 0;">
700
+ {'🚫 Recommended Actions' if result['is_malicious'] else '✓ Assessment'}
701
+ </h4>
702
+ <ul style="color: #94a3b8; margin: 0; padding-left: 20px;">
703
+ """
704
+
705
+ if result["is_malicious"]:
706
+ html += """
707
+ <li>Block this indicator in your firewall/proxy</li>
708
+ <li>Search logs for any connections to this indicator</li>
709
+ <li>If found, isolate affected systems</li>
710
+ <li>Report to your security team</li>
711
+ """
712
+ else:
713
+ html += """
714
+ <li>No threats detected in current threat feeds</li>
715
+ <li>Continue monitoring for changes</li>
716
+ <li>Note: Absence of threat data doesn't guarantee safety</li>
717
+ """
718
+
719
+ html += """
720
+ </ul>
721
+ </div>
722
+ <p style="color: #64748b; font-size: 11px; margin-top: 16px;">
723
+ Data sources: URLhaus, ThreatFox, FeodoTracker, Spamhaus DROP, Emerging Threats, OpenPhish
724
+ </p>
725
+ </div>
726
+ """
727
+
728
+ return gauge_fig, sources_fig, html
729
 
730
 
731
+ def extract_and_analyze_iocs(text: str) -> Tuple[go.Figure, str, str]:
732
+ """Extract and analyze Indicators of Compromise from text.
733
 
734
+ Extracts IPs, domains, URLs, email addresses, file hashes,
735
+ Bitcoin addresses, and CVEs from the provided text, then
736
+ checks extracted indicators against threat feeds.
737
 
738
  Args:
739
+ text: Text containing potential IOCs (logs, reports, emails, etc.)
 
740
 
741
  Returns:
742
+ IOC distribution chart, extracted IOCs as JSON, and analysis report HTML
743
  """
744
+ if not text or len(text.strip()) < 5:
745
+ empty_fig = go.Figure()
746
+ empty_fig.update_layout(paper_bgcolor='#0a0f1a', height=280)
747
+ return empty_fig, "{}", "<p style='color: #ef4444;'>Please enter text to analyze</p>"
748
 
749
+ # Extract IOCs
750
+ iocs = extract_iocs(text)
751
+
752
+ # Create visualization
753
+ ioc_chart = create_ioc_chart(iocs)
754
+
755
+ # Count totals
756
+ total_iocs = sum(len(v) for v in iocs.values())
757
+
758
+ # Check some IOCs against threat feeds (limit to avoid overload)
759
+ malicious_found = []
760
+
761
+ # Check IPs (limit to first 10)
762
+ for ip in iocs.get("ipv4", [])[:10]:
763
+ result = check_ip_reputation(ip)
764
+ if result["is_malicious"]:
765
+ malicious_found.append({"type": "IP", "value": ip, "sources": result["sources"]})
766
+
767
+ # Check domains (limit to first 10)
768
+ for domain in iocs.get("domain", [])[:10]:
769
+ result = check_domain_reputation(domain)
770
+ if result["is_malicious"]:
771
+ malicious_found.append({"type": "Domain", "value": domain, "sources": result["sources"]})
772
+
773
+ # Check URLs (limit to first 5)
774
+ for url in iocs.get("url", [])[:5]:
775
+ result = check_url_reputation(url)
776
+ if result["is_malicious"]:
777
+ malicious_found.append({"type": "URL", "value": url, "sources": result["sources"]})
778
+
779
+ # Build HTML report
780
+ html = f"""
781
+ <div style="font-family: 'JetBrains Mono', monospace; color: #e2e8f0;">
782
+ <h3 style="color: #00ffd5; border-bottom: 1px solid #1e3a5f; padding-bottom: 10px;">
783
+ 📋 IOC Extraction Report
784
+ </h3>
785
+
786
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 20px 0;">
787
+ <div style="background: #111827; padding: 16px; border-radius: 8px; border: 1px solid #1e3a5f;">
788
+ <div style="color: #64748b; font-size: 11px; text-transform: uppercase;">Total IOCs</div>
789
+ <div style="font-size: 28px; color: #00ffd5;">{total_iocs}</div>
790
+ </div>
791
+ <div style="background: #111827; padding: 16px; border-radius: 8px; border: 1px solid #1e3a5f;">
792
+ <div style="color: #64748b; font-size: 11px; text-transform: uppercase;">IPs Found</div>
793
+ <div style="font-size: 28px; color: #e2e8f0;">{len(iocs.get('ipv4', []))}</div>
794
+ </div>
795
+ <div style="background: #111827; padding: 16px; border-radius: 8px; border: 1px solid #1e3a5f;">
796
+ <div style="color: #64748b; font-size: 11px; text-transform: uppercase;">Domains</div>
797
+ <div style="font-size: 28px; color: #e2e8f0;">{len(iocs.get('domain', []))}</div>
798
+ </div>
799
+ <div style="background: #111827; padding: 16px; border-radius: 8px; border: 1px solid #1e3a5f;">
800
+ <div style="color: #64748b; font-size: 11px; text-transform: uppercase;">Malicious</div>
801
+ <div style="font-size: 28px; color: {'#ef4444' if malicious_found else '#22c55e'};">{len(malicious_found)}</div>
802
+ </div>
803
+ </div>
804
+ """
805
+
806
+ # Show malicious indicators
807
+ if malicious_found:
808
+ html += """
809
+ <h4 style="color: #ef4444; margin-top: 24px;">🚨 Malicious Indicators Detected</h4>
810
+ <table style="width: 100%; border-collapse: collapse; margin-top: 12px;">
811
+ <tr style="background: #1e3a5f;">
812
+ <th style="padding: 12px; text-align: left; color: #00ffd5;">Type</th>
813
+ <th style="padding: 12px; text-align: left; color: #00ffd5;">Indicator</th>
814
+ <th style="padding: 12px; text-align: left; color: #00ffd5;">Sources</th>
815
+ </tr>
816
+ """
817
+ for item in malicious_found:
818
+ html += f"""
819
+ <tr style="border-bottom: 1px solid #1e3a5f;">
820
+ <td style="padding: 12px; color: #f97316;">{item['type']}</td>
821
+ <td style="padding: 12px; color: #ef4444; font-size: 12px; word-break: break-all;">{item['value']}</td>
822
+ <td style="padding: 12px; color: #94a3b8;">{', '.join(item['sources'])}</td>
823
+ </tr>
824
+ """
825
+ html += "</table>"
826
+
827
+ # Show extracted IOCs summary
828
+ html += "<h4 style='color: #fbbf24; margin-top: 24px;'>📊 Extracted Indicators</h4>"
829
+
830
+ for ioc_type, items in iocs.items():
831
+ if items:
832
+ preview = items[:5]
833
+ html += f"""
834
+ <div style="background: #111827; padding: 12px 16px; border-radius: 6px; margin-top: 8px; border-left: 3px solid #00ffd5;">
835
+ <span style="color: #00ffd5; font-weight: 600;">{ioc_type.upper()}</span>
836
+ <span style="color: #64748b; margin-left: 8px;">({len(items)} found)</span>
837
+ <div style="color: #94a3b8; font-size: 12px; margin-top: 8px; word-break: break-all;">
838
+ {', '.join(preview)}{'...' if len(items) > 5 else ''}
839
+ </div>
840
+ </div>
841
+ """
842
+
843
+ html += """
844
+ <div style="margin-top: 24px; padding: 16px; background: #111827; border-radius: 8px; border-left: 4px solid #00ffd5;">
845
+ <h4 style="color: #00ffd5; margin: 0 0 12px 0;">💡 Next Steps</h4>
846
+ <ul style="color: #94a3b8; margin: 0; padding-left: 20px;">
847
+ <li>Export IOCs for import into your SIEM/SOAR</li>
848
+ <li>Block malicious indicators at network perimeter</li>
849
+ <li>Search historical logs for these indicators</li>
850
+ <li>Share findings with threat intel sharing groups</li>
851
+ </ul>
852
+ </div>
853
+ </div>
854
+ """
855
+
856
+ # Return IOCs as formatted JSON
857
+ iocs_json = json.dumps(iocs, indent=2)
858
+
859
+ return ioc_chart, iocs_json, html
860
 
 
 
861
 
862
+ def get_threat_feed_stats() -> Tuple[go.Figure, str]:
863
+ """Get current statistics from all loaded threat feeds.
864
+
865
+ Shows the number of indicators loaded from each open-source
866
+ threat intelligence feed.
867
+
868
+ Returns:
869
+ Feed statistics chart and detailed HTML report
870
+ """
871
+ # Gather stats from all feeds
872
+ stats = {}
873
+
874
+ # URLhaus
875
+ urlhaus = feed_manager.get_urlhaus_urls()
876
+ stats["URLhaus URLs"] = len(urlhaus)
877
+
878
+ # ThreatFox
879
+ threatfox = feed_manager.get_threatfox_iocs()
880
+ stats["ThreatFox IOCs"] = len(threatfox)
881
+
882
+ # FeodoTracker
883
+ feodo = feed_manager.get_feodo_ips()
884
+ stats["Feodo Botnet IPs"] = len(feodo)
885
+
886
+ # Spamhaus
887
+ spamhaus = feed_manager.get_spamhaus_ips()
888
+ stats["Spamhaus DROP Ranges"] = len(spamhaus)
889
+
890
+ # Emerging Threats
891
+ et = feed_manager.get_emergingthreats_ips()
892
+ stats["Emerging Threats IPs"] = len(et)
893
+
894
+ # OpenPhish
895
+ phish = feed_manager.get_openphish_urls()
896
+ stats["OpenPhish URLs"] = len(phish)
897
+
898
+ # HIBP Breaches
899
+ hibp = feed_manager.get_hibp_breaches()
900
+ stats["Known Breaches"] = len(hibp)
901
+
902
+ # Create bar chart
903
+ fig = go.Figure(data=[go.Bar(
904
+ x=list(stats.keys()),
905
+ y=list(stats.values()),
906
+ marker_color=['#00ffd5', '#22c55e', '#fbbf24', '#f97316', '#ef4444', '#8b5cf6', '#06b6d4'],
907
+ text=list(stats.values()),
908
+ textposition='outside'
909
+ )])
910
+
911
+ fig.update_layout(
912
+ title={'text': '📊 Loaded Threat Intelligence', 'font': {'color': '#00ffd5'}},
913
+ paper_bgcolor='#0a0f1a',
914
+ plot_bgcolor='#111827',
915
+ font={'color': '#e2e8f0'},
916
+ height=350,
917
+ xaxis={'tickangle': 45, 'gridcolor': '#1e3a5f'},
918
+ yaxis={'gridcolor': '#1e3a5f'},
919
+ margin=dict(l=40, r=20, t=60, b=120)
920
+ )
921
+
922
+ total_indicators = sum(stats.values())
923
+
924
+ # Build HTML
925
+ html = f"""
926
+ <div style="font-family: 'JetBrains Mono', monospace; color: #e2e8f0;">
927
+ <h3 style="color: #00ffd5; border-bottom: 1px solid #1e3a5f; padding-bottom: 10px;">
928
+ 📡 Threat Feed Status
929
+ </h3>
930
+
931
+ <div style="background: #111827; padding: 20px; border-radius: 8px; margin: 20px 0; text-align: center;">
932
+ <div style="color: #64748b; font-size: 12px; text-transform: uppercase;">Total Indicators Loaded</div>
933
+ <div style="font-size: 48px; color: #00ffd5; font-weight: bold;">{total_indicators:,}</div>
934
+ </div>
935
+
936
+ <h4 style="color: #fbbf24; margin-top: 24px;">📋 Feed Details</h4>
937
+ <table style="width: 100%; border-collapse: collapse; margin-top: 12px;">
938
+ <tr style="background: #1e3a5f;">
939
+ <th style="padding: 12px; text-align: left; color: #00ffd5;">Feed</th>
940
+ <th style="padding: 12px; text-align: left; color: #00ffd5;">Indicators</th>
941
+ <th style="padding: 12px; text-align: left; color: #00ffd5;">Status</th>
942
+ </tr>
943
+ """
944
+
945
+ for feed_name, count in stats.items():
946
+ status = "✅ Loaded" if count > 0 else "⚠️ Empty"
947
+ status_color = "#22c55e" if count > 0 else "#f97316"
948
+ html += f"""
949
+ <tr style="border-bottom: 1px solid #1e3a5f;">
950
+ <td style="padding: 12px; color: #e2e8f0;">{feed_name}</td>
951
+ <td style="padding: 12px; color: #00ffd5;">{count:,}</td>
952
+ <td style="padding: 12px; color: {status_color};">{status}</td>
953
+ </tr>
954
+ """
955
+
956
+ html += """
957
+ </table>
958
+
959
+ <div style="margin-top: 24px; padding: 16px; background: #111827; border-radius: 8px; border-left: 4px solid #00ffd5;">
960
+ <h4 style="color: #00ffd5; margin: 0 0 12px 0;">ℹ️ About These Feeds</h4>
961
+ <ul style="color: #94a3b8; margin: 0; padding-left: 20px; font-size: 13px;">
962
+ <li><b>URLhaus:</b> Malicious URLs used for malware distribution (abuse.ch)</li>
963
+ <li><b>ThreatFox:</b> IOCs from malware samples and campaigns (abuse.ch)</li>
964
+ <li><b>FeodoTracker:</b> Botnet C2 servers (Emotet, Dridex, etc.)</li>
965
+ <li><b>Spamhaus DROP:</b> IPs controlled by spammers/criminals</li>
966
+ <li><b>Emerging Threats:</b> Compromised IPs from ProofPoint</li>
967
+ <li><b>OpenPhish:</b> Active phishing URLs</li>
968
+ <li><b>HIBP Breaches:</b> Public breach database metadata</li>
969
+ </ul>
970
+ <p style="color: #64748b; font-size: 11px; margin-top: 12px;">
971
+ Feeds refresh automatically every hour. All data is free and requires no API keys.
972
+ </p>
973
+ </div>
974
+ </div>
975
+ """
976
+
977
+ return fig, html
978
 
979
 
980
  # ============================================================================
981
+ # CUSTOM CSS
982
  # ============================================================================
983
 
984
  custom_css = """
 
987
  :root {
988
  --sw-bg-dark: #0a0f1a;
989
  --sw-bg-card: #111827;
 
990
  --sw-border: #1e3a5f;
991
  --sw-cyan: #00ffd5;
 
 
 
 
 
 
 
992
  }
993
 
 
994
  .gradio-container {
995
  background: var(--sw-bg-dark) !important;
996
  font-family: 'Inter', sans-serif !important;
 
997
  }
998
 
999
  .dark {
 
1002
  --border-color-primary: var(--sw-border) !important;
1003
  }
1004
 
1005
+ h1, h2, h3, h4 { font-family: 'JetBrains Mono', monospace !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1006
 
1007
  .tab-nav button {
1008
  font-family: 'JetBrains Mono', monospace !important;
1009
  font-size: 12px !important;
1010
  letter-spacing: 1px !important;
1011
  text-transform: uppercase !important;
 
 
 
1012
  }
1013
 
1014
  .tab-nav button.selected {
 
1016
  color: var(--sw-bg-dark) !important;
1017
  }
1018
 
1019
+ input, textarea {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1020
  font-family: 'JetBrains Mono', monospace !important;
 
 
 
1021
  background: var(--sw-bg-dark) !important;
 
 
 
 
1022
  border-color: var(--sw-border) !important;
1023
  }
1024
 
1025
+ input:focus, textarea:focus { border-color: var(--sw-cyan) !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1026
 
1027
+ .primary { background: linear-gradient(135deg, #00b396 0%, #007a6a 100%) !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1028
  """
1029
 
1030
+
1031
  # ============================================================================
1032
  # GRADIO INTERFACE
1033
  # ============================================================================
1034
 
1035
  with gr.Blocks(
1036
+ title="ShadowWatch | Open Source Threat Intel",
1037
  theme=gr.themes.Base(
1038
  primary_hue="cyan",
1039
+ secondary_hue="slate",
1040
  neutral_hue="slate",
1041
  ).set(
1042
  body_background_fill="#0a0f1a",
 
1044
  block_border_width="1px",
1045
  block_border_color="#1e3a5f",
1046
  button_primary_background_fill="#00b396",
 
 
1047
  ),
1048
  css=custom_css
1049
  ) as demo:
1050
 
1051
+ # Header
1052
  gr.HTML("""
1053
+ <div style="background: linear-gradient(90deg, #111827 0%, #0d1520 100%); border: 1px solid #1e3a5f; border-radius: 8px; padding: 20px 24px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
1054
+ <div style="display: flex; align-items: center; gap: 16px;">
1055
+ <div style="font-size: 40px;">🛡️</div>
1056
  <div>
1057
+ <div style="font-family: 'JetBrains Mono', monospace; font-size: 28px; font-weight: 700; color: white; letter-spacing: 3px;">SHADOWWATCH</div>
1058
+ <div style="font-size: 12px; color: #64748b; letter-spacing: 2px; text-transform: uppercase;">Open Source Threat Intelligence • No API Keys Required</div>
1059
  </div>
1060
  </div>
1061
+ <div style="display: flex; gap: 20px; align-items: center; font-family: 'JetBrains Mono', monospace; font-size: 11px;">
1062
+ <div style="color: #22c55e;">● 100% FREE</div>
1063
+ <div style="color: #00ffd5;">● MCP ENABLED</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1064
  </div>
1065
  </div>
1066
  """)
1067
 
1068
+ with gr.Tabs():
1069
+ # Indicator Scan Tab
1070
+ with gr.TabItem("🎯 SCAN INDICATOR"):
1071
+ gr.Markdown("**Check an IP, domain, or URL against open-source threat feeds.**")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1072
 
1073
+ with gr.Row():
1074
+ indicator_input = gr.Textbox(
1075
+ label="Indicator",
1076
+ placeholder="Enter IP, domain, or URL...",
1077
+ scale=3
1078
+ )
1079
+ indicator_type = gr.Radio(
1080
+ choices=["auto", "ip", "domain", "url"],
1081
+ value="auto",
1082
+ label="Type",
1083
+ scale=1
1084
+ )
1085
+ scan_btn = gr.Button("🔍 SCAN", variant="primary", scale=1)
1086
 
1087
+ with gr.Row():
1088
+ risk_gauge = gr.Plot(label="Threat Score")
1089
+ sources_chart = gr.Plot(label="Detection Sources")
1090
+
1091
+ scan_report = gr.HTML(label="Analysis Report")
1092
+
1093
+ scan_btn.click(
1094
+ fn=scan_indicator,
1095
+ inputs=[indicator_input, indicator_type],
1096
+ outputs=[risk_gauge, sources_chart, scan_report]
1097
+ )
1098
+
1099
+ # IOC Extractor Tab
1100
+ with gr.TabItem("📋 IOC EXTRACTOR"):
1101
+ gr.Markdown("**Extract and analyze Indicators of Compromise from text, logs, or reports.**")
1102
+
1103
+ text_input = gr.Textbox(
1104
+ label="Paste Text to Analyze",
1105
+ placeholder="Paste logs, reports, emails, or any text containing potential IOCs...",
1106
+ lines=8
1107
+ )
1108
+ extract_btn = gr.Button("🔬 EXTRACT & ANALYZE", variant="primary")
1109
+
1110
+ with gr.Row():
1111
+ ioc_chart = gr.Plot(label="IOC Distribution")
1112
+ ioc_json = gr.Code(label="Extracted IOCs (JSON)", language="json")
1113
+
1114
+ ioc_report = gr.HTML(label="Analysis Report")
1115
+
1116
+ extract_btn.click(
1117
+ fn=extract_and_analyze_iocs,
1118
+ inputs=[text_input],
1119
+ outputs=[ioc_chart, ioc_json, ioc_report]
1120
+ )
1121
 
1122
+ # Threat Feeds Tab
1123
+ with gr.TabItem("📡 THREAT FEEDS"):
1124
+ gr.Markdown("**View loaded threat intelligence from open-source feeds.**")
1125
+
1126
+ refresh_btn = gr.Button("🔄 REFRESH FEEDS", variant="primary")
1127
+
1128
+ feed_chart = gr.Plot(label="Feed Statistics")
1129
+ feed_report = gr.HTML(label="Feed Details")
1130
+
1131
+ refresh_btn.click(
1132
+ fn=get_threat_feed_stats,
1133
+ inputs=[],
1134
+ outputs=[feed_chart, feed_report]
1135
+ )
1136
+
1137
+ # Load on page load
1138
+ demo.load(fn=get_threat_feed_stats, outputs=[feed_chart, feed_report])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1139
 
1140
  # Footer
1141
  gr.HTML("""
1142
+ <div style="background: #111827; border: 1px solid #1e3a5f; border-radius: 8px; padding: 16px 24px; margin-top: 20px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #64748b;">
1143
+ <div style="display: flex; justify-content: space-between; align-items: center;">
1144
+ <div>
1145
+ <span>🔗 MCP:</span>
1146
+ <span style="color: #00ffd5; margin-left: 8px;">https://crypticallyrequie-shadowwatchv2.hf.space/gradio_api/mcp/sse</span>
1147
+ </div>
1148
+ <div>
1149
+ Built by <span style="color: #00ffd5;">Cogensec</span> | ARGUS Platform
1150
+ </div>
1151
  </div>
1152
+ <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #1e3a5f;">
1153
+ Data Sources: abuse.ch (URLhaus, ThreatFox, FeodoTracker, MalwareBazaar) • Spamhaus • Emerging Threats • OpenPhish • HIBP
1154
  </div>
1155
  </div>
1156
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1157
 
1158
+
1159
  if __name__ == "__main__":
1160
+ demo.launch(mcp_server=True)