Skip to content

Input Validation

Untrusted input arrives from the bot, from reporters' data, and from browser sessions. The API validates at three points: request-level bounds, route-level field checks, and database-level re-validation inside the RPCs.

Request-level protections (request_limits.py)

ControlValueThreat addressed
MAX_CONTENT_LENGTH1 MiBOversized bodies consuming worker time/memory; Flask aborts with 413 before the view runs, converted to the JSON error contract
MAX_REPORT_ID_LENGTH64 charsMegabyte primary-key probes; bounds indexed lookups
MAX_SEARCH_LENGTH100 charsFree-text search terms reaching PostgREST filters

Report ID validation

request_limits.validate_report_id:

  • required (non-empty),
  • ≤ 64 characters,
  • only [A-Za-z0-9_\-:.#] (the characters that occur in real IDs — short codes like DPS-CASE-01234 or UUID-style strings).

Anything else → 400 before any database interaction. The same bound applies on report creation; other path parameters (action_id, punishment_id, session_id, …) are type-checked by Flask converters or validated against canonical formats (AA0AA0 for punishment IDs).

Request size limits

  • Body cap: 1 MiB globally (MAX_CONTENT_LENGTH), checked both by Flask and by the before_request handler for a consistent JSON 413.
  • Training text fields: injection content ≤ 2000 chars, titles ≤ 120, note bodies ≤ 1000.
  • Admin search: ≤ 100 chars and stripped of PostgREST filter metacharacters (,()[]"), because the value is interpolated into an or=(...) filter expression.
  • Audit log: limit clamped to ≤ 500; report list limit ≤ 500; admin audit limit ≤ 200.

Search input handling

  • /audit: search is lowercased and passed to rpc_audit_log_v2 as a parameter (SQL-side ilike with %term%); event is split on commas into substring patterns; sort must be one of five known values or it defaults.
  • /admin/reports: search is sanitized against the PostgREST or= mini language (commas/parens stripped) and length-capped — an attacker cannot rewrite the filter expression.
  • Report list params (statuses, type, sorts, contact_open) are parsed into whitelisted values; unknown ones are dropped or defaulted, never passed through raw.

Reason/notes validation

  • Destructive or high-impact mutations require non-empty reasons: report deletion, admin agent updates, record corrections, punishment edits/revokes, admin queue actions, access changes. Missing reasons → 400 (REASON_REQUIRED re-checked in the DB).
  • notes/evidence/timeline bodies require non-empty text fields; note and evidence URLs are scheme-validated.
  • is_supervisor is normalized from form-encoded booleans (string → bool) on the creation path so it can never arrive as an arbitrary value.

URL validation

db_access._validate_http_url accepts only http:// / https:// (case- insensitive prefix match) and rejects every other scheme (javascript:, data:, file:, vbscript:) with a 400 for the offending field. Applied to:

  • evidence_url on report creation,
  • url on evidence add.

Older rows predate this validation, which is why the frontend re-validates at every sink (see Media / URL Safety).

Parameter validation

  • Enum membership everywhere: report statuses, punishment statuses, report actions, training event types, injection types, queue result values (success/failed), training grade results (pass/fail), admin queue status filters.
  • Numeric bounds: clearance 1–5, score 0–100, positive Roblox IDs, pagination limits/offsets, retry delays.
  • ISO timestamps: expires_at must parse (punishment_expires_at), otherwise 400 before PostgREST cast errors.
  • The fields objects on admin RPCs must be non-empty JSON objects; each key is checked against the whitelist inside the RPC (FIELD_NOT_ALLOWED).

Database-side re-validation

The RPCs repeat the critical checks so validation survives a route bug:

  • rpc_write_event validates event_type, category, actor_type, and metadata shape.
  • rpc_punishment_create/update validate required fields, status enum, expires_at cast, report_id existence, and the field whitelist.
  • rpc_admin_agent_update validates every field value (status set, rank set, clearance 1–5, trainer flag, non-empty strings) before writing.
  • rpc_report_action validates the allowed-status set and contact/claim invariants under the row lock.
  • Report IDs on route paths are additionally bounded by request_limits.validate_report_id where they enter as request bodies.