Wymaganie było mocniejsze niż schowanie przycisku: po niedostępnej funkcji nie może zostać śladu w źródle strony. Największy wyciek nie był przyciskiem — _prompt_block wstrzykiwał w stronę CAŁY katalog modeli jako JSON (dostawcy, nazwy modeli, rozmiary okien kontekstu), na każdym ekranie z generowaniem, niezależnie od uprawnień konta. Druga dziura była głębsza: handlery nie sprawdzały nic. Trasa /interpret musi być dostępna dla konta z Interpretacjami, więc granica przebiega WEWNĄTRZ niej, po polu `action` — spreparowany formularz z action=prompt generował tekst, a action=export pobierał arkusz, mimo że szablon chował oba przyciski. Akcja bez uprawnienia wraca do akcji domyślnej ekranu zamiast dawać błąd: komunikat „brak uprawnień do generowania" sam w sobie mówiłby, że taka funkcja istnieje. Ślady wycięte także tam, gdzie nie były kontrolką: znaczniki natalNote/ reportNatal, pliki models.js/progress.js/natal.js/predictions.js oraz podtytuł ekranu Skompiluj, który wymieniał interpretację od AI z nazwy. 12 testów; 9 z nich pada na kodzie sprzed poprawki (sprawdzone przez cofnięcie zmian w app/). Kontrola pozytywna pilnuje, żeby nie przechodziły dlatego, że generowanie jest zepsute dla wszystkich. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.5 KiB
Konta i uprawnienia (PRE-27)
Rozszerzenie kont imiennych z PRE-17: 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.
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.