PRE-27: generowanie tekstu przez model znika bez uprawnienia
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m20s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 5s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 5s
build / build (push) Successful in 1m39s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4m45s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m32s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 7s
Testy / Kontrola składni wszystkich warstw (push) Successful in 6s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m20s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 5s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 5s
build / build (push) Successful in 1m39s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4m45s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m32s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 7s
Testy / Kontrola składni wszystkich warstw (push) Successful in 6s
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>
This commit was merged in pull request #77.
This commit is contained in:
@@ -129,6 +129,35 @@ def _build_utc(date: str, time: str, tz_offset: float) -> tuple[str, str]:
|
||||
return utc.isoformat(), label
|
||||
|
||||
|
||||
# Akcja formularza → uprawnienie, którego wymaga. Akcji spoza tej mapy nie
|
||||
# ograniczamy; akcja Z mapy, na którą konto nie ma prawa, jest traktowana jak
|
||||
# NIEZNANA — czyli wraca do zachowania domyślnego ekranu.
|
||||
_AKCJE_POD_UPRAWNIENIEM = {"prompt": "ai", "horoscope": "ai", "export": "export"}
|
||||
|
||||
|
||||
def _dozwolona_akcja(request: Request, action: str, domyslna: str) -> str:
|
||||
"""Akcja, której konto nie ma prawa użyć, zachowuje się jak literówka.
|
||||
|
||||
NIE zwracamy błędu ani 404: komunikat „brak uprawnień do generowania" sam
|
||||
w sobie mówiłby, że taka funkcja istnieje (PRE-27). Ukrycie przycisku chroni
|
||||
tylko przed przypadkiem — kto zna nazwę pola, wyśle ją wprost, i to TU musi
|
||||
się zatrzymać."""
|
||||
wymagane = _AKCJE_POD_UPRAWNIENIEM.get(action)
|
||||
if wymagane and wymagane not in _perms(request):
|
||||
return domyslna
|
||||
return action
|
||||
|
||||
|
||||
def _llm_catalog_for(request: Request) -> dict:
|
||||
"""Katalog modeli — pusty, gdy konto nie generuje tekstu przez model.
|
||||
|
||||
Katalog trafia do strony jako blok JSON, więc bez tego ograniczenia nazwy
|
||||
dostawców, modeli i rozmiary okien kontekstu byłyby w źródle KAŻDEJ strony
|
||||
Interpretacji i Kalendarza, także dla kont, które o modelach nie mają prawa
|
||||
wiedzieć."""
|
||||
return _llm_catalog() if "ai" in _perms(request) else {}
|
||||
|
||||
|
||||
def _llm_catalog() -> dict:
|
||||
"""Podpowiedzi modeli dla pola wyboru. Awaria logiki nie może wywrócić strony —
|
||||
pole modelu jest tekstowe, więc bez katalogu nadal da się wpisać model ręcznie."""
|
||||
@@ -175,7 +204,7 @@ def chart_form(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request, "chart.html",
|
||||
{"result": None, "form": default_form(), "location_label": DEFAULT_LOCATION_LABEL,
|
||||
"llm_catalog": _llm_catalog()},
|
||||
"llm_catalog": _llm_catalog_for(request)},
|
||||
)
|
||||
|
||||
|
||||
@@ -472,7 +501,7 @@ def interpret_form(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request, "interpret.html",
|
||||
{"result": None, "form": default_form(), "location_label": DEFAULT_LOCATION_LABEL,
|
||||
"llm_catalog": _llm_catalog()},
|
||||
"llm_catalog": _llm_catalog_for(request)},
|
||||
)
|
||||
|
||||
|
||||
@@ -494,8 +523,9 @@ def interpret_run(
|
||||
form = {"person": person, "date": date, "time": time, "tz_offset": tz_offset,
|
||||
"lat": lat, "lon": lon, "group": group, "prompt_budget": prompt_budget,
|
||||
"llm_provider": llm_provider, "llm_model": llm_model}
|
||||
action = _dozwolona_akcja(request, action, "report")
|
||||
ctx: dict = {"form": form, "result": None, "error": None, "moment": None,
|
||||
"llm_catalog": _llm_catalog()}
|
||||
"llm_catalog": _llm_catalog_for(request)}
|
||||
try:
|
||||
iso_utc, label = _build_utc(date, time, tz_offset)
|
||||
ctx["moment"] = label
|
||||
@@ -540,7 +570,7 @@ def timeline_form(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request, "timeline.html",
|
||||
{"result": None, "form": default_form(), "location_label": DEFAULT_LOCATION_LABEL,
|
||||
"llm_catalog": _llm_catalog()},
|
||||
"llm_catalog": _llm_catalog_for(request)},
|
||||
)
|
||||
|
||||
|
||||
@@ -563,8 +593,9 @@ def timeline_run(
|
||||
form = {"person": person, "date": date, "time": time, "tz_offset": tz_offset, "lat": lat, "lon": lon,
|
||||
"from_date": from_date, "to_date": to_date, "prompt_budget": prompt_budget,
|
||||
"llm_provider": llm_provider, "llm_model": llm_model}
|
||||
action = _dozwolona_akcja(request, action, "timeline")
|
||||
ctx: dict = {"form": form, "result": None, "error": None, "moment": None,
|
||||
"llm_catalog": _llm_catalog()}
|
||||
"llm_catalog": _llm_catalog_for(request)}
|
||||
try:
|
||||
iso_utc, label = _build_utc(date, time, tz_offset)
|
||||
ctx["moment"] = label
|
||||
|
||||
Reference in New Issue
Block a user