Skip to content

Media / URL Safety

The platform stores externally supplied URLs (evidence links, Discord profile avatars/banners) and renders them in agents' browsers. Because the data can originate from community members via the bot, every URL is a potential execution vector if it ever lands in a src, href, or navigation sink with a dangerous scheme.

The threat

A stored javascript: (or data:, vbscript:) URL placed into an anchor href or an image src executes in the viewing agent's browser session — a stored XSS against the dashboard. The defenses are layered:

  1. Server-side scheme validation on every write (new rows).
  2. Client-side re-validation at every sink (all rows, including legacy ones that predate server validation).
  3. No raw HTML embedding anywhere in the frontend.

Safe vs unsafe URLs

CategoryAllowedNotes
Safe HTTP URLshttps://…, http://…safeHttpUrl — navigation targets, media src
Safe image URLshttp(s) or base64 data:image/(png|jpe?g|gif|webp|bmp);base64,…safeImageUrl<img src> only (the Settings avatar upload uses base64 images)
Unsafe schemesjavascript:, vbscript:, file:, bare data: (non-image), custom schemesRejected by the API (_validate_http_url) and by the frontend helpers (return null)
Relative pathsRejected by the frontend helpersnew URL() requires an absolute URL; relative-path smuggling is neutralized
Anything unparseableRejectednull → UI renders a fallback

Frontend sanitization points

All sinks live in ecc-dps-dashboard/src and must use src/lib/urlSafety.ts:

  • Evidence rendering — docket detail evidence items, the Evidence workspace, and the fullscreen evidence lightbox (including the recent fix that sanitizes URLs in the lightbox itself).
  • Discord profile imagesDiscordProfileCard / DiscordProfileModal avatar and banner images (bot-supplied data).
  • Navigation — any window.open/anchor driven by server-provided values (safeHttpUrl).
  • Settings avatar — base64 image uploads (the explicit safeImageUrl image-data exception).

Rule for frontend work: any string from the API that will be placed into src, href, or a navigation API must be passed through safeHttpUrl or safeImageUrl first. If a new component renders media, follow the existing pattern: sanitize → null means fallback.

Embedding external content

The dashboard does not embed external content with raw HTML. Images load as normal <img src> elements after scheme validation; non-image evidence opens in an overlay/lightbox whose URL has passed safeHttpUrl. There is no dangerouslySetInnerHTML usage for server content, and React's escaping covers text interpolation.