""" MeshBay Hub — CSAM hash matching. Checks public content hashes against known CSAM (Child Sexual Abuse Material) hash databases before allowing content to be registered or served publicly. Production integration: - NCMEC (National Center for Missing & Exploited Children): PhotoDNA hash database Access requires formal application: https://www.missingkids.org/gethelpnow/cybertipline - IWF (Internet Watch Foundation): URL and hash list (UK-based) Access via IWF membership: https://www.iwf.org.uk/our-technology/our-products/hash-list/ This module provides: 1. A local CSAM hash database (SQLite file, populated from official sources) 2. A check function used before content registration 3. An admin endpoint to update the hash list IMPORTANT: Never log matched hashes or file contents. CSAM detection must be reported to NCMEC (US law) or relevant authority immediately. """ import hashlib import logging import os from pathlib import Path log = logging.getLogger(__name__) # Default path for the CSAM hash database (blake3 hex hashes, one per line) DEFAULT_CSAM_DB_PATH = Path("/var/lib/meshbay/hub/csam_hashes.txt") class CSAMChecker: """ Checks content hashes against a known CSAM hash database. Usage: checker = CSAMChecker() checker.load() if checker.is_known_csam(blake3_hex): # refuse to serve, report to authority pass """ def __init__(self, db_path: Path = DEFAULT_CSAM_DB_PATH): self._db_path = db_path self._hashes: set[str] = set() self._loaded = False def load(self, db_path: Path | None = None) -> int: """ Load CSAM hashes from the hash database file. Returns the number of hashes loaded. File format: one blake3 hex hash per line (64 chars), comments with #. """ path = db_path or self._db_path if not path.exists(): log.warning("CSAM hash database not found: %s. " "Contact NCMEC (US) or IWF (EU) for access.", path) self._loaded = True return 0 count = 0 with open(path) as f: for line in f: line = line.strip() if line and not line.startswith("#") and len(line) == 64: self._hashes.add(line.lower()) count += 1 self._loaded = True log.info("CSAM hash database loaded: %d hashes from %s", count, path) return count def is_known_csam(self, content_hash_hex: str) -> bool: """ Return True if the hash matches a known CSAM hash. NEVER logs the hash or any file information. """ if not self._loaded: self.load() return content_hash_hex.lower() in self._hashes @property def hash_count(self) -> int: return len(self._hashes) def add_hash(self, hash_hex: str) -> None: """Add a hash to the in-memory set (and optionally persist).""" self._hashes.add(hash_hex.lower()) def update_from_file(self, new_db_path: Path) -> int: """Hot-reload from a new hash database file.""" old_count = len(self._hashes) self._hashes.clear() count = self.load(new_db_path) log.info("CSAM database updated: %d → %d hashes", old_count, count) return count # Module-level singleton (initialised in hub lifespan) _checker = CSAMChecker() def get_csam_checker() -> CSAMChecker: return _checker def check_content_hash(blake3_hex: str) -> bool: """ Check a content hash against the CSAM database. Returns True if the content is KNOWN CSAM — block immediately. Callers MUST: 1. Refuse to serve the content 2. Log the event (without the hash) for legal audit purposes 3. Report to NCMEC CyberTipline if operating in the US: https://www.missingkids.org/gethelpnow/cybertipline """ return _checker.is_known_csam(blake3_hex) # ── Hub API integration ─────────────────────────────────────────────────────── from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from meshbay_hub.api.deps import require_admin from meshbay_hub.db.models import User csam_router = APIRouter(prefix="/v1/admin/csam", tags=["csam"]) @csam_router.get("/status") async def csam_status(current_user: User = Depends(require_admin)): """Return CSAM checker status (hash count, database path).""" return { "hash_count": _checker.hash_count, "db_path": str(_checker._db_path), "loaded": _checker._loaded, "note": "Contact NCMEC or IWF for hash database access.", } @csam_router.post("/check") async def check_hash( body: dict, current_user: User = Depends(require_admin), ): """ Check a single hash. Admin use only. Returns True/False WITHOUT logging the hash (legal requirement). """ hash_hex = body.get("hash", "") if len(hash_hex) != 64: raise HTTPException(status_code=422, detail="hash must be 64 hex chars") matched = check_content_hash(hash_hex) # Do NOT log whether a match was found — only log the check attempt log.info("CSAM check performed by admin %s", current_user.username) return {"matched": matched}