validation.py
python
sha256:7d6dd8f4a89e2d1fef2d84f6e65feaff51385d382f466766b7f690a22ec18e32
fix: fall back to DB ancestry check when mpack-only fast-fo…
Sonnet 4.6
patch
7 days ago
| 1 | """Validated path-segment types for FastAPI route parameters. |
| 2 | |
| 3 | All URL path segments that come from user input are validated against an |
| 4 | allowlist regex and capped at a safe length. FastAPI enforces these at |
| 5 | decode time and returns 422 Unprocessable Entity on violation — before any |
| 6 | handler logic runs. |
| 7 | |
| 8 | Usage in route signatures:: |
| 9 | |
| 10 | from musehub.api.validation import SlugParam, BranchParam, FilePathParam |
| 11 | |
| 12 | async def my_route(owner: SlugParam, repo_slug: SlugParam, branch: BranchParam): |
| 13 | ... |
| 14 | """ |
| 15 | |
| 16 | from typing import Annotated |
| 17 | |
| 18 | from fastapi import Path |
| 19 | |
| 20 | # owner / slug / repo_slug — alphanumeric, underscore, hyphen, dot. |
| 21 | # Must start with alphanumeric. Max 100 characters. |
| 22 | # Blocks: slashes, null bytes, "..", shell metacharacters, Unicode overlong sequences. |
| 23 | _SLUG_RE = r"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,99}$" |
| 24 | |
| 25 | # branch — same charset as slug but allows "/" for branch namespaces (e.g. feat/foo). |
| 26 | # Pydantic uses Rust's regex engine which does not support lookaheads, so we |
| 27 | # block ".." structurally: each path component must start with [a-zA-Z0-9_], |
| 28 | # meaning no component can be "." or "..". |
| 29 | # Pattern: one or more components of the form [a-zA-Z0-9_][a-zA-Z0-9_.-]*, |
| 30 | # optionally separated by "/". Max 200 characters. |
| 31 | _BRANCH_RE = r"^[a-zA-Z0-9_][a-zA-Z0-9_.-]*(/[a-zA-Z0-9_][a-zA-Z0-9_.-]*)*$" |
| 32 | |
| 33 | # file path inside a repo (e.g. "src/main.py", "README.md"). |
| 34 | # Each path component is either: |
| 35 | # - a normal name: starts with [a-zA-Z0-9_] |
| 36 | # - a dotfile: starts with "." followed immediately by [a-zA-Z0-9_] |
| 37 | # This structurally blocks ".", "..", and traversal sequences without lookaheads. |
| 38 | # Max 1000 characters enforced by max_length on the Path() field info. |
| 39 | _SEGMENT = r"(?:\.[a-zA-Z0-9_]|[a-zA-Z0-9_])[a-zA-Z0-9_.-]*" |
| 40 | _FILE_PATH_RE = rf"^{_SEGMENT}(\/{_SEGMENT})*$" |
| 41 | |
| 42 | SlugParam = Annotated[ |
| 43 | str, |
| 44 | Path(pattern=_SLUG_RE, max_length=100, min_length=1), |
| 45 | ] |
| 46 | |
| 47 | BranchParam = Annotated[ |
| 48 | str, |
| 49 | Path(pattern=_BRANCH_RE, max_length=200, min_length=1), |
| 50 | ] |
| 51 | |
| 52 | FilePathParam = Annotated[ |
| 53 | str, |
| 54 | Path(pattern=_FILE_PATH_RE, max_length=1000, min_length=1), |
| 55 | ] |
File History
1 commit
sha256:7d6dd8f4a89e2d1fef2d84f6e65feaff51385d382f466766b7f690a22ec18e32
fix: fall back to DB ancestry check when mpack-only fast-fo…
Sonnet 4.6
patch
7 days ago