Appearance
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)
| Control | Value | Threat addressed |
|---|---|---|
MAX_CONTENT_LENGTH | 1 MiB | Oversized bodies consuming worker time/memory; Flask aborts with 413 before the view runs, converted to the JSON error contract |
MAX_REPORT_ID_LENGTH | 64 chars | Megabyte primary-key probes; bounds indexed lookups |
MAX_SEARCH_LENGTH | 100 chars | Free-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 likeDPS-CASE-01234or 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 thebefore_requesthandler 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 anor=(...)filter expression. - Audit log:
limitclamped to ≤ 500; report listlimit≤ 500; admin auditlimit≤ 200.
Search input handling
/audit:searchis lowercased and passed torpc_audit_log_v2as a parameter (SQL-sideilikewith%term%);eventis split on commas into substring patterns;sortmust be one of five known values or it defaults./admin/reports: search is sanitized against the PostgRESTor=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_REQUIREDre-checked in the DB). notes/evidence/timelinebodies require non-empty text fields; note and evidence URLs are scheme-validated.is_supervisoris 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_urlon report creation,urlon 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_atmust parse (punishment_expires_at), otherwise 400 before PostgREST cast errors. - The
fieldsobjects 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_eventvalidatesevent_type,category,actor_type, andmetadatashape.rpc_punishment_create/updatevalidate required fields, status enum,expires_atcast,report_idexistence, and the field whitelist.rpc_admin_agent_updatevalidates every field value (status set, rank set, clearance 1–5, trainer flag, non-empty strings) before writing.rpc_report_actionvalidates 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_idwhere they enter as request bodies.