Skip to content

Evidence

Evidence is stored as external media links with descriptive metadata — the platform does not host media files. Evidence rows hang off a report (evidence.report_id), and each report also carries the reporter's original evidence description (reports.evidence) plus an optional evidence_url captured at creation.

Evidence metadata

Authoritative table: public.evidence (migration 000 baseline).

ColumnPurpose
idDatabase-assigned identity column; the stable key used by the Evidence workspace to deep-link to a specific item
report_idOwning report (FK → reports, ON DELETE CASCADE)
descriptionHuman-readable description of the item
urlThe external media URL (Discord CDN, imgur, etc.)
submitted_byDisplay name of the submitter
created_atTimestamp

The public shape additionally includes submitted_by_id (the submitter's Discord ID, resolved server-side when the submitter is a known agent) and, on the global evidence list, a lightweight report summary (report id, reported party, reason, status, supervisor flag) resolved in one batched query.

Supported media

The system makes no distinction between media types: a URL is a URL. Anything reachable over http(s) can be stored — Discord CDN images/videos, imgur, and so on. The dashboard renders known image URLs inline and opens everything else in a safe overlay (see Media / URL Safety).

URL handling

  • Write path: every evidence URL is validated server-side by db_access._validate_http_url before storage — the scheme must be http or https; javascript:, data:, file: and any other scheme is rejected with HTTP 400. This applies to POST /reports/<id>/evidence (url field) and POST /reports/create (evidence_url field).
  • Read path: serializers return the stored URL as-is; the frontend re-checks the scheme at every sink (safeHttpUrl / safeImageUrl in src/lib/urlSafety.ts) because older rows predate server-side validation and profile images are bot-supplied.

Safe media rendering

The dashboard renders media through dedicated components that apply src/lib/urlSafety.ts:

  • safeHttpUrl — absolute http(s) only; used for navigation targets (window.open) and document/media src.
  • safeImageUrlhttp(s) or base64 data:image/(png|jpe?g|gif|webp|bmp) (used by the Settings avatar upload); everything else returns null and the UI renders a fallback instead of an unsafe URL.

Nothing is ever embedded via dangerouslySetInnerHTML; React escapes text interpolation, and these helpers are defense-in-depth so a single missed validation cannot become script execution.

Evidence access permissions

OperationGate
View evidence in a docketview_dashboard (1) + supervisor-report rank check
Add evidence to a caseadd_evidence (1) + assigned-agent rule + investigation in progress (browser sessions)
Global evidence workspace (GET /evidence)view_dashboard (1)
Bot-attach evidence at report creationAPI key (bot path)
Report correction of evidence fieldsadmin_reports (Director-only)

The assigned-agent rule means a clearance-1 agent can add evidence only to cases they are assigned to; supervisor-rank agents can add to any case they can see.

Storage architecture

  • Metadata lives in PostgreSQL (public.evidence), scoped to reports via a foreign key with ON DELETE CASCADE — deleting a report removes its evidence metadata (working-data retention model, migration 017).
  • Media files are never stored by the platform. The URL is the record. There is no object storage, no upload endpoint, and no content fetch/proxy — the browser loads media directly from the external host.
  • Discord profile images (discord_profiles.avatar/banner) are likewise external URLs captured by the bot at report creation.

Deletion / retention

Evidence rows cascade with their report (working data). Individual evidence rows cannot be deleted through the API — there is no evidence-delete endpoint; record correction is the Director-only admin path (/admin/reports/<id>/correct). The timeline and canonical events (report.evidence_added) survive report deletion as the audit record.

Security considerations (externally supplied URLs)

Evidence URLs are supplied by reporters via the bot and by agents in the dashboard. The threats are:

  1. Stored XSS via URL scheme. A javascript: URL stored in the database and placed in href/src would execute in an agent's browser. Mitigations: server-side scheme validation on every write, client-side re-validation at every sink, and no raw HTML embedding.
  2. Tracking / leak of viewer IPs. The dashboard loads external URLs directly from the browser; the media host observes viewer requests. This is accepted by design (no proxy) and is worth remembering when handling sensitive cases.
  3. Unsafe host content. The platform does not scan media content; agents must treat evidence links as untrusted content from community members.