AYI-NEDJIMI commited on
Commit
68e176b
Β·
verified Β·
1 Parent(s): 6edf81d

Initial Bug Bounty & Pentesting Explorer Space

Browse files
Files changed (3) hide show
  1. README.md +39 -7
  2. app.py +486 -0
  3. requirements.txt +4 -0
README.md CHANGED
@@ -1,12 +1,44 @@
1
  ---
2
- title: Bug Bounty Pentest Explorer
3
- emoji: πŸ“‰
4
- colorFrom: green
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 6.5.1
8
  app_file: app.py
9
- pinned: false
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Bug Bounty & Pentesting Explorer
3
+ emoji: πŸ›‘οΈ
4
+ colorFrom: red
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.50.0
8
  app_file: app.py
9
+ pinned: true
10
+ license: mit
11
+ short_description: Bug bounty methodologies, techniques, tools in FR/EN
12
  ---
13
 
14
+ # Bug Bounty & Pentesting Explorer
15
+
16
+ An interactive explorer for bug bounty and penetration testing resources, built with Gradio.
17
+
18
+ ## Features
19
+
20
+ - **Methodologies** – Browse pentesting methodologies with phases, scope, and deliverables
21
+ - **Checklists** – Filter security checklists by target type (Web, API, Mobile, Cloud, AD)
22
+ - **Attack Techniques** – Explore techniques by category with impact, exploitation steps, payloads, and remediation
23
+ - **Platforms** – Compare bug bounty platforms, payout models, and top programs
24
+ - **Report Templates** – View and use vulnerability report templates
25
+ - **Tools** – Discover security tools filtered by category
26
+ - **Q&A** – Browse common questions and answers about bug bounty and pentesting
27
+ - **Statistics** – Visualize data with charts on categories, bounty ranges, and technique distribution
28
+
29
+ ## Datasets
30
+
31
+ - [AYI-NEDJIMI/bug-bounty-pentest-fr](https://huggingface.co/datasets/AYI-NEDJIMI/bug-bounty-pentest-fr) (French)
32
+ - [AYI-NEDJIMI/bug-bounty-pentest-en](https://huggingface.co/datasets/AYI-NEDJIMI/bug-bounty-pentest-en) (English)
33
+
34
+ ## Disclaimer
35
+
36
+ ⚠️ This resource is strictly for **authorized security testing** and **educational purposes** only.
37
+
38
+ ## Author
39
+
40
+ **AYI-NEDJIMI Consultants**
41
+ - 🌐 [ayinedjimi-consultants.fr](https://ayinedjimi-consultants.fr)
42
+ - πŸ’Ό [LinkedIn](https://linkedin.com/in/ayi-nedjimi)
43
+ - πŸ™ [GitHub](https://github.com/ayi-nedjimi)
44
+ - 🐦 [Twitter/X](https://x.com/ayi_nedjimi)
app.py ADDED
@@ -0,0 +1,486 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import plotly.graph_objects as go
5
+ from datasets import load_dataset
6
+ import json
7
+ import re
8
+
9
+ # ---------------------------------------------------------------------------
10
+ # Load datasets
11
+ # ---------------------------------------------------------------------------
12
+
13
+ def load_data(lang="en"):
14
+ """Load the bug-bounty-pentest dataset for the given language."""
15
+ repo = f"AYI-NEDJIMI/bug-bounty-pentest-{lang}"
16
+ try:
17
+ ds = load_dataset(repo, split="train")
18
+ return ds.to_pandas()
19
+ except Exception as e:
20
+ print(f"Error loading {repo}: {e}")
21
+ return pd.DataFrame()
22
+
23
+ df_en = load_data("en")
24
+ df_fr = load_data("fr")
25
+
26
+ DATASETS = {"EN": df_en, "FR": df_fr}
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Helpers
30
+ # ---------------------------------------------------------------------------
31
+
32
+ def get_df(lang):
33
+ return DATASETS.get(lang, df_en)
34
+
35
+ def safe(val):
36
+ """Return a cleaned string representation; handle NaN / None / lists."""
37
+ if val is None or (isinstance(val, float) and pd.isna(val)):
38
+ return ""
39
+ if isinstance(val, list):
40
+ return "\n".join(str(v) for v in val)
41
+ return str(val)
42
+
43
+ def safe_json_list(val):
44
+ """Try to interpret a value as a list (JSON string or Python list)."""
45
+ if val is None or (isinstance(val, float) and pd.isna(val)):
46
+ return []
47
+ if isinstance(val, list):
48
+ return val
49
+ s = str(val).strip()
50
+ if s.startswith("["):
51
+ try:
52
+ return json.loads(s)
53
+ except Exception:
54
+ pass
55
+ return [s] if s else []
56
+
57
+ def bullet_list(items):
58
+ """Format a list as a Markdown bullet list."""
59
+ lst = safe_json_list(items)
60
+ if not lst:
61
+ return "_No items_"
62
+ return "\n".join(f"- {item}" for item in lst)
63
+
64
+ def filter_type(df, entry_type):
65
+ subset = df[df["type"] == entry_type] if "type" in df.columns else pd.DataFrame()
66
+ return subset.reset_index(drop=True)
67
+
68
+ def unique_vals(df, col):
69
+ if col not in df.columns:
70
+ return []
71
+ vals = df[col].dropna().unique().tolist()
72
+ return sorted(set(str(v) for v in vals))
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # CSS
76
+ # ---------------------------------------------------------------------------
77
+
78
+ CUSTOM_CSS = """
79
+ footer.custom-footer {
80
+ text-align: center; padding: 18px 10px 10px 10px;
81
+ font-size: 0.92em; color: #888; border-top: 1px solid #ddd; margin-top: 20px;
82
+ }
83
+ footer.custom-footer a { color: #5b7ff5; text-decoration: none; margin: 0 8px; }
84
+ footer.custom-footer a:hover { text-decoration: underline; }
85
+ .disclaimer-box {
86
+ background: #fff3cd; border-left: 4px solid #ffc107; padding: 12px 16px;
87
+ margin: 10px 0; border-radius: 4px; font-size: 0.95em; color: #856404;
88
+ }
89
+ """
90
+
91
+ FOOTER_HTML = """
92
+ <footer class="custom-footer">
93
+ <p>⚠️ <strong>Disclaimer:</strong> This resource is for <em>authorized security testing</em> and <em>educational purposes</em> only.</p>
94
+ <p>
95
+ <a href="https://ayinedjimi-consultants.fr" target="_blank">🌐 ayinedjimi-consultants.fr</a>
96
+ <a href="https://linkedin.com/in/ayi-nedjimi" target="_blank">πŸ’Ό LinkedIn</a>
97
+ <a href="https://github.com/ayi-nedjimi" target="_blank">πŸ™ GitHub</a>
98
+ <a href="https://x.com/ayi_nedjimi" target="_blank">🐦 Twitter / X</a>
99
+ </p>
100
+ <p style="font-size:0.85em;">Β© 2026 AYI-NEDJIMI Consultants – Built with ❀️ and Gradio</p>
101
+ </footer>
102
+ """
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # Tab builders
106
+ # ---------------------------------------------------------------------------
107
+
108
+ def build_methodologies(lang):
109
+ df = filter_type(get_df(lang), "methodology")
110
+ if df.empty:
111
+ return "No methodology entries found."
112
+ parts = []
113
+ for _, row in df.iterrows():
114
+ name = safe(row.get("methodology_name", ""))
115
+ org = safe(row.get("organization", ""))
116
+ phases = bullet_list(row.get("phases"))
117
+ scope = safe(row.get("scope", ""))
118
+ targets = safe(row.get("target_types", ""))
119
+ deliverables = bullet_list(row.get("deliverables"))
120
+ parts.append(
121
+ f"## {name}\n"
122
+ f"**Organization:** {org}\n\n"
123
+ f"**Scope:** {scope}\n\n"
124
+ f"**Target Types:** {targets}\n\n"
125
+ f"### Phases\n{phases}\n\n"
126
+ f"### Deliverables\n{deliverables}\n\n---\n"
127
+ )
128
+ return "\n".join(parts) if parts else "No methodology entries found."
129
+
130
+ def build_checklists(lang, target_type_filter="All"):
131
+ df = filter_type(get_df(lang), "checklist")
132
+ if df.empty:
133
+ return "No checklist entries found.", []
134
+ targets = ["All"] + unique_vals(df, "target_type")
135
+ if target_type_filter and target_type_filter != "All":
136
+ df = df[df["target_type"].astype(str) == target_type_filter]
137
+ parts = []
138
+ for _, row in df.iterrows():
139
+ cname = safe(row.get("checklist_name", ""))
140
+ ttype = safe(row.get("target_type", ""))
141
+ items = bullet_list(row.get("items"))
142
+ parts.append(
143
+ f"## {cname}\n**Target Type:** {ttype}\n\n### Items\n{items}\n\n---\n"
144
+ )
145
+ md = "\n".join(parts) if parts else "No checklists match the filter."
146
+ return md, targets
147
+
148
+ def build_techniques(lang, category_filter="All"):
149
+ df = filter_type(get_df(lang), "technique")
150
+ if df.empty:
151
+ return "No technique entries found.", []
152
+ cats = ["All"] + unique_vals(df, "category")
153
+ if category_filter and category_filter != "All":
154
+ df = df[df["category"].astype(str) == category_filter]
155
+ parts = []
156
+ for _, row in df.iterrows():
157
+ tname = safe(row.get("technique_name", ""))
158
+ cat = safe(row.get("category", ""))
159
+ impact = safe(row.get("impact", ""))
160
+ steps = bullet_list(row.get("exploitation_steps"))
161
+ payloads = bullet_list(row.get("payload_examples"))
162
+ remed = safe(row.get("remediation", ""))
163
+ bounty = safe(row.get("bounty_range_usd", ""))
164
+ cvss = safe(row.get("cvss_range", ""))
165
+ parts.append(
166
+ f"## {tname}\n"
167
+ f"**Category:** {cat} &nbsp;|&nbsp; **Impact:** {impact} &nbsp;|&nbsp; "
168
+ f"**CVSS:** {cvss} &nbsp;|&nbsp; **Bounty Range (USD):** {bounty}\n\n"
169
+ f"### Exploitation Steps\n{steps}\n\n"
170
+ f"### Payload Examples\n{payloads}\n\n"
171
+ f"### Remediation\n{remed}\n\n---\n"
172
+ )
173
+ md = "\n".join(parts) if parts else "No techniques match the filter."
174
+ return md, cats
175
+
176
+ def build_platforms(lang):
177
+ df = filter_type(get_df(lang), "platform")
178
+ if df.empty:
179
+ return "No platform entries found."
180
+ parts = []
181
+ for _, row in df.iterrows():
182
+ pname = safe(row.get("platform_name", ""))
183
+ payout = safe(row.get("payout_model", ""))
184
+ programs = bullet_list(row.get("top_programs"))
185
+ vtype = safe(row.get("vulnerability_type", ""))
186
+ bounty = safe(row.get("bounty_range_usd", ""))
187
+ parts.append(
188
+ f"## {pname}\n"
189
+ f"**Payout Model:** {payout}\n\n"
190
+ f"**Average Bounty Range:** {bounty}\n\n"
191
+ f"### Top Programs\n{programs}\n\n---\n"
192
+ )
193
+ return "\n".join(parts) if parts else "No platform entries found."
194
+
195
+ def build_reports(lang, vuln_filter="All"):
196
+ df = filter_type(get_df(lang), "report_template")
197
+ if df.empty:
198
+ return "No report template entries found.", []
199
+ vtypes = ["All"] + unique_vals(df, "vulnerability_type")
200
+ if vuln_filter and vuln_filter != "All":
201
+ df = df[df["vulnerability_type"].astype(str) == vuln_filter]
202
+ parts = []
203
+ for _, row in df.iterrows():
204
+ title = safe(row.get("title_template", ""))
205
+ vtype = safe(row.get("vulnerability_type", ""))
206
+ steps = bullet_list(row.get("steps_to_reproduce"))
207
+ impact = safe(row.get("impact", ""))
208
+ remed = safe(row.get("remediation", ""))
209
+ bounty = safe(row.get("bounty_range_usd", ""))
210
+ cvss = safe(row.get("cvss_range", ""))
211
+ parts.append(
212
+ f"## {title}\n"
213
+ f"**Vulnerability Type:** {vtype} &nbsp;|&nbsp; **CVSS:** {cvss} &nbsp;|&nbsp; "
214
+ f"**Bounty Range (USD):** {bounty}\n\n"
215
+ f"### Steps to Reproduce\n{steps}\n\n"
216
+ f"### Impact\n{impact}\n\n"
217
+ f"### Remediation\n{remed}\n\n---\n"
218
+ )
219
+ md = "\n".join(parts) if parts else "No report templates match the filter."
220
+ return md, vtypes
221
+
222
+ def build_tools(lang, category_filter="All"):
223
+ df = filter_type(get_df(lang), "tool")
224
+ if df.empty:
225
+ return "No tool entries found.", []
226
+ cats = ["All"] + unique_vals(df, "category")
227
+ if category_filter and category_filter != "All":
228
+ df = df[df["category"].astype(str) == category_filter]
229
+ parts = []
230
+ for _, row in df.iterrows():
231
+ name = safe(row.get("name", ""))
232
+ desc = safe(row.get("description", ""))
233
+ cat = safe(row.get("category", ""))
234
+ usage = bullet_list(row.get("usage_examples"))
235
+ parts.append(
236
+ f"## {name}\n**Category:** {cat}\n\n{desc}\n\n"
237
+ f"### Usage Examples\n{usage}\n\n---\n"
238
+ )
239
+ md = "\n".join(parts) if parts else "No tools match the filter."
240
+ return md, cats
241
+
242
+ def build_qa(lang):
243
+ df = filter_type(get_df(lang), "qa")
244
+ if df.empty:
245
+ return "No Q&A entries found."
246
+ parts = []
247
+ for _, row in df.iterrows():
248
+ q = safe(row.get("question", ""))
249
+ a = safe(row.get("answer", ""))
250
+ diff = safe(row.get("difficulty", ""))
251
+ badge = ""
252
+ if diff:
253
+ badge = f" `{diff}`"
254
+ parts.append(f"### ❓ {q}{badge}\n\n{a}\n\n---\n")
255
+ return "\n".join(parts) if parts else "No Q&A entries found."
256
+
257
+ # ---------------------------------------------------------------------------
258
+ # Statistics
259
+ # ---------------------------------------------------------------------------
260
+
261
+ def build_statistics(lang):
262
+ df = get_df(lang)
263
+ if df.empty:
264
+ return None, None, None, "No data available for statistics."
265
+
266
+ figs = []
267
+
268
+ # 1. Entry type distribution
269
+ if "type" in df.columns:
270
+ type_counts = df["type"].value_counts().reset_index()
271
+ type_counts.columns = ["Type", "Count"]
272
+ fig1 = px.pie(type_counts, names="Type", values="Count",
273
+ title="Entry Type Distribution",
274
+ color_discrete_sequence=px.colors.qualitative.Set2)
275
+ fig1.update_layout(template="plotly_white")
276
+ else:
277
+ fig1 = go.Figure()
278
+ figs.append(fig1)
279
+
280
+ # 2. Technique category distribution
281
+ tech_df = filter_type(df, "technique")
282
+ if not tech_df.empty and "category" in tech_df.columns:
283
+ cat_counts = tech_df["category"].value_counts().reset_index()
284
+ cat_counts.columns = ["Category", "Count"]
285
+ fig2 = px.bar(cat_counts, x="Category", y="Count",
286
+ title="Technique Distribution by Category",
287
+ color="Category",
288
+ color_discrete_sequence=px.colors.qualitative.Vivid)
289
+ fig2.update_layout(template="plotly_white", showlegend=False)
290
+ else:
291
+ fig2 = go.Figure()
292
+ figs.append(fig2)
293
+
294
+ # 3. Bounty range chart
295
+ bounty_rows = df.dropna(subset=["bounty_range_usd"]) if "bounty_range_usd" in df.columns else pd.DataFrame()
296
+ if not bounty_rows.empty:
297
+ labels = []
298
+ lows = []
299
+ highs = []
300
+ for _, row in bounty_rows.iterrows():
301
+ label = safe(row.get("technique_name") or row.get("title_template") or row.get("platform_name") or row.get("name") or "")
302
+ br = safe(row.get("bounty_range_usd", ""))
303
+ nums = re.findall(r"[\d,]+", br.replace(",", ""))
304
+ if len(nums) >= 2:
305
+ try:
306
+ lows.append(int(nums[0]))
307
+ highs.append(int(nums[1]))
308
+ labels.append(label[:40])
309
+ except ValueError:
310
+ pass
311
+ elif len(nums) == 1:
312
+ try:
313
+ lows.append(0)
314
+ highs.append(int(nums[0]))
315
+ labels.append(label[:40])
316
+ except ValueError:
317
+ pass
318
+ if labels:
319
+ bdf = pd.DataFrame({"Label": labels, "Min": lows, "Max": highs})
320
+ bdf = bdf.sort_values("Max", ascending=True).tail(20)
321
+ fig3 = go.Figure()
322
+ fig3.add_trace(go.Bar(y=bdf["Label"], x=bdf["Min"], orientation="h",
323
+ name="Min", marker_color="#66bb6a"))
324
+ fig3.add_trace(go.Bar(y=bdf["Label"], x=bdf["Max"] - bdf["Min"], orientation="h",
325
+ name="Max", marker_color="#ef5350", base=bdf["Min"]))
326
+ fig3.update_layout(title="Bounty Ranges (USD) – Top 20",
327
+ barmode="stack", template="plotly_white",
328
+ height=max(400, len(bdf)*28),
329
+ yaxis=dict(automargin=True))
330
+ else:
331
+ fig3 = go.Figure()
332
+ else:
333
+ fig3 = go.Figure()
334
+ figs.append(fig3)
335
+
336
+ # 4. Difficulty distribution for Q&A
337
+ qa_df = filter_type(df, "qa")
338
+ if not qa_df.empty and "difficulty" in qa_df.columns:
339
+ diff_counts = qa_df["difficulty"].value_counts().reset_index()
340
+ diff_counts.columns = ["Difficulty", "Count"]
341
+ fig4 = px.pie(diff_counts, names="Difficulty", values="Count",
342
+ title="Q&A Difficulty Distribution",
343
+ color_discrete_sequence=px.colors.qualitative.Pastel)
344
+ fig4.update_layout(template="plotly_white")
345
+ else:
346
+ fig4 = go.Figure()
347
+ figs.append(fig4)
348
+
349
+ total = len(df)
350
+ type_summary = ""
351
+ if "type" in df.columns:
352
+ for t, c in df["type"].value_counts().items():
353
+ type_summary += f"- **{t}**: {c} entries\n"
354
+ summary = f"### Dataset Summary ({lang})\n**Total entries:** {total}\n\n{type_summary}"
355
+
356
+ return figs[0], figs[1], figs[2], figs[3], summary
357
+
358
+ # ---------------------------------------------------------------------------
359
+ # Gradio UI
360
+ # ---------------------------------------------------------------------------
361
+
362
+ def create_app():
363
+ with gr.Blocks(
364
+ title="Bug Bounty & Pentesting Explorer",
365
+ css=CUSTOM_CSS,
366
+ theme=gr.themes.Soft(primary_hue="red", secondary_hue="purple"),
367
+ ) as app:
368
+ gr.Markdown(
369
+ "# πŸ›‘οΈ Bug Bounty & Pentesting Explorer\n"
370
+ "Browse **146 entries** covering methodologies, checklists, attack techniques, "
371
+ "platforms, report templates, tools, and Q&A β€” in **French** and **English**."
372
+ )
373
+ gr.HTML('<div class="disclaimer-box">⚠️ <strong>Disclaimer:</strong> All content is intended for <em>authorized security testing</em> and <em>educational purposes</em> only. Unauthorized access to computer systems is illegal.</div>')
374
+
375
+ lang = gr.Radio(["EN", "FR"], value="EN", label="🌐 Language / Langue", interactive=True)
376
+
377
+ # ---- Tabs ----
378
+ with gr.Tabs():
379
+ # 1. Methodologies
380
+ with gr.Tab("πŸ“‹ Methodologies"):
381
+ meth_output = gr.Markdown()
382
+ def refresh_meth(l):
383
+ return build_methodologies(l)
384
+ lang.change(refresh_meth, inputs=lang, outputs=meth_output)
385
+ app.load(refresh_meth, inputs=lang, outputs=meth_output)
386
+
387
+ # 2. Checklists
388
+ with gr.Tab("βœ… Checklists"):
389
+ cl_filter = gr.Dropdown(choices=["All", "Web", "API", "Mobile", "Cloud", "AD"],
390
+ value="All", label="Filter by Target Type", interactive=True)
391
+ cl_output = gr.Markdown()
392
+ def refresh_cl(l, f):
393
+ md, targets = build_checklists(l, f)
394
+ return md
395
+ cl_filter.change(refresh_cl, inputs=[lang, cl_filter], outputs=cl_output)
396
+ lang.change(refresh_cl, inputs=[lang, cl_filter], outputs=cl_output)
397
+ app.load(refresh_cl, inputs=[lang, cl_filter], outputs=cl_output)
398
+
399
+ # 3. Attack Techniques
400
+ with gr.Tab("βš”οΈ Attack Techniques"):
401
+ tech_filter = gr.Dropdown(choices=["All"], value="All",
402
+ label="Filter by Category", interactive=True)
403
+ tech_output = gr.Markdown()
404
+ def refresh_tech(l, f):
405
+ md, cats = build_techniques(l, f)
406
+ return md, gr.update(choices=cats, value=f if f in cats else "All")
407
+ def init_tech(l):
408
+ md, cats = build_techniques(l, "All")
409
+ return md, gr.update(choices=cats, value="All")
410
+ tech_filter.change(refresh_tech, inputs=[lang, tech_filter], outputs=[tech_output, tech_filter])
411
+ lang.change(init_tech, inputs=lang, outputs=[tech_output, tech_filter])
412
+ app.load(init_tech, inputs=lang, outputs=[tech_output, tech_filter])
413
+
414
+ # 4. Platforms
415
+ with gr.Tab("🏒 Platforms"):
416
+ plat_output = gr.Markdown()
417
+ def refresh_plat(l):
418
+ return build_platforms(l)
419
+ lang.change(refresh_plat, inputs=lang, outputs=plat_output)
420
+ app.load(refresh_plat, inputs=lang, outputs=plat_output)
421
+
422
+ # 5. Report Templates
423
+ with gr.Tab("πŸ“ Report Templates"):
424
+ rpt_filter = gr.Dropdown(choices=["All"], value="All",
425
+ label="Filter by Vulnerability Type", interactive=True)
426
+ rpt_output = gr.Markdown()
427
+ def refresh_rpt(l, f):
428
+ md, vtypes = build_reports(l, f)
429
+ return md, gr.update(choices=vtypes, value=f if f in vtypes else "All")
430
+ def init_rpt(l):
431
+ md, vtypes = build_reports(l, "All")
432
+ return md, gr.update(choices=vtypes, value="All")
433
+ rpt_filter.change(refresh_rpt, inputs=[lang, rpt_filter], outputs=[rpt_output, rpt_filter])
434
+ lang.change(init_rpt, inputs=lang, outputs=[rpt_output, rpt_filter])
435
+ app.load(init_rpt, inputs=lang, outputs=[rpt_output, rpt_filter])
436
+
437
+ # 6. Tools
438
+ with gr.Tab("πŸ”§ Tools"):
439
+ tool_filter = gr.Dropdown(choices=["All"], value="All",
440
+ label="Filter by Category", interactive=True)
441
+ tool_output = gr.Markdown()
442
+ def refresh_tools(l, f):
443
+ md, cats = build_tools(l, f)
444
+ return md, gr.update(choices=cats, value=f if f in cats else "All")
445
+ def init_tools(l):
446
+ md, cats = build_tools(l, "All")
447
+ return md, gr.update(choices=cats, value="All")
448
+ tool_filter.change(refresh_tools, inputs=[lang, tool_filter], outputs=[tool_output, tool_filter])
449
+ lang.change(init_tools, inputs=lang, outputs=[tool_output, tool_filter])
450
+ app.load(init_tools, inputs=lang, outputs=[tool_output, tool_filter])
451
+
452
+ # 7. Q&A
453
+ with gr.Tab("❓ Q&A"):
454
+ qa_output = gr.Markdown()
455
+ def refresh_qa(l):
456
+ return build_qa(l)
457
+ lang.change(refresh_qa, inputs=lang, outputs=qa_output)
458
+ app.load(refresh_qa, inputs=lang, outputs=qa_output)
459
+
460
+ # 8. Statistics
461
+ with gr.Tab("πŸ“Š Statistics"):
462
+ stats_summary = gr.Markdown()
463
+ stats_fig1 = gr.Plot(label="Entry Type Distribution")
464
+ stats_fig2 = gr.Plot(label="Technique Category Distribution")
465
+ stats_fig3 = gr.Plot(label="Bounty Ranges (USD)")
466
+ stats_fig4 = gr.Plot(label="Q&A Difficulty Distribution")
467
+ def refresh_stats(l):
468
+ f1, f2, f3, f4, summary = build_statistics(l)
469
+ return f1, f2, f3, f4, summary
470
+ lang.change(refresh_stats, inputs=lang,
471
+ outputs=[stats_fig1, stats_fig2, stats_fig3, stats_fig4, stats_summary])
472
+ app.load(refresh_stats, inputs=lang,
473
+ outputs=[stats_fig1, stats_fig2, stats_fig3, stats_fig4, stats_summary])
474
+
475
+ # Footer
476
+ gr.HTML(FOOTER_HTML)
477
+
478
+ return app
479
+
480
+ # ---------------------------------------------------------------------------
481
+ # Launch
482
+ # ---------------------------------------------------------------------------
483
+
484
+ if __name__ == "__main__":
485
+ app = create_app()
486
+ app.launch()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio==5.50.0
2
+ plotly
3
+ pandas
4
+ datasets