danielostrow commited on
Commit
fb62c6c
·
verified ·
1 Parent(s): 7f5a3a7

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +84 -231
app.py CHANGED
@@ -21,6 +21,7 @@ sentinel = C2Sentinel.load('c2_sentinel')
21
 
22
  # Example connection data
23
  EXAMPLES = {
 
24
  "C2 Beacon (60s intervals)": json.dumps([
25
  {"timestamp": 1705600000, "dst_ip": "45.33.32.156", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},
26
  {"timestamp": 1705600060, "dst_ip": "45.33.32.156", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},
@@ -31,15 +32,13 @@ EXAMPLES = {
31
  {"timestamp": 1705600360, "dst_ip": "45.33.32.156", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},
32
  {"timestamp": 1705600420, "dst_ip": "45.33.32.156", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},
33
  ], indent=2),
34
-
35
- "Metasploit Default Port": json.dumps([
36
  {"timestamp": 1705600000, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
37
  {"timestamp": 1705600030, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
38
  {"timestamp": 1705600060, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
39
  {"timestamp": 1705600090, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
40
  {"timestamp": 1705600120, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
41
  ], indent=2),
42
-
43
  "SSH Keepalive (Legitimate)": json.dumps([
44
  {"timestamp": 1705600000, "dst_ip": "192.168.1.10", "dst_port": 22, "bytes_sent": 48, "bytes_recv": 48},
45
  {"timestamp": 1705600030, "dst_ip": "192.168.1.10", "dst_port": 22, "bytes_sent": 48, "bytes_recv": 48},
@@ -48,7 +47,6 @@ EXAMPLES = {
48
  {"timestamp": 1705600120, "dst_ip": "192.168.1.10", "dst_port": 22, "bytes_sent": 48, "bytes_recv": 48},
49
  {"timestamp": 1705600150, "dst_ip": "192.168.1.10", "dst_port": 22, "bytes_sent": 48, "bytes_recv": 48},
50
  ], indent=2),
51
-
52
  "Web Browsing (Legitimate)": json.dumps([
53
  {"timestamp": 1705600000, "dst_ip": "93.184.216.34", "dst_port": 443, "bytes_sent": 500, "bytes_recv": 15000},
54
  {"timestamp": 1705600002, "dst_ip": "93.184.216.34", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 8000},
@@ -56,23 +54,13 @@ EXAMPLES = {
56
  {"timestamp": 1705600015, "dst_ip": "172.217.14.206", "dst_port": 443, "bytes_sent": 300, "bytes_recv": 12000},
57
  {"timestamp": 1705600025, "dst_ip": "151.101.1.140", "dst_port": 443, "bytes_sent": 150, "bytes_recv": 5000},
58
  ], indent=2),
59
-
60
- "Slow Beacon (5 min intervals)": json.dumps([
61
  {"timestamp": 1705600000, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
62
  {"timestamp": 1705600300, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
63
  {"timestamp": 1705600600, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
64
  {"timestamp": 1705600900, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
65
  {"timestamp": 1705601200, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
66
  ], indent=2),
67
-
68
- "DNS Tunnel C2": json.dumps([
69
- {"timestamp": 1705600000, "dst_ip": "198.51.100.53", "dst_port": 53, "bytes_sent": 64, "bytes_recv": 512},
70
- {"timestamp": 1705600005, "dst_ip": "198.51.100.53", "dst_port": 53, "bytes_sent": 64, "bytes_recv": 512},
71
- {"timestamp": 1705600010, "dst_ip": "198.51.100.53", "dst_port": 53, "bytes_sent": 64, "bytes_recv": 512},
72
- {"timestamp": 1705600015, "dst_ip": "198.51.100.53", "dst_port": 53, "bytes_sent": 64, "bytes_recv": 512},
73
- {"timestamp": 1705600020, "dst_ip": "198.51.100.53", "dst_port": 53, "bytes_sent": 64, "bytes_recv": 512},
74
- {"timestamp": 1705600025, "dst_ip": "198.51.100.53", "dst_port": 53, "bytes_sent": 64, "bytes_recv": 512},
75
- ], indent=2),
76
  }
77
 
78
 
@@ -86,7 +74,6 @@ def parse_log_file(file_content: str) -> list:
86
  if not line or line.startswith('#'):
87
  continue
88
 
89
- # Try JSON format
90
  try:
91
  record = json.loads(line)
92
  if 'dst_ip' in record or 'id.resp_h' in record:
@@ -103,7 +90,6 @@ def parse_log_file(file_content: str) -> list:
103
  except (json.JSONDecodeError, ValueError):
104
  pass
105
 
106
- # Try Zeek tab-separated format
107
  parts = line.split('\t')
108
  if len(parts) >= 10:
109
  try:
@@ -115,7 +101,6 @@ def parse_log_file(file_content: str) -> list:
115
  'bytes_recv': int(parts[10] if len(parts) > 10 and parts[10] != '-' else 0),
116
  }
117
  connections.append(conn)
118
- continue
119
  except (ValueError, IndexError):
120
  pass
121
 
@@ -128,127 +113,86 @@ def analyze_connections(
128
  threshold: float,
129
  strict_mode: bool,
130
  whitelist_ips: str,
131
- whitelist_domains: str,
132
- blacklist_ips: str,
133
- blacklist_domains: str
134
- ) -> tuple:
135
  """Analyze connection data and return results."""
136
  try:
137
- # Reset whitelist/blacklist for this analysis
138
  sentinel.whitelist_ips = set()
139
  sentinel.whitelist_domains = set()
140
  sentinel.blacklist_ips = set()
141
  sentinel.blacklist_domains = set()
142
 
143
  # Apply whitelist
144
- if whitelist_ips.strip():
145
  ips = [ip.strip() for ip in whitelist_ips.split(',') if ip.strip()]
146
  sentinel.add_whitelist(ips=ips)
147
- if whitelist_domains.strip():
148
- domains = [d.strip() for d in whitelist_domains.split(',') if d.strip()]
149
- sentinel.add_whitelist(domains=domains)
150
 
151
  # Apply blacklist
152
- if blacklist_ips.strip():
153
  ips = [ip.strip() for ip in blacklist_ips.split(',') if ip.strip()]
154
  sentinel.add_blacklist(ips=ips)
155
- if blacklist_domains.strip():
156
- domains = [d.strip() for d in blacklist_domains.split(',') if d.strip()]
157
- sentinel.add_blacklist(domains=domains)
158
 
159
- # Get connections from file upload or text input
160
  connections = []
161
 
162
  if uploaded_file is not None:
163
  file_content = uploaded_file.decode('utf-8') if isinstance(uploaded_file, bytes) else open(uploaded_file, 'r').read()
164
  connections = parse_log_file(file_content)
165
  if not connections:
166
- # Try as JSON array
167
  try:
168
  connections = json.loads(file_content)
169
  except:
170
  pass
171
 
172
- if not connections and connection_json.strip():
173
  connections = json.loads(connection_json)
174
 
175
  if not isinstance(connections, list):
176
- return "Error: Input must be a JSON array of connection objects", "", "", ""
177
 
178
  if len(connections) < 3:
179
- return "Error: Need at least 3 connections for analysis", "", "", ""
180
 
181
  # Run analysis
182
  result = sentinel.analyze(connections, threshold=threshold, strict_mode=strict_mode)
183
 
184
- # Format primary result
185
  if result.is_c2:
186
- verdict = f"**C2 DETECTED:** {result.c2_type}"
187
  else:
188
- verdict = "**No C2 Detected**"
189
 
190
- primary = f"""## {verdict}
191
-
192
- | Metric | Value |
193
- |--------|-------|
194
- | Probability | {result.c2_probability:.1%} |
195
- | Confidence | {result.confidence:.1%} |
196
- | Detection Method | {result.detection_method} |
197
- | Connections Analyzed | {len(connections)} |
198
- """
199
 
200
  if result.matched_legitimate_pattern:
201
- primary += f"| Matched Pattern | {result.matched_legitimate_pattern} |\n"
202
- if result.service_type:
203
- primary += f"| Service Type | {result.service_type} |\n"
204
- if result.immediate_detection:
205
- primary += "| Immediate Detection | Yes (signature match) |\n"
206
-
207
- # Format risk factors
208
- risk_text = ""
209
  if result.risk_factors:
210
- risk_text = "### Risk Factors\n\n"
211
- for factor in result.risk_factors:
212
- risk_text += f"- {factor}\n"
213
 
214
  if result.mitigating_factors:
215
- risk_text += "\n### Mitigating Factors\n\n"
216
- for factor in result.mitigating_factors:
217
- risk_text += f"- {factor}\n"
218
 
219
- # Format recommendations
220
- rec_text = ""
221
  if result.recommendations:
222
- rec_text = "### Recommendations\n\n"
223
- for rec in result.recommendations:
224
- rec_text += f"- {rec}\n"
225
-
226
- # Connection stats
227
- stats_text = "### Connection Statistics\n\n"
228
- if connections:
229
- dst_ips = set(c.get('dst_ip', '') for c in connections)
230
- dst_ports = set(c.get('dst_port', 0) for c in connections)
231
- total_sent = sum(c.get('bytes_sent', 0) for c in connections)
232
- total_recv = sum(c.get('bytes_recv', 0) for c in connections)
233
-
234
- stats_text += f"| Stat | Value |\n|------|-------|\n"
235
- stats_text += f"| Unique Destinations | {len(dst_ips)} |\n"
236
- stats_text += f"| Unique Ports | {len(dst_ports)} |\n"
237
- stats_text += f"| Total Bytes Sent | {total_sent:,} |\n"
238
- stats_text += f"| Total Bytes Received | {total_recv:,} |\n"
239
-
240
- if len(connections) > 1:
241
- timestamps = sorted(c.get('timestamp', 0) for c in connections)
242
- intervals = [timestamps[i+1] - timestamps[i] for i in range(len(timestamps)-1)]
243
- avg_interval = sum(intervals) / len(intervals)
244
- stats_text += f"| Avg Interval | {avg_interval:.1f}s |\n"
245
-
246
- return primary, risk_text, rec_text, stats_text
247
 
248
  except json.JSONDecodeError as e:
249
- return f"Error: Invalid JSON - {str(e)}", "", "", ""
250
  except Exception as e:
251
- return f"Error: {str(e)}", "", "", ""
252
 
253
 
254
  def load_example(example_name: str) -> str:
@@ -256,141 +200,57 @@ def load_example(example_name: str) -> str:
256
  return EXAMPLES.get(example_name, "")
257
 
258
 
259
- # Build the interface
260
  with gr.Blocks(title="C2Sentinel", theme=gr.themes.Soft()) as demo:
261
  gr.Markdown("""
262
- # C2Sentinel
263
 
264
- **Command and Control Beacon Detection**
265
 
266
- Analyze network connection patterns to detect C2 beacon activity using behavioral analysis.
267
- The model identifies C2 communications on any port by analyzing timing patterns, packet sizes, and traffic symmetry.
268
-
269
- [Model Repository](https://huggingface.co/danielostrow/c2sentinel) | [API Documentation](https://huggingface.co/danielostrow/c2sentinel/blob/main/API_REFERENCE.md) | [neuralintellect.com](https://neuralintellect.com)
270
  """)
271
 
272
- with gr.Tabs():
273
- with gr.TabItem("Analyze"):
274
- with gr.Row():
275
- with gr.Column(scale=1):
276
- gr.Markdown("### Input")
277
-
278
- example_dropdown = gr.Dropdown(
279
- choices=list(EXAMPLES.keys()),
280
- label="Load Example",
281
- value=None
282
- )
283
-
284
- connection_input = gr.Textbox(
285
- label="Connection Data (JSON)",
286
- placeholder='[\n {"timestamp": 1000000, "dst_ip": "10.0.0.1", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},\n ...\n]',
287
- lines=12
288
- )
289
-
290
- file_upload = gr.File(
291
- label="Or Upload Log File (JSON, Zeek conn.log)",
292
- file_types=[".json", ".log", ".txt"],
293
- type="binary"
294
- )
295
-
296
- gr.Markdown("### Detection Settings")
297
-
298
- threshold = gr.Slider(
299
- minimum=0.1,
300
- maximum=0.9,
301
- value=0.5,
302
- step=0.05,
303
- label="Detection Threshold",
304
- info="Lower = more sensitive, Higher = fewer false positives"
305
- )
306
-
307
- strict_mode = gr.Checkbox(
308
- label="Strict Mode",
309
- value=False,
310
- info="Enforce minimum 0.7 threshold for high-confidence detections only"
311
- )
312
-
313
- analyze_btn = gr.Button("Analyze", variant="primary", size="lg")
314
-
315
- with gr.Column(scale=1):
316
- gr.Markdown("### Results")
317
- result_primary = gr.Markdown()
318
- result_stats = gr.Markdown()
319
- result_risks = gr.Markdown()
320
- result_recommendations = gr.Markdown()
321
-
322
- with gr.TabItem("Whitelist / Blacklist"):
323
- gr.Markdown("""
324
- ### Configure Trusted and Blocked Indicators
325
-
326
- Add IPs and domains to customize detection behavior. Separate multiple entries with commas.
327
- """)
328
 
329
  with gr.Row():
330
- with gr.Column():
331
- gr.Markdown("#### Whitelist (Trusted)")
332
- whitelist_ips = gr.Textbox(
333
- label="Trusted IPs",
334
- placeholder="8.8.8.8, 1.1.1.1, 192.168.1.0/24",
335
- lines=2
336
- )
337
- whitelist_domains = gr.Textbox(
338
- label="Trusted Domains",
339
- placeholder="google.com, microsoft.com, github.com",
340
- lines=2
341
- )
342
-
343
- with gr.Column():
344
- gr.Markdown("#### Blacklist (Suspicious)")
345
- blacklist_ips = gr.Textbox(
346
- label="Blocked IPs",
347
- placeholder="10.10.10.10, 45.33.32.156",
348
- lines=2
349
- )
350
- blacklist_domains = gr.Textbox(
351
- label="Blocked Domains",
352
- placeholder="malware.example.com, c2server.bad",
353
- lines=2
354
- )
355
-
356
- gr.Markdown("""
357
- **Note:** Whitelist/blacklist settings apply to the current analysis only.
358
- - Whitelisted IPs will reduce C2 probability
359
- - Blacklisted IPs will increase C2 probability
360
- """)
361
-
362
- with gr.TabItem("Log Format"):
363
- gr.Markdown("""
364
- ### Supported Log Formats
365
-
366
- #### JSON Array
367
- ```json
368
- [
369
- {"timestamp": 1705600000, "dst_ip": "10.0.0.1", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},
370
- {"timestamp": 1705600060, "dst_ip": "10.0.0.1", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500}
371
- ]
372
- ```
373
-
374
- #### JSON Lines (NDJSON)
375
- ```
376
- {"timestamp": 1705600000, "dst_ip": "10.0.0.1", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500}
377
- {"timestamp": 1705600060, "dst_ip": "10.0.0.1", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500}
378
- ```
379
-
380
- #### Zeek conn.log Format
381
- The parser also supports Zeek/Bro conn.log tab-separated format with fields:
382
- `ts, uid, id.orig_h, id.orig_p, id.resp_h, id.resp_p, proto, service, duration, orig_bytes, resp_bytes, ...`
383
-
384
- ### Required Fields
385
-
386
- | Field | Type | Description |
387
- |-------|------|-------------|
388
- | `timestamp` | float | Unix timestamp |
389
- | `dst_ip` | string | Destination IP address |
390
- | `dst_port` | int | Destination port |
391
- | `bytes_sent` | int | Bytes sent |
392
- | `bytes_recv` | int | Bytes received |
393
- """)
394
 
395
  # Event handlers
396
  example_dropdown.change(
@@ -401,24 +261,17 @@ The parser also supports Zeek/Bro conn.log tab-separated format with fields:
401
 
402
  analyze_btn.click(
403
  fn=analyze_connections,
404
- inputs=[
405
- connection_input,
406
- file_upload,
407
- threshold,
408
- strict_mode,
409
- whitelist_ips,
410
- whitelist_domains,
411
- blacklist_ips,
412
- blacklist_domains
413
- ],
414
- outputs=[result_primary, result_risks, result_recommendations, result_stats]
415
  )
416
 
417
  gr.Markdown("""
418
  ---
419
- **Author:** Daniel Ostrow | [neuralintellect.com](https://neuralintellect.com) | Built on [LogBERT](https://arxiv.org/abs/2103.04475)
 
 
420
  """)
421
 
422
 
423
  if __name__ == "__main__":
424
- demo.launch()
 
21
 
22
  # Example connection data
23
  EXAMPLES = {
24
+ "-- Select Example --": "",
25
  "C2 Beacon (60s intervals)": json.dumps([
26
  {"timestamp": 1705600000, "dst_ip": "45.33.32.156", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},
27
  {"timestamp": 1705600060, "dst_ip": "45.33.32.156", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},
 
32
  {"timestamp": 1705600360, "dst_ip": "45.33.32.156", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},
33
  {"timestamp": 1705600420, "dst_ip": "45.33.32.156", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500},
34
  ], indent=2),
35
+ "Metasploit Port 4444": json.dumps([
 
36
  {"timestamp": 1705600000, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
37
  {"timestamp": 1705600030, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
38
  {"timestamp": 1705600060, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
39
  {"timestamp": 1705600090, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
40
  {"timestamp": 1705600120, "dst_ip": "10.10.10.10", "dst_port": 4444, "bytes_sent": 150, "bytes_recv": 300},
41
  ], indent=2),
 
42
  "SSH Keepalive (Legitimate)": json.dumps([
43
  {"timestamp": 1705600000, "dst_ip": "192.168.1.10", "dst_port": 22, "bytes_sent": 48, "bytes_recv": 48},
44
  {"timestamp": 1705600030, "dst_ip": "192.168.1.10", "dst_port": 22, "bytes_sent": 48, "bytes_recv": 48},
 
47
  {"timestamp": 1705600120, "dst_ip": "192.168.1.10", "dst_port": 22, "bytes_sent": 48, "bytes_recv": 48},
48
  {"timestamp": 1705600150, "dst_ip": "192.168.1.10", "dst_port": 22, "bytes_sent": 48, "bytes_recv": 48},
49
  ], indent=2),
 
50
  "Web Browsing (Legitimate)": json.dumps([
51
  {"timestamp": 1705600000, "dst_ip": "93.184.216.34", "dst_port": 443, "bytes_sent": 500, "bytes_recv": 15000},
52
  {"timestamp": 1705600002, "dst_ip": "93.184.216.34", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 8000},
 
54
  {"timestamp": 1705600015, "dst_ip": "172.217.14.206", "dst_port": 443, "bytes_sent": 300, "bytes_recv": 12000},
55
  {"timestamp": 1705600025, "dst_ip": "151.101.1.140", "dst_port": 443, "bytes_sent": 150, "bytes_recv": 5000},
56
  ], indent=2),
57
+ "Slow Beacon (5 min)": json.dumps([
 
58
  {"timestamp": 1705600000, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
59
  {"timestamp": 1705600300, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
60
  {"timestamp": 1705600600, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
61
  {"timestamp": 1705600900, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
62
  {"timestamp": 1705601200, "dst_ip": "203.0.113.50", "dst_port": 8080, "bytes_sent": 256, "bytes_recv": 512},
63
  ], indent=2),
 
 
 
 
 
 
 
 
 
64
  }
65
 
66
 
 
74
  if not line or line.startswith('#'):
75
  continue
76
 
 
77
  try:
78
  record = json.loads(line)
79
  if 'dst_ip' in record or 'id.resp_h' in record:
 
90
  except (json.JSONDecodeError, ValueError):
91
  pass
92
 
 
93
  parts = line.split('\t')
94
  if len(parts) >= 10:
95
  try:
 
101
  'bytes_recv': int(parts[10] if len(parts) > 10 and parts[10] != '-' else 0),
102
  }
103
  connections.append(conn)
 
104
  except (ValueError, IndexError):
105
  pass
106
 
 
113
  threshold: float,
114
  strict_mode: bool,
115
  whitelist_ips: str,
116
+ blacklist_ips: str
117
+ ) -> str:
 
 
118
  """Analyze connection data and return results."""
119
  try:
120
+ # Reset whitelist/blacklist
121
  sentinel.whitelist_ips = set()
122
  sentinel.whitelist_domains = set()
123
  sentinel.blacklist_ips = set()
124
  sentinel.blacklist_domains = set()
125
 
126
  # Apply whitelist
127
+ if whitelist_ips and whitelist_ips.strip():
128
  ips = [ip.strip() for ip in whitelist_ips.split(',') if ip.strip()]
129
  sentinel.add_whitelist(ips=ips)
 
 
 
130
 
131
  # Apply blacklist
132
+ if blacklist_ips and blacklist_ips.strip():
133
  ips = [ip.strip() for ip in blacklist_ips.split(',') if ip.strip()]
134
  sentinel.add_blacklist(ips=ips)
 
 
 
135
 
136
+ # Get connections
137
  connections = []
138
 
139
  if uploaded_file is not None:
140
  file_content = uploaded_file.decode('utf-8') if isinstance(uploaded_file, bytes) else open(uploaded_file, 'r').read()
141
  connections = parse_log_file(file_content)
142
  if not connections:
 
143
  try:
144
  connections = json.loads(file_content)
145
  except:
146
  pass
147
 
148
+ if not connections and connection_json and connection_json.strip():
149
  connections = json.loads(connection_json)
150
 
151
  if not isinstance(connections, list):
152
+ return "## Error\nInput must be a JSON array of connection objects"
153
 
154
  if len(connections) < 3:
155
+ return "## Error\nNeed at least 3 connections for analysis"
156
 
157
  # Run analysis
158
  result = sentinel.analyze(connections, threshold=threshold, strict_mode=strict_mode)
159
 
160
+ # Format result
161
  if result.is_c2:
162
+ output = f"## 🚨 C2 DETECTED: {result.c2_type}\n\n"
163
  else:
164
+ output = "## ✅ No C2 Detected\n\n"
165
 
166
+ output += f"| Metric | Value |\n|--------|-------|\n"
167
+ output += f"| Probability | {result.c2_probability:.1%} |\n"
168
+ output += f"| Confidence | {result.confidence:.1%} |\n"
169
+ output += f"| Detection Method | {result.detection_method} |\n"
170
+ output += f"| Connections | {len(connections)} |\n"
 
 
 
 
171
 
172
  if result.matched_legitimate_pattern:
173
+ output += f"| Matched Pattern | {result.matched_legitimate_pattern} |\n"
174
+
 
 
 
 
 
 
175
  if result.risk_factors:
176
+ output += "\n### Risk Factors\n"
177
+ for f in result.risk_factors[:5]:
178
+ output += f"- {f}\n"
179
 
180
  if result.mitigating_factors:
181
+ output += "\n### Mitigating Factors\n"
182
+ for f in result.mitigating_factors[:5]:
183
+ output += f"- {f}\n"
184
 
 
 
185
  if result.recommendations:
186
+ output += "\n### Recommendations\n"
187
+ for r in result.recommendations[:3]:
188
+ output += f"- {r}\n"
189
+
190
+ return output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
  except json.JSONDecodeError as e:
193
+ return f"## Error\nInvalid JSON: {str(e)}"
194
  except Exception as e:
195
+ return f"## Error\n{str(e)}"
196
 
197
 
198
  def load_example(example_name: str) -> str:
 
200
  return EXAMPLES.get(example_name, "")
201
 
202
 
203
+ # Build interface
204
  with gr.Blocks(title="C2Sentinel", theme=gr.themes.Soft()) as demo:
205
  gr.Markdown("""
206
+ # C2Sentinel - C2 Beacon Detection
207
 
208
+ Analyze network connections for Command and Control beacon activity.
209
 
210
+ [Model](https://huggingface.co/danielostrow/c2sentinel) | [Docs](https://huggingface.co/danielostrow/c2sentinel/blob/main/API_REFERENCE.md) | [neuralintellect.com](https://neuralintellect.com)
 
 
 
211
  """)
212
 
213
+ with gr.Row():
214
+ with gr.Column():
215
+ example_dropdown = gr.Dropdown(
216
+ choices=list(EXAMPLES.keys()),
217
+ value="-- Select Example --",
218
+ label="Load Example"
219
+ )
220
+
221
+ connection_input = gr.Textbox(
222
+ label="Connection Data (JSON)",
223
+ placeholder='[{"timestamp": 1000000, "dst_ip": "10.0.0.1", "dst_port": 443, "bytes_sent": 200, "bytes_recv": 500}, ...]',
224
+ lines=10
225
+ )
226
+
227
+ file_upload = gr.File(
228
+ label="Or Upload Log File",
229
+ file_types=[".json", ".log", ".txt"],
230
+ type="binary"
231
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
  with gr.Row():
234
+ threshold = gr.Slider(
235
+ minimum=0.1, maximum=0.9, value=0.5, step=0.1,
236
+ label="Threshold (lower=sensitive)"
237
+ )
238
+ strict_mode = gr.Checkbox(label="Strict Mode", value=False)
239
+
240
+ with gr.Accordion("Whitelist / Blacklist", open=False):
241
+ whitelist_ips = gr.Textbox(
242
+ label="Whitelist IPs (comma-separated)",
243
+ placeholder="8.8.8.8, 1.1.1.1"
244
+ )
245
+ blacklist_ips = gr.Textbox(
246
+ label="Blacklist IPs (comma-separated)",
247
+ placeholder="10.10.10.10"
248
+ )
249
+
250
+ analyze_btn = gr.Button("Analyze", variant="primary")
251
+
252
+ with gr.Column():
253
+ result_output = gr.Markdown(label="Results")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  # Event handlers
256
  example_dropdown.change(
 
261
 
262
  analyze_btn.click(
263
  fn=analyze_connections,
264
+ inputs=[connection_input, file_upload, threshold, strict_mode, whitelist_ips, blacklist_ips],
265
+ outputs=[result_output]
 
 
 
 
 
 
 
 
 
266
  )
267
 
268
  gr.Markdown("""
269
  ---
270
+ **Format:** `[{"timestamp": float, "dst_ip": "x.x.x.x", "dst_port": int, "bytes_sent": int, "bytes_recv": int}, ...]`
271
+
272
+ Built on [LogBERT](https://arxiv.org/abs/2103.04475) | Author: Daniel Ostrow
273
  """)
274
 
275
 
276
  if __name__ == "__main__":
277
+ demo.launch(server_name="0.0.0.0", server_port=7860)