Commit Graph

8 Commits

Author SHA1 Message Date
gitea 320a0ab24e PRE-27: pełne ukrycie niedostępnych funkcji (paranoja)
build-render / build (push) Failing after 8s
build-swisseph / build (push) Successful in 9s
build / build (push) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 5s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 4s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Failing after 4s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 6s
Testy / Kontrola składni wszystkich warstw (push) Failing after 4s
Ukrywanie jest teraz nadrzędne wobec wygody i czytelności komunikatów.
Persona: konto z uprawnieniami files + files_input, zatrudnione wyłącznie do
wgrywania plików. Nie ma się dowiedzieć, po co je wgrywa ani co program będzie
robił — bo to rozgada.

Audyt sześciu kanałów wycieku (statyki, HTML, sondowanie HTTP, ekran plików,
odpowiedzi JSON i błędy, pozostałe warstwy) potwierdził 26 wycieków, każdy
odtworzony uruchomionym kodem i zweryfikowany adwersarialnie. Ani jeden nie był
przyciskiem.

ZASÓB JEST CZĘŚCIĄ FUNKCJI
/static/ omijało CAŁĄ bramkę (PUBLIC_PREFIXES), więc każdy skrypt i arkusz
pobierał ktokolwiek, także niezalogowany, pod zgadywalnym adresem — a ich treść
wymienia ekrany, dostawców modeli i przeznaczenie plików. Ruch ten nie trafiał
przy tym ani do dziennika, ani pod limit żądań, więc wyciek był niewidoczny.
Zasoby idą teraz trasą z bramką; każdy ma w features.STATIC uprawnienie swojego
ekranu. Publiczny został jeden base.css, bo potrzebuje go ekran logowania.

KOMENTARZ NIE JEDZIE NA DRUT
Komentarze w CSS/JS opisywały funkcje pełnymi zdaniami po polsku — łącznie
z „Wstrzymane widzi tylko administrator", czyli i mechanizmem kwarantanny,
i istnieniem konta o wyższych uprawnieniach. _asset_body() usuwa je przy
serwowaniu; w repozytorium zostają.

styles.css rozbity na base.css + arkusz na ekran + x-ai.css. Jeden plik z
wszystkimi selektorami był spisem treści programu. Podział zrobiony
mechanicznie, z osobnym sprawdzeniem, że żaden ekran nie stracił reguły.

base.html ładował skrypty kosmogramu na KAŻDEJ stronie — konto mające wyłącznie
Pliki pobierało je przy wejściu na swój jedyny ekran, razem ze wzmianką
o „przyszłej zakładce". Teraz dokłada je ekran, który ich używa.

RÓŻNICA JEST INFORMACJĄ
Komunikat po wgraniu pliku różnił się zależnie od wyniku walidacji — czyli był
wyrocznią do odgadywania reguł, które ma znać tylko administrator — i mówił
wprost, że plik „musi zatwierdzić administrator". Teraz jest jeden, ten sam.

_logic_error wypisywał na ekran nazwę trasy, nazwę podsystemu, nazwę gałęzi
rozwojowej i wewnętrzny host:port. Jedno zdanie dla wszystkich awarii, szczegóły
do dziennika. Odsiew w jednym punkcie, nie w siedemnastu wywołaniach.

Ponadto: stopka nie ogłasza architektury, /health nie nazywa warstwy, konto bez
ekranów dostaje 404 zamiast tłumaczenia, ekran plików mówi o plikach zamiast
o „bazach interpretacyjnych", klasy .house-warning i .account-card przemianowane
na neutralne, a logic/data/render/engine-swisseph nie wystawiają już /docs ani
/openapi.json i nie publikują portów na hoście.

ZAPORA SŁOWNIKOWA
test_slownik_zakazany.py nie sprawdza miejsc, tylko przechodzi wszystko, co dane
konto może pobrać, i szuka słów, które nie mają prawa paść (87 pozycji dla tej
persony). Nazwy funkcji, adresy ekranów i nazwy zasobów biorą się wprost
z katalogu, więc nowa funkcja obejmuje się sama. Kontrola pozytywna pilnuje, żeby
test nie przechodził dlatego, że program jest pusty.

Sprawdzone: zapora puszczona na treść sprzed poprawek daje 16 trafień na samym
styles.css i łapie każdy ze zneutralizowanych komunikatów. 358 testów zielonych,
ekrany obejrzane w przeglądarce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 17:54:03 +02:00
gitea 6466ab89a9 feat(dane): interfejs zarządzania plikami baz — trzy poziomy dostępu (DAN-27)
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m12s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m33s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 19s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 12m25s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m33s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 16s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
Ekran „Pliki" z trzema poziomami, wpiętymi w kontrolę dostępu z PRE-27:

  „files"        widzi listę i KLIKANIEM decyduje, z których baz program korzysta,
  „files_input"  dokłada wgrywanie i ARCHIWIZACJĘ,
  administrator  kasowanie, przywracanie z archiwum i REGUŁY WALIDACJI.

STAN JEST TERAZ TRWAŁY. DAN-15 trzymał go w zmiennej DISABLED_BASES, bo warstwa
danych nie miała gdzie zapisywać — udział był montowany read-only. Skoro stan ma
być klikany, musi przetrwać restart, więc udział jest zapisywalny, a stan leży
w pliku obok baz (zapis atomowy: plik opisuje CAŁY zbiór, więc obcięcie w połowie
skasowałoby wiedzę o wszystkich naraz). DISABLED_BASES zostaje jako awaryjne
wyłączenie z konfiguracji i odsiewa DODATKOWO — nie odwrotnie, bo inaczej ktoś
z dostępem do ekranu włączyłby bazę wyłączoną świadomie na poziomie wdrożenia.

ARCHIWIZACJA NIE KASUJE. Plik zostaje na dysku, zamrożony, ze znacznikiem czasu;
znika wyłącznie z użytku. To najdalej idąca operacja osoby wgrywającej dane —
kasować może tylko administrator. Test sprawdza, że plik po archiwizacji nadal
istnieje, bo to jest cała istota tej operacji.

WALIDACJA JEST BRAMKĄ DO UŻYTKU, NIE FILTREM NA WEJŚCIU. Plik wgrany zostaje
NIEZALEŻNIE od wyniku — nie tracimy niczego, co ktoś wgrał. Zmienia się tylko to,
czy da się go włączyć. Sprawdzenie biegnie też w chwili włączania, nie tylko przy
wgrywaniu: reguły mogą się zmienić po fakcie.

O WALIDACJI WIE TYLKO ADMINISTRATOR. Pliki wstrzymane są odsiewane W WARSTWIE
DANYCH przy for_admin=False, a nie ukrywane w szablonie — gdyby dochodziły do
przeglądarki, wystarczyłby podgląd źródła, żeby poznać reguły. Odmowa włączenia
wraca do konta bez uprawnień BEZ POWODU, bo powód zdradza regułę. Sekcja reguł
nie trafia nawet do źródła strony. Test parametryzowany po obu niższych poziomach
szuka w odpowiedzi śladów mechanizmu i wymaga, żeby żadnego nie było.

Każdy plik ma policzony sha256 — tożsamość niezależna od nazwy. Wykorzystuje ją
już odrzucanie duplikatów, a w kroku drugim posłuży do pilnowania zgodności
lustra w SQL.

Przy okazji naprawiony błąd, który dopiero co bym wprowadził: Path("") to
Path("."), czyli wartość PRAWDZIWA, więc `Path(os.getenv(...)) or domyślna`
zawsze wybierało pustą zmienną i zapisywało stan do katalogu bieżącego.

Wymaga zapisywalnego udziału — osobny PR w repo deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:39:42 +02:00
gitea 4c1e7f8808 feat: przegląd baz na udziale + globalne włączanie/wyłączanie (DAN-15/PRE-09)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m30s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 12s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
build / build (push) Successful in 40s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m3s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m35s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m29s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 14s
Testy / Kontrola składni wszystkich warstw (push) Successful in 10s
Wymaganie przedefiniowane pod model serwerowy (#47): nie wybiera się folderu —
pliki leżą na stałym NFS. Potrzeba za to WIDZIEĆ, jakie bazy są dostępne i móc
zdecydować, które biorą udział w interpretacji.

Warstwa danych: `bases.py` (lista plików + metaopis: nazwa, ścieżka, rozmiar,
data, stan) i endpoint `/bases`. Wyłączone bazy są ODSIEWANE z kandydatów przy
wyszukiwaniu, więc naprawdę nie biorą udziału w interpretacji — nie tylko znikają
z listy. Lista wyłączonych wchodzi do klucza cache zapytań: bez tego zmiana
ustawień oddawałaby wynik sprzed zmiany, czyli treść bazy uznanej za wyłączoną.
`list_bases()` doszło do interfejsu dostawcy jako OPCJONALNE (SQL nie operuje na
plikach → pusto, zamiast wywrotki).

Przelot logika → prezentacja i ekran „Ustawienia" z tabelą baz. Przez łącze idą
SAME METADANE — podgląd listy nie jest kolejną drogą do wyniesienia treści.

Stan przełączników jest DEKLARATYWNY (`DISABLED_BASES`), nie klikalny — i to jest
świadome: udział z bazami montujemy read-only, a katalog cache to `emptyDir`, więc
zapisany przełącznik ginąłby przy restarcie poda i po cichu włączał z powrotem
wyłączoną bazę. Ekran mówi wprost, jak wyłączyć bazę i dlaczego nie klikaniem.
Tryb klikalny wymagałby dołożenia trwałego wolumenu.

Weryfikacja na żywym łańcuchu: `/bases` przechodzi przez SZYFROWANE łącze
(logic→data), pokazuje 3 bazy z metaopisem i stanem; wyszukiwanie daje 3 → 2 → 0
wierszy w miarę wyłączania baz. Testy: dane +6, prezentacja +6. Dane 13,
logika 277, prezentacja 249.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 22:09:27 +02:00
gitea 78af6d4755 feat(dane): mechanizm rekordów-pułapek (canary) — wykrywanie wycieku baz (DAN-26)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m29s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 11s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
build / build (push) Successful in 19s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m30s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 11s
Testy / Kontrola składni wszystkich warstw (push) Successful in 7s
Zabezpieczenie DETEKCYJNE (nie prewencyjne): kilka unikalnych, wiarygodnie
wyglądających rekordów-pułapek w bazach. Nie zmieniają interpretacji (odsiewamy
je z wyników), ale jeśli pojawią się w cudzej kopii — są dowodem pochodzenia, a
przy wariancie na kopię — wskazują ŹRÓDŁO wycieku.

`canary.py`: pułapkę rozpoznajemy po MARKERZE (unikalny ciąg z ENV, nieobecny w
realnych danych). `screen(rows, query_value)`:
- ODSIEWA rekordy z markerem z wyników — i to na WYJŚCIU z warstwy danych
  (`/search`), więc nie dotrą wyżej ani do promptu LLM (LOG-30), niezależnie od
  dostawcy (Excel/SQL);
- TRIPWIRE: gdy zapytanie celuje wprost w marker (enumeracja bazy, nie liczenie
  horoskopu) → log warning.
Bez `CANARY_MARKERS` — przezroczyste, zero kosztu dla normalnego ruchu.

Rejestr wariant→kopia (traitor tracing) i wstrzyknięcie do REALNYCH baz to krok
właściciela (poza kodem — nie ruszamy kupionych plików automatycznie);
instrukcja: docs/canary-registry.md. Mechanizm zbudowany i przetestowany na
syntetycznych pułapkach.

Testy: +7 (przezroczystość bez markerów, odsiewanie, tripwire, marker w dowolnym
polu, endpoint odsiewa przed zwrotem). Pierwsze testy w usłudze `data`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-03 21:15:53 +02:00
gitea 15964dd0df chore: wymuszenie nowego obrazu data/logic/presentation po incydencie z :latest
build / build (push) Successful in 1m16s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m11s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m40s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 30s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 20s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m38s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m38s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 26s
Testy / Kontrola składni wszystkich warstw (push) Successful in 21s
Komentarz-marker w main.py każdej z trzech usług — zmienia zawartość obrazu, więc
build.yaml wyprodukuje NOWE SHA-tagi z nowszym `created`. To odblokowuje
image-updatera (strategia newest-build), który utknął: wszystkie trzy usługi mają
w deployu tag `:latest`, którego build.yaml nigdy nie pcha (tylko SHA), więc nowe
pody wpadły w ImagePullBackOff. Nowy build z realnym SHA da updaterowi co promować
i przykryje `:latest` w kustomization — bez ręcznej zmiany w repo deploy.

UWAGA: samo to NIE wystarczy. W namespace astrololo zniknął pull-secret
`gitea-registry`, więc nawet z poprawnym tagiem nowe pody nie pobiorą obrazu.
Trzeba go odtworzyć (kopia działającego `gitea-registry-creds` z ns argocd) —
osobna, ręczna operacja, poza tym commitem.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 16:58:01 +02:00
gitea 56fdf01d7d feat(bezpieczenstwo): szyfrowanie lacz miedzy warstwami AES-256-GCM (PRE-16)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m45s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m31s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 31s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 15s
build / build (push) Successful in 4m15s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m41s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m36s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 33s
Testy / Kontrola składni wszystkich warstw (push) Successful in 15s
Warstwy rozmawialy ze soba jawnym tekstem wewnatrz klastra. Token
miedzywarstwowy (LOG-32) mowil KTO pyta, ale nie ukrywal CZEGO dotyczy
odpowiedz — a plyna nia surowe wiersze oryginalnych baz interpretacyjnych,
czyli rdzen produktu. Kto podsluchal ruch wewnatrz sieci (drugi pod, mirror
portu na switchu, zrzut z wezla), mial je w calosci.

Nowy modul link_crypto (kopia w kazdej z trzech uslug — nie maja wspolnej
biblioteki; test pilnuje, ze kopie sa identyczne):
- AES-256-GCM na ciele kazdego zadania i odpowiedzi. GCM daje poufnosc I
  uwierzytelnienie naraz, wiec nie ma wariantu „zaszyfrowane, ale podatne na
  modyfikacje".
- DWA niezalezne klucze, po jednym na pare rozmowcow (prezentacja-logika,
  logika-dane). Przejecie klucza prezentacji nie otwiera warstwy danych, gdzie
  leza cale bazy. Z kazdego klucza lacza HKDF wyprowadza osobne podklucze na
  kierunek, wiec zadanie i odpowiedz nigdy nie szyfruja sie tym samym kluczem.
- Do materialu uwierzytelnianego (AAD) wchodza kierunek, sciezka, znacznik
  czasu i numer ramki — wiec ramki nie da sie przekleic na inny endpoint,
  odtworzyc po czasie (okno MAX_SKEW) ani przestawic w strumieniu.
- Strona serwerowa to czyste ASGI: podmienia cialo zanim zobaczy je FastAPI
  i przepuszcza odpowiedz strumieniowa kawalek po kawalku (okno postepu dziala
  dalej). Fail-closed: przy ustawionym kluczu jawne zadanie dostaje odmowe.

Strumien postepu (okno pisania horoskopu) tez idzie przez szyfrowane lacze:
link_crypto.stream_lines() pieczetuje zadanie i odszyfrowuje odpowiedz ramka
po ramce (granice ramek != granice linii NDJSON), zachowujac dostarczanie na
zywo. Bez tego przy wlaczonym LINK_ENCRYPTION_REQUIRED serwer odrzucalby
strumien (400) i okno postepu przestaloby dzialac. Fail-closed obejmuje takze
strumien: klient bez klucza nie wysyla nic, zamiast puscic dane urodzenia
jawnym tekstem, zanim serwer zdazy odmowic.

Najgrozniejszy blad wyszedl z PODSLUCHU prawdziwego gniazda, nie z testow:
klient bez klucza wysylal pytanie jawnym tekstem, ZANIM serwer zdazyl odmowic.
Stad LINK_ENCRYPTION_REQUIRED: klient nie wysyla niczego, a usluga nie wstaje,
jesli klucza brak. Ta sama zasada co przy sekrecie logowania.

Klient prezentacji przepuszczony przez jeden punkt _post()/stream_lines: dopoki
kazda metoda skladala zadanie sama, dolozenie nowej znaczylo, ze latwo zapomniec
o tokenie albo kluczu (401 wyszedl juz raz dopiero na produkcji). Test
strukturalny: kazde wyjscie w dol musi miec i token, i klucz lacza (takze
strumien), a surowe httpx wolno tylko na sciezkach wyjetych spod szyfrowania.

Weryfikacja:
- testy link_crypto (round-trip, brak tresci baz w bajtach na sieci, odrzucenie
  obcego klucza / przestawionego bitu / przekleconej sciezki / przestawionej
  ramki / przeterminowanej koperty / urwanego strumienia; round-trip strumienia
  i fail-closed klienta i serwera dla strumienia),
- e2e na prawdziwym uvicornie z proxy zrzucajacym gniazdo: tresci baz brak na
  kablu w obie strony (grep=0), takze dla strumienia horoskopu; klucz jednej
  pary nie otwiera drugiej,
- calosc: logika 234 passed / 1 skipped, prezentacja 25 passed.

docs/wdrozenie-pre16.md: instrukcja krok po kroku z uzasadnieniem kolejnosci.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:06:11 +02:00
gitea 752f477a27 feat(security): zamkniecie dostepu do baz interpretacyjnych (LOG-32)
build / build (push) Successful in 1m16s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m9s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m51s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 26s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
Bazy sa rdzeniem produktu i wlasnie zostaly kupione — a aplikacja nie miala
ZADNEGO uwierzytelniania. Prezentacja to NodePort, wiec kazdy w LAN wchodzil
bez logowania, a `/search` oddawal surowe wiersze do 50 000 na zapytanie.
Bazy mogly wyjsc przez sama aplikacje, bez udzialu jakiegokolwiek LLM.

- prezentacja: HTTP Basic (APP_USER/APP_PASSWORD) + limit zadan na IP
  (RATE_LIMIT_PER_MIN, domyslnie 120/min). Limit dziala TAKZE przed
  uwierzytelnieniem, zeby zgadywanie hasla i sondowanie API nie bylo darmowe.
- logika i dane: token miedzywarstwowy X-Astrololo-Token (INTERNAL_TOKEN) —
  bez niego dalo sie ominac logowanie, uderzajac wprost w warstwe nizej.
  Warstwa danych oddaje surowe wiersze, wiec to najwrazliwszy punkt.
- /search: gorny limit 50 000 -> 5000 (tyle realnie uzywa build_report).
  Publiczne /api/query zostaje na 200.
- /health celowo publiczny (sondy k8s go nie uwierzytelnia).
- swiadomie nie logujemy tresci zadan ani promptow — logi to kolejny nosnik.

Fail-open przy braku konfiguracji (zgodnosc wstecz i dev), ale z GLOSNYM
ostrzezeniem przy starcie, zeby nikt nie wdrozyl tego w przekonaniu, ze jest
chroniony. Wlaczenie w produkcji wymaga ustawienia sekretow w repo deploy.

Testy: 12 (prezentacja, nowy katalog + job w CI) i 5 (logika). Zweryfikowane
na zywym stosie: bez hasla 401, z haslem 200, logika wprost bez tokenu 401,
z tokenem 200, /health 200, limit 50000 odrzucony (422), a prezentacja nadal
liczy horoskop przez logike.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:50:18 +00:00
gitea 16d35c16dc Szkielet aplikacji trójwarstwowej (prezentacja / logika / dane)
Trzy niezależne usługi FastAPI komunikujące się przez HTTP/JSON, każda
zna tylko adres warstwy bezpośrednio pod nią:

- presentation (:8000) — strona WWW + formularz
- logic (:8001) — reguły biznesowe, pośrednik
- data (:8002) — wyszukiwanie danych za interfejsem DataProvider

Warstwa danych: czytanie setek plików .xlsx z wykrywaniem nagłówka i
mapowaniem układu kolumn na schemat kanoniczny, z 4-poziomowym cache
(schemat L1, Parquet L2, wyniki zapytań L3, odwrócony indeks L4) i
unieważnianiem po odcisku pliku. Gotowa ścieżka migracji do SQL
(ingest/to_sql.py + SqlDataProvider, przełączane przez DATA_PROVIDER).

Zawiera docker-compose, Makefile, generator danych przykładowych.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:35:09 +02:00