Appearance
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:
- Server-side scheme validation on every write (new rows).
- Client-side re-validation at every sink (all rows, including legacy ones that predate server validation).
- No raw HTML embedding anywhere in the frontend.
Safe vs unsafe URLs
| Category | Allowed | Notes |
|---|---|---|
| Safe HTTP URLs | https://…, http://… | safeHttpUrl — navigation targets, media src |
| Safe image URLs | http(s) or base64 data:image/(png|jpe?g|gif|webp|bmp);base64,… | safeImageUrl — <img src> only (the Settings avatar upload uses base64 images) |
| Unsafe schemes | javascript:, vbscript:, file:, bare data: (non-image), custom schemes | Rejected by the API (_validate_http_url) and by the frontend helpers (return null) |
| Relative paths | Rejected by the frontend helpers | new URL() requires an absolute URL; relative-path smuggling is neutralized |
| Anything unparseable | Rejected | null → 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 images —
DiscordProfileCard/DiscordProfileModalavatar and banner images (bot-supplied data). - Navigation — any
window.open/anchor driven by server-provided values (safeHttpUrl). - Settings avatar — base64 image uploads (the explicit
safeImageUrlimage-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.