"""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." )