Skip to content

Input Validation

Rigorous input validation is a core pillar of the OsuRender API security model.

Replay Validation (.osr)

When a user uploads a replay file, it undergoes the following checks:

  1. Extension: Must end in .osr
  2. Size: Must not exceed MAX_REPLAY_SIZE_MB (default 50 MB)
  3. Empty Check: File size must be > 0
  4. Structural Integrity: The file is parsed using the osrparse library. If parsing fails, the file is rejected.
  5. Game Mode: Only osu!standard replays (mode == 0) are supported. Taiko, Catch, and Mania replays are rejected.

Skin Validation (.osk)

Skin uploads are particularly dangerous because they are ZIP archives that will be extracted on the worker nodes. They undergo extreme scrutiny:

  1. Extension: Must end in .osk
  2. Size: Must not exceed MAX_SKIN_SIZE_MB (default 200 MB)
  3. Magic Bytes: Must start with PK (the standard ZIP header)
  4. ZIP Structure: The archive must pass Python's zipfile.testzip()
  5. Entry Count: Maximum of 10,000 files/directories inside the archive
  6. Compression Ratio: To prevent zip bombs, the ratio of uncompressed size to compressed size must not exceed 100x.
  7. Nesting Depth: Directory depth cannot exceed 3 levels.
  8. Nested Archives: No .zip or .osk files are allowed inside the skin archive.

Extraction Safety

Even after validation, the worker node extracts the skin carefully:

  • It uses zipfile.ZipFile.extractall() but overrides it with a custom secure extractor.
  • Path traversal (../) is stripped.
  • Absolute paths (/etc/passwd) are stripped.
  • The total extracted size is tracked byte-by-byte and aborted if it exceeds a hard limit.

API Parameter Validation

All API endpoints use Pydantic for request validation:

  • Skin Name: Must match the regex ^[a-zA-Z0-9_ -]+$
  • Resolution: Must be exactly "1080p" or "4k"
  • Background Dim: Must be a float. If > 1.0, it is automatically normalized (e.g., 95 becomes 0.95). If < 0.0 or > 100.0, it is rejected.
  • Booleans: Fields like motion_blur must be valid booleans.

Built with VitePress