"""Rekordy-pułapki (canary) — wykrywanie wycieku baz (DAN-26). Zabezpieczenie DETEKCYJNE, nie prewencyjne. Kilka unikalnych, wiarygodnie wyglądających rekordów wplecionych w bazy: nie zmieniają interpretacji (są ODSIEWANE z wyników, więc nie trafiają ani do użytkownika, ani do promptu LLM — wymóg LOG-30), ale jeśli kiedyś pojawią się w cudzej kopii, są dowodem pochodzenia. Pułapkę rozpoznajemy po MARKERZE: unikalny ciąg, który nie występuje w realnych danych (wpleciony np. w pole znaczące). Markery i WARIANT tego wdrożenia biorą się z konfiguracji (ENV `CANARY_MARKERS`, `CANARY_VARIANT`) — każda kopia może dostać swój zestaw. Który wariant trafił do którego wdrożenia trzyma rejestr po stronie ops (osobno, poza kodem — patrz docs/canary-registry.md). Dwa sygnały: * ODSIEWANIE — pułapka w wynikach znika, zanim opuści warstwę danych (log info); * TRIPWIRE — zapytanie CELUJE wprost w marker (ktoś enumeruje bazę, a nie liczy realny horoskop) → log warning, bo to podejrzane zachowanie. """ from __future__ import annotations import logging import os log = logging.getLogger("astrololo.data.canary") def _load_markers() -> list[str]: return [m.strip() for m in os.getenv("CANARY_MARKERS", "").split(",") if m.strip()] MARKERS = _load_markers() VARIANT = os.getenv("CANARY_VARIANT", "") def _row_is_canary(row: dict, markers: list[str]) -> bool: """Czy KTÓRAKOLWIEK tekstowa wartość wiersza zawiera marker pułapki.""" for v in row.values(): if isinstance(v, str): for m in markers: if m in v: return True return False def screen( rows: list[dict], query_value: str = "", markers: list[str] | None = None, variant: str | None = None, ) -> tuple[list[dict], dict]: """Zwraca (widoczne_wiersze, raport). Odsiewa pułapki z wyników (nie opuszczą warstwy danych). Gdy zapytanie celuje wprost w marker — podnosi TRIPWIRE (możliwa enumeracja bazy). Bez skonfigurowanych markerów: przezroczyste (`active=False`), zero kosztu dla normalnego ruchu. """ markers = MARKERS if markers is None else markers variant = VARIANT if variant is None else variant if not markers: return rows, {"active": False, "removed": 0, "tripwire": False} visible = [r for r in rows if not _row_is_canary(r, markers)] removed = len(rows) - len(visible) tripwire = any(m in (query_value or "") for m in markers) if tripwire: log.warning( "CANARY TRIPWIRE: zapytanie celuje wprost w rekord-pułapkę (wariant %s) " "— możliwa enumeracja bazy", variant or "?") elif removed: log.info("Odsiano %d rekord(ów)-pułapek z wyników (wariant %s)", removed, variant or "?") return visible, {"active": True, "removed": removed, "tripwire": tripwire}