Spaces:
Sleeping
Sleeping
| """ | |
| Challenge deduplication utilities. | |
| Before inserting a new challenge into any of the four tables, | |
| we check whether a sufficiently similar challenge already exists | |
| to prevent the pool from filling with near-duplicates. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import re | |
| from difflib import SequenceMatcher | |
| from typing import Dict, List, Optional, Set | |
| import httpx | |
| SUPABASE_URL = os.getenv("SUPABASE_URL", "") | |
| SUPABASE_ANON_KEY = os.getenv("SUPABASE_ANON_KEY", "") | |
| # --------------------------------------------------------------------------- | |
| # Text normalisation helpers | |
| # --------------------------------------------------------------------------- | |
| _AR_STOP_WORDS: Set[str] = { | |
| "في", "من", "على", "إلى", "عن", "مع", "هذا", "هذه", "التي", "الذي", | |
| "أن", "إن", "لا", "ما", "لا", "كل", "بها", "به", "فيها", "فيه", | |
| "أو", "ثم", "لكن", "بل", "حتى", "قد", "لقد", "كان", "يجب", "يتم", | |
| "يمكن", "حيث", "كما", "أيضاً", "ذلك", "تلك", "اللذين", "اللتين", | |
| } | |
| def _normalise(text: str) -> str: | |
| """Lower-case, strip punctuation, collapse whitespace.""" | |
| text = text.lower() | |
| text = re.sub(r"[^\w\s]", " ", text) | |
| text = re.sub(r"\s+", " ", text).strip() | |
| return text | |
| def _token_set(text: str) -> Set[str]: | |
| """Return de-duplicated token set, Arabic stop-words removed.""" | |
| tokens = _normalise(text).split() | |
| return {t for t in tokens if t not in _AR_STOP_WORDS and len(t) > 1} | |
| def _title_similarity(a: str, b: str) -> float: | |
| """ | |
| Title similarity = max of: | |
| - token overlap (Jaccard-ish) | |
| - SequenceMatcher ratio on normalised strings | |
| """ | |
| sa, sb = _normalise(a), _normalise(b) | |
| if not sa or not sb: | |
| return 0.0 | |
| # SequenceMatcher on normalised strings | |
| seq = SequenceMatcher(None, sa, sb).ratio() | |
| # Token overlap | |
| ta, tb = _token_set(a), _token_set(b) | |
| if not ta or not tb: | |
| return seq | |
| jaccard = len(ta & tb) / max(len(ta | tb), 1) | |
| return max(seq, jaccard) | |
| def _code_similarity(a: str, b: str) -> float: | |
| """ | |
| For code: use SequenceMatcher on stripped lines. | |
| Code tends to have boilerplate; line-level matching works well. | |
| """ | |
| def _strip(lines: str) -> List[str]: | |
| return [ln.strip() for ln in lines.splitlines() if ln.strip()] | |
| la, lb = _strip(a), _strip(b) | |
| if not la or not lb: | |
| return 0.0 | |
| return SequenceMatcher(None, la, lb).ratio() | |
| # --------------------------------------------------------------------------- | |
| # DB fetch helpers | |
| # --------------------------------------------------------------------------- | |
| _HEADERS = { | |
| "apikey": SUPABASE_ANON_KEY, | |
| "Authorization": f"Bearer {SUPABASE_ANON_KEY}", | |
| "Content-Type": "application/json", | |
| } | |
| def _fetch_existing_titles(table: str, role_filter: Optional[str] = None, | |
| limit: int = 200) -> List[str]: | |
| """Fetch recent titles from a challenge table.""" | |
| url = f"{SUPABASE_URL}/rest/v1/{table}" | |
| params: Dict[str, str] = { | |
| "select": "title", | |
| "order": "created_at.desc", | |
| "limit": str(limit), | |
| } | |
| if role_filter: | |
| params["team_role"] = f"eq.{role_filter}" | |
| try: | |
| r = httpx.get(url, headers=_HEADERS, params=params, timeout=10) | |
| r.raise_for_status() | |
| return [row.get("title", "") for row in r.json()] | |
| except Exception: | |
| return [] | |
| def _fetch_existing_codes(table: str, code_col: str, | |
| role_filter: Optional[str] = None, | |
| limit: int = 100) -> List[str]: | |
| """Fetch recent code/story/log_content from a challenge table.""" | |
| url = f"{SUPABASE_URL}/rest/v1/{table}" | |
| params: Dict[str, str] = { | |
| "select": f"title,{code_col}", | |
| "order": "created_at.desc", | |
| "limit": str(limit), | |
| } | |
| if role_filter: | |
| params["team_role"] = f"eq.{role_filter}" | |
| try: | |
| r = httpx.get(url, headers=_HEADERS, params=params, timeout=10) | |
| r.raise_for_status() | |
| return [row.get(code_col, "") for row in r.json() if row.get(code_col)] | |
| except Exception: | |
| return [] | |
| def fetch_existing_titles(table: str, role_filter: Optional[str] = None, | |
| limit: int = 200) -> List[str]: | |
| """Fetch recent titles from a challenge table (public wrapper).""" | |
| return _fetch_existing_titles(table, role_filter, limit) | |
| # --------------------------------------------------------------------------- | |
| # Public API — one function per generator | |
| # --------------------------------------------------------------------------- | |
| def is_duplicate_encryption(title: str, story: str, | |
| task_outline: str = "", | |
| role_filter: Optional[str] = None) -> bool: | |
| """Check if an encryption challenge is too similar to existing ones.""" | |
| existing_titles = _fetch_existing_titles("encryption_challenges", role_filter) | |
| for t in existing_titles: | |
| if _title_similarity(title, t) > 0.55: | |
| return True | |
| existing_stories = _fetch_existing_codes( | |
| "encryption_challenges", "story", role_filter) | |
| for s in existing_stories: | |
| if _title_similarity(story, s) > 0.55: | |
| return True | |
| if task_outline: | |
| existing_tasks = _fetch_existing_codes( | |
| "encryption_challenges", "task_outline", role_filter) | |
| for t in existing_tasks: | |
| if _title_similarity(task_outline, t) > 0.55: | |
| return True | |
| return False | |
| def is_duplicate_code_fixing(title: str, vulnerable_code: str, | |
| task_outline: str = "", | |
| role_filter: Optional[str] = None) -> bool: | |
| """Check if a code-fixing challenge is too similar to existing ones.""" | |
| existing_titles = _fetch_existing_titles( | |
| "code_fixing_challenges", role_filter, limit=300) | |
| for t in existing_titles: | |
| if _title_similarity(title, t) > 0.55: | |
| return True | |
| existing_codes = _fetch_existing_codes( | |
| "code_fixing_challenges", "vulnerable_code", role_filter, limit=100) | |
| for c in existing_codes: | |
| if _code_similarity(vulnerable_code, c) > 0.65: | |
| return True | |
| if task_outline: | |
| existing_tasks = _fetch_existing_codes( | |
| "code_fixing_challenges", "task_outline", role_filter, limit=200) | |
| for t in existing_tasks: | |
| if _title_similarity(task_outline, t) > 0.55: | |
| return True | |
| return False | |
| def is_duplicate_log_analysis(title: str, log_content: str, | |
| task_outline: str = "", | |
| role_filter: Optional[str] = None) -> bool: | |
| """Check if a log-analysis challenge is too similar to existing ones.""" | |
| existing_titles = _fetch_existing_titles( | |
| "log_analysis_challenges", role_filter, limit=300) | |
| for t in existing_titles: | |
| if _title_similarity(title, t) > 0.55: | |
| return True | |
| existing_logs = _fetch_existing_codes( | |
| "log_analysis_challenges", "log_content", role_filter, limit=100) | |
| for lc in existing_logs: | |
| if _code_similarity(log_content, lc) > 0.60: | |
| return True | |
| if task_outline: | |
| existing_tasks = _fetch_existing_codes( | |
| "log_analysis_challenges", "task_outline", role_filter, limit=200) | |
| for t in existing_tasks: | |
| if _title_similarity(task_outline, t) > 0.55: | |
| return True | |
| return False | |
| def is_duplicate_vuln_hunter(title: str, vulnerable_code: str, | |
| task_outline: str = "", | |
| role_filter: Optional[str] = None) -> bool: | |
| """Check if a vulnerability-hunter challenge is too similar to existing ones.""" | |
| existing_titles = _fetch_existing_titles( | |
| "vulnerability_hunter_challenges", role_filter, limit=300) | |
| for t in existing_titles: | |
| if _title_similarity(title, t) > 0.55: | |
| return True | |
| existing_codes = _fetch_existing_codes( | |
| "vulnerability_hunter_challenges", "vulnerable_code", role_filter, limit=100) | |
| for c in existing_codes: | |
| if _code_similarity(vulnerable_code, c) > 0.60: | |
| return True | |
| if task_outline: | |
| existing_tasks = _fetch_existing_codes( | |
| "vulnerability_hunter_challenges", "task_outline", role_filter, limit=200) | |
| for t in existing_tasks: | |
| if _title_similarity(task_outline, t) > 0.55: | |
| return True | |
| return False | |
| def is_duplicate_web_exploit(title: str, http_request: str, | |
| task_outline: str = "", | |
| role_filter: Optional[str] = None) -> bool: | |
| """Check if a web-exploitation challenge is too similar to existing ones.""" | |
| existing_titles = _fetch_existing_titles( | |
| "web_exploitation_challenges", role_filter, limit=300) | |
| for t in existing_titles: | |
| if _title_similarity(title, t) > 0.55: | |
| return True | |
| existing_requests = _fetch_existing_codes( | |
| "web_exploitation_challenges", "http_request", role_filter, limit=100) | |
| for r in existing_requests: | |
| if _code_similarity(http_request, r) > 0.60: | |
| return True | |
| if task_outline: | |
| existing_tasks = _fetch_existing_codes( | |
| "web_exploitation_challenges", "task_outline", role_filter, limit=200) | |
| for t in existing_tasks: | |
| if _title_similarity(task_outline, t) > 0.55: | |
| return True | |
| return False | |
| def is_duplicate_steganography(title: str, story: str, | |
| task_outline: str = "", | |
| role_filter: Optional[str] = None) -> bool: | |
| """Check if a steganography challenge is too similar to existing ones.""" | |
| existing_titles = _fetch_existing_titles( | |
| "steganography_challenges", role_filter, limit=300) | |
| for t in existing_titles: | |
| if _title_similarity(title, t) > 0.55: | |
| return True | |
| existing_stories = _fetch_existing_codes( | |
| "steganography_challenges", "story", role_filter, limit=100) | |
| for s in existing_stories: | |
| if _title_similarity(story, s) > 0.55: | |
| return True | |
| if task_outline: | |
| existing_tasks = _fetch_existing_codes( | |
| "steganography_challenges", "task_outline", role_filter, limit=200) | |
| for t in existing_tasks: | |
| if _title_similarity(task_outline, t) > 0.55: | |
| return True | |
| return False | |