320a0ab24e
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>
169 lines
8.6 KiB
Markdown
169 lines
8.6 KiB
Markdown
# Konta i uprawnienia (PRE-27)
|
|
|
|
Rozszerzenie kont imiennych z [PRE-17](konta-i-audyt.md): konta zakłada się
|
|
**z aplikacji**, a każde dostaje własny zestaw funkcji.
|
|
|
|
## Dwie zasady, z których wynika reszta
|
|
|
|
**1. Konto ograniczone widzi program KOMPLETNY — tylko mniejszy.**
|
|
Nic nie może zdradzać, że istnieje coś więcej. Żadnych wyszarzonych zakładek,
|
|
żadnego „brak uprawnień", żadnego 403 — bo **403 samo w sobie jest informacją**,
|
|
że pod tym adresem coś jest. Ścieżka bez uprawnienia odpowiada **404**, tak samo
|
|
jak adres, którego nie ma.
|
|
|
|
Z tej zasady wynikło też wyłączenie `/docs`, `/redoc` i `/openapi.json`.
|
|
Automatyczna dokumentacja FastAPI wypisuje komplet tras — czyli spis wszystkich
|
|
funkcji programu. Ochrona zakładek nic by nie dała, gdyby obok leżał ich katalog.
|
|
(Znalezione testem, nie przeglądem kodu.)
|
|
|
|
**2. Konto administracyjne pochodzi WYŁĄCZNIE ze środowiska.**
|
|
`APP_USER` / `APP_PASSWORD` (albo `APP_USERS`) — jak dotąd. To konto ma wszystkie
|
|
uprawnienia i jako jedyne zarządza pozostałymi. **Nie leży w pliku kont**, więc
|
|
nie da się go skasować ani ograniczyć z ekranu — nawet przez pomyłkę, nawet
|
|
spreparowanym żądaniem. Konto założone w pliku o tym samym loginie **nie
|
|
przesłoni** administracyjnego (kolejność sprawdzania jest odwrotna).
|
|
|
|
## Podział funkcji
|
|
|
|
**Ekrany** — zakładki widoczne w nawigacji:
|
|
|
|
| klucz | zakładka |
|
|
|---|---|
|
|
| `chart` | Horoskop |
|
|
| `interpret` | Interpretacje |
|
|
| `timeline` | Kalendarz |
|
|
| `synastry` | Synastria |
|
|
| `significators` | Sygnifikatory |
|
|
| `compile` | Skompiluj |
|
|
| `settings` | Ustawienia |
|
|
|
|
**Rozszerzenia** — poziomy złożoności wewnątrz ekranów:
|
|
|
|
| klucz | co daje |
|
|
|---|---|
|
|
| `houses_compare` | wybór systemu domów, zestawienie kilku obok siebie, obrót koła |
|
|
| `extra_charts` | aspektarian, wykres deklinacji, oś antyscji |
|
|
| `advanced_calc` | stacje planet, tabele żywiołów i faz, aspekty poboczne, zodiaki syderyczne |
|
|
| `ai` | horoskopy pisane przez model językowy (**każde użycie kosztuje**) |
|
|
| `export` | pobieranie PDF i Excela |
|
|
|
|
Konto bez `houses_compare` dostaje horoskop w Whole Sign i **nie widzi**, że
|
|
systemów jest trzynaście. Konto bez `ai` nie zobaczy przycisku generowania ani
|
|
nie wywoła go z pominięciem interfejsu.
|
|
|
|
## Gdzie leży granica
|
|
|
|
W handlerze, nie w szablonie. Ukrycie pola w formularzu chroni przed przypadkiem,
|
|
ale nie przed kimś, kto zna nazwy pól — dlatego `_limit_options()` ścina opcje
|
|
**po stronie serwera**, a rysunki dodatkowe bez uprawnienia w ogóle nie powstają
|
|
(nie ma ich nawet w źródle strony).
|
|
|
|
Mapa `trasa → uprawnienie` jest **jedna**, w `app/features.py`. Rozproszenie jej
|
|
po dekoratorach kończy się trasą, o której ochronie ktoś zapomniał — a taka dziura
|
|
jest niewidoczna do chwili, gdy ktoś ją znajdzie. Trasa bez wpisu w mapie wymaga
|
|
uprawnień administracyjnych: **przeoczenie ma zamykać, nie otwierać**. Test
|
|
przechodzi po wszystkich trasach aplikacji i wymaga, by każda była opisana.
|
|
|
|
### Gdy jedna trasa robi kilka rzeczy
|
|
|
|
Mapa tras nie wystarcza tam, gdzie jedna trasa obsługuje kilka funkcji naraz.
|
|
`POST /interpret` liczy horoskop, ale to samo pole `action` prosi o wygenerowanie
|
|
promptu, napisanie horoskopu przez model albo eksport arkusza. Konto, które ma
|
|
mieć Interpretacje bez generowania, musi dostać tę trasę — więc granica przebiega
|
|
wewnątrz niej, po akcjach: `_AKCJE_POD_UPRAWNIENIEM` przypisuje akcji uprawnienie,
|
|
a `_dozwolona_akcja()` sprowadza żądanie bez uprawnienia do akcji domyślnej ekranu.
|
|
|
|
Sprowadza — nie odrzuca. Komunikat „brak uprawnień do generowania" sam w sobie
|
|
mówiłby, że taka funkcja istnieje, czyli łamałby zasadę drugą po to, żeby
|
|
wyegzekwować pierwszą. Akcja bez uprawnienia ma wyglądać na literówkę w formularzu.
|
|
|
|
### Ślad to nie tylko przycisk
|
|
|
|
Wymaganie brzmi „nie może być śladu", i to jest mocniejsze niż schowanie kontrolki.
|
|
Największym wyciekiem po stronie generowania nie był przycisk, tylko **katalog
|
|
modeli** — nazwy dostawców, nazwy modeli i rozmiary okien kontekstu — wstrzykiwany
|
|
w stronę blokiem JSON na każdym ekranie z generowaniem, niezależnie od uprawnień.
|
|
Dlatego `_llm_catalog_for()` oddaje pusty katalog kontu bez uprawnienia, a szablony
|
|
trzymają pod bramką także znaczniki (`natalNote`, `reportNatal`), pliki skryptów
|
|
(`models.js`, `progress.js`, `natal.js`, `predictions.js`) i **zdania opisujące
|
|
funkcję** — podtytuł ekranu Skompiluj wymieniał interpretację od AI z nazwy.
|
|
|
|
Testu na to nie da się napisać przez „sprawdź, czy przycisku nie ma": trzeba
|
|
sprawdzić, że w źródle strony nie ma żadnego z tych śladów, i mieć kontrolę
|
|
pozytywną, że przy uprawnieniu wszystkie są. Inaczej test przechodzi także wtedy,
|
|
gdy generowanie jest zepsute dla wszystkich.
|
|
|
|
## Pełna paranoja: ukrywanie jest nadrzędne
|
|
|
|
Właściciel produktu postawił to wyżej niż wygodę i wyżej niż czytelność
|
|
komunikatów: *„nie chcę, żeby osoba wrzucająca bazy wiedziała, po co to robi
|
|
i jak będzie w przyszłości działał program, bo to rozgada"*. Persona nazywa się
|
|
**wgrywacz** — konto z uprawnieniami `files` + `files_input` i niczym więcej.
|
|
|
|
Nie ma dowiedzieć się: jakie inne funkcje istnieją, że teksty pisze model
|
|
językowy i u jakiego dostawcy, do czego służą wgrywane pliki, co jest planowane,
|
|
że istnieje walidacja plików, ani że istnieje konto, które widzi więcej.
|
|
|
|
### Wyciek prawie nigdy nie siedzi tam, gdzie się go szuka
|
|
|
|
Audyt sześciu kanałów potwierdził 26 wycieków. Ani jeden nie był przyciskiem.
|
|
|
|
| Kanał | Co wyciekało |
|
|
|---|---|
|
|
| `/static/**` poza bramką | komplet skryptów i arkuszy dla **niezalogowanego** |
|
|
| komentarze w CSS/JS | pełne zdania po polsku o funkcjach, o kwarantannie i o tym, że administrator widzi więcej |
|
|
| `styles.css` jako jeden plik | nazwy selektorów = spis funkcji programu |
|
|
| `base.html` | skrypty kosmogramu na **każdej** stronie, łącznie z „przyszłą zakładką" |
|
|
| komunikaty błędu | nazwa trasy, nazwa podsystemu, nazwa gałęzi rozwojowej, wewnętrzny `host:port` |
|
|
| komunikat po wgraniu | słowo „administrator" — i **dwie różne treści**, czyli wyrocznia do odgadywania reguł walidacji |
|
|
| `/openapi.json` warstw wewnętrznych | katalog wszystkich funkcji, bez logowania |
|
|
|
|
### Trzy zasady, które z tego wynikają
|
|
|
|
**Zasób jest częścią funkcji.** Skrypt i arkusz przechodzą przez tę samą bramkę
|
|
co ekran (`features.STATIC`). Nazwa pliku jest zgadywalna, więc plik publiczny
|
|
opowiada o funkcji równie dokładnie jak przycisk. Publiczny został jeden
|
|
`base.css` — bo potrzebuje go ekran logowania — i dlatego nie wolno w nim
|
|
umieścić niczego, co nazywa funkcję.
|
|
|
|
**Komentarz nie jedzie na drut.** `_asset_body()` usuwa komentarze przy
|
|
serwowaniu. Zostają w repozytorium, gdzie są potrzebne. Ta jedna zmiana zamyka
|
|
cztery z sześciu kanałów naraz.
|
|
|
|
**Różnica jest informacją.** Dwa różne komunikaty po wgraniu pliku były
|
|
wyrocznią: wystarczyło wgrywać spreparowane pliki i czytać odpowiedź. Teraz
|
|
komunikat jest jeden, niezależnie od wyniku. Z tego samego powodu odmowa to
|
|
404 identyczne z „nie ma takiej trasy", a akcja bez uprawnienia cofa się do
|
|
domyślnej zamiast tłumaczyć, czego brakuje.
|
|
|
|
### Zapora słownikowa
|
|
|
|
Łatanie punkt po punkcie przegrywa z następną zmianą. Dlatego
|
|
`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ść.
|
|
|
|
Dwie z trzech list biorą się wprost z katalogu funkcji — nazwa funkcji, adres
|
|
jej ekranu i nazwy jej zasobów — więc dopisanie funkcji automatycznie dopisuje
|
|
je do tego, czego konto bez niej nie może zobaczyć. Trzecia lista, słownictwo
|
|
dziedziny i mechanizmów, jest pisana ręcznie, bo katalog jej nie zna.
|
|
|
|
Test ma kontrolę pozytywną: dla administratora te same słowa **muszą** się
|
|
pojawiać. Bez niej przechodziłby także wtedy, gdyby program był pusty.
|
|
|
|
## Gdzie leżą konta
|
|
|
|
Plik JSON wskazany przez `ACCOUNTS_FILE` (domyślnie `/app/state/accounts.json`),
|
|
na NFS — **własny podkatalog prezentacji**, nie katalog z bazami: zamontowanie
|
|
tutaj całego udziału obeszłoby bokiem zamknięcie dostępu z DAN-25.
|
|
|
|
Hasła wyłącznie jako hash scrypt, tym samym mechanizmem co `APP_USERS` — jedna
|
|
implementacja, więc nie ma czego rozjechać. Zapis jest **atomowy** (plik
|
|
tymczasowy + `os.replace` w tym samym katalogu): przerwanie zapisu nie obetnie
|
|
pliku, czyli nie skasuje wszystkich kont naraz.
|
|
|
|
## Czego ten mechanizm NIE robi
|
|
|
|
Nie zastępuje ochrony baz na poziomie sieci ani NFS (DAN-25). Ktoś z dostępem do
|
|
udziału albo do warstwy danych nadal je odczyta — uprawnienia w aplikacji
|
|
ograniczają to, co widać **przez aplikację**, i tyle.
|