patristic-be / src /api /validation.py
Mario33333's picture
deploy: feature/audiobook@f1c0d35 (fix batch β€” models/providers, TTS $0, grants, DB admins, delete-hardening)
833ba13 verified
Raw
History Blame Contribute Delete
4 kB
"""Request-value validation for filesystem-/storage-bound identifiers.
Several endpoints take an id (``book_id``, ``upload_id``) that flows into a
filesystem path or an object-storage key. We constrain those to a tight
character class at the API boundary so a crafted value (``../``, absolute
paths, separators, NULs) can't traverse out of its directory or rewrite a
storage key. Ids in this system are slugs or uuid hex, so the class is
deliberately narrow β€” legitimate ids never contain anything else.
"""
from __future__ import annotations
import re
from src.api.errors import BadRequest
_SAFE_ID_RE = re.compile(r"^[A-Za-z0-9_\-]+$")
_MAX_ID_LEN = 200
def is_safe_id(value: str) -> bool:
return bool(value) and len(value) <= _MAX_ID_LEN and _SAFE_ID_RE.match(value) is not None
def require_safe_id(value: str, *, field: str = "id") -> str:
"""Return ``value`` if it's a safe id, else raise 400 ``BAD_REQUEST``."""
if not is_safe_id(value):
raise BadRequest(
f"Invalid {field}: must be 1–{_MAX_ID_LEN} characters of letters, "
f"digits, '_' or '-'."
)
return value
# Sentinels meaning "use the configured default model" β€” never validated.
_MODEL_DEFAULT_SENTINELS = {"", "default"}
# A model override counts as "premium" (admin-only) when its token pricing is
# well above the cheap working tier. With the current registry this isolates
# claude-opus-4-8 (5/25 per Mtok) from gemini-3.1-pro-preview (1.25/10) and the
# flash models (<=2.5 out) β€” so admins keep full choice while a query-capable
# NON-admin can't pin the priciest model and drain the shared daily spend cap in
# a handful of queries (the spend caps still bound the absolute worst case; this
# just stops a single non-admin from burning the whole budget fast).
_PREMIUM_INPUT_PER_M_USD = 5.0
_PREMIUM_OUTPUT_PER_M_USD = 20.0
def is_premium_model(model: str | None) -> bool:
"""True if ``model``'s registry pricing is in the premium (admin-only) tier."""
if not model or model in _MODEL_DEFAULT_SENTINELS:
return False
from src.lib.tools.repo import get_pricing
pricing = get_pricing(model)
if pricing is None:
return False
return (
(pricing.input_per_million_usd or 0) >= _PREMIUM_INPUT_PER_M_USD
or (pricing.output_per_million_usd or 0) >= _PREMIUM_OUTPUT_PER_M_USD
)
def assert_known_model(model: str | None, *, field: str, allow_premium: bool = True) -> None:
"""Reject a model name that isn't in the tools registry's ``llm`` category,
and (when ``allow_premium`` is False) reject premium-priced models too.
Model names flow straight to a paid provider, so an unconstrained value lets
a caller pin work (a query's ~8 parallel judges, or every eval item) to the
most expensive model and burn the daily cap faster. Both ``/query`` model
overrides and ``/eval/runs`` model selection go through here so the
allow-list is defined once. A ``None``/``"default"`` sentinel is the config
default and is always allowed; an empty/unreadable registry does not
hard-block (the spend caps still bound cost). ``allow_premium=False``
additionally blocks the premium tier so a non-admin caller can't pin
claude-opus-class models (see :func:`is_premium_model`).
"""
if not model or model in _MODEL_DEFAULT_SENTINELS:
return
from src.lib.tools.repo import by_category
allowed = {t.name for t in by_category("llm")}
if allowed and model not in allowed:
raise BadRequest(
f"Unknown model {model!r} for {field!r}. Choose a model from "
f"GET /tools?category=llm."
)
if not allow_premium and is_premium_model(model):
raise BadRequest(
f"Model {model!r} for {field!r} is admin-only (premium pricing). "
f"Choose a standard model from GET /tools?category=llm."
)