Appearance
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).
| Column | Purpose |
|---|---|
id | Database-assigned identity column; the stable key used by the Evidence workspace to deep-link to a specific item |
report_id | Owning report (FK → reports, ON DELETE CASCADE) |
description | Human-readable description of the item |
url | The external media URL (Discord CDN, imgur, etc.) |
submitted_by | Display name of the submitter |
created_at | Timestamp |
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_urlbefore storage — the scheme must behttporhttps;javascript:,data:,file:and any other scheme is rejected with HTTP 400. This applies toPOST /reports/<id>/evidence(urlfield) andPOST /reports/create(evidence_urlfield). - Read path: serializers return the stored URL as-is; the frontend re-checks the scheme at every sink (
safeHttpUrl/safeImageUrlinsrc/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— absolutehttp(s)only; used for navigation targets (window.open) and document/mediasrc.safeImageUrl—http(s)or base64data:image/(png|jpe?g|gif|webp|bmp)(used by the Settings avatar upload); everything else returnsnulland 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
| Operation | Gate |
|---|---|
| View evidence in a docket | view_dashboard (1) + supervisor-report rank check |
| Add evidence to a case | add_evidence (1) + assigned-agent rule + investigation in progress (browser sessions) |
Global evidence workspace (GET /evidence) | view_dashboard (1) |
| Bot-attach evidence at report creation | API key (bot path) |
| Report correction of evidence fields | admin_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 withON 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:
- Stored XSS via URL scheme. A
javascript:URL stored in the database and placed inhref/srcwould 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. - 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.
- Unsafe host content. The platform does not scan media content; agents must treat evidence links as untrusted content from community members.