Server (relay/SSRF): - Bound connect+TLS+request under CONNECT_TIMEOUT and body download under BODY_TIMEOUT so a stalling upstream can't pin a relay permit indefinitely - Add inbound header_read_timeout to defeat slow-loris connections - Allowlist relayed Content-Type (fall back to octet-stream) to block content-confusion from attacker-controlled stream origins - Reuse one DNS resolver process-wide instead of rebuilding it per request (restores hickory's DNS cache; drops per-segment resolver setup) - Probe cache: random per-process hash seed (DefaultHasher was deterministic) + bounded growth with expired-entry eviction - Real Ctrl-C handler for a clean shutdown (honours the banner) - Collapse the wrap_raw no-op alias into wrap Frontend: - Guide: visible focus ring tracking arrow-key navigation - Player: buffering/tuning indicator while <video> is stalled - prefers-contrast: more pass (lifts --ink-dim to meet WCAG AA) - Service worker: date-stamped cache names so shell updates propagate Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🔮 The Pondering Orb
A retro CRT-themed web TV. Channel-surf live streams through a security-hardened, same-origin relay.
A reformed mad-wizard gazes into a scrying orb and flips channels like it's 1994 — but every upstream is treated as hostile.
What it is
The Pondering Orb turns any HLS source into a 1994 cable-TV experience in the browser: a red/black phosphor CRT frame, a scrolling TV guide, channel zapping with a 7-segment OSD, and YouTube-style theater / dim / ambient-glow modes.
It is built around one idea: a channel is "a named thing on a guide that resolves to an HLS stream at a point in time." Public live streams (via iptv-org) and — later — your own self-hosted media library are just different providers feeding the same UI. No rewrite required to add the second.
"Purple team" here is a security identity (red offense + blue defense), not a color. The palette stays red/black; the wizard theme is naming and lore on top of clear, accessible function.
It is developed as a parallel build to a peer project that takes the same premise — your media as 24/7 TV channels — through a different architecture (a NAS + ErsatzTV + Raspberry Pi feeding a physical CRT). This implementation is portable, browser-based, and security-first.
Features
- Authentic CRT presentation — phosphor glow, scanlines, screen curvature, vignette breathing (all CSS, no WebGL).
- Cable-box channel zapping — number entry with debounce + 7-segment OSD, channel up/down, last-channel toggle, auto-fading now-playing banner.
- "Scrying Static" transitions — procedural channel-change noise that clears the moment the new stream buffers (reduced-motion aware).
- The Almanac — a virtualized, 1994-style scrolling TV guide with now/next, a live current-time playhead, and keyboard navigation. Scales to tens of thousands of channels.
- Immersion stack — Theater, Dim, and Ambient-backlight modes (à la YouTube), composable and keyboard-driven.
- Graceful dead-stream handling — "the orb clouds over" no-signal card, automatic fallback URLs, and auto-skip.
- The Lexicon — optional in-world vocabulary (guide → The Almanac, search → Scry) with a one-click "mundane mode"; screen-reader labels always stay plain-English.
- Accessible by default — full keyboard map + help overlay, ARIA roles/live regions, visible focus rings, and a complete
prefers-reduced-motionkill-switch.
Security posture
Every stream URL comes from a public, third-party, mutable playlist — so the Orb treats all of it as hostile input. The browser only ever talks to its own origin; the Rust relay is the single egress point, and all SSRF risk is concentrated in one auditable module.
| Control | Implementation |
|---|---|
| SSRF defense | Resolves DNS itself, rejects the whole host if any resolved IP is private / loopback / link-local / CGNAT / cloud-metadata (169.254.169.254) / IPv6 ULA+link-local, then connects to that exact validated IP — no re-resolve, defeating DNS-rebinding. Fail-closed. |
| HTTPS-only egress | Non-https upstreams are refused outright. |
| Bounded redirects | Each redirect hop is re-validated through the same SSRF gate. |
| Strict CSP | default-src 'none'; no unsafe-inline / unsafe-eval; scripting, styling, and XHR all locked to 'self'. |
| Untrusted-input rendering | Playlist/EPG fields are inserted as text only (never innerHTML); URL schemes allow-listed; field/length caps guard against client-side DoS. |
| Hardened serving | Path-traversal-safe static handler, nosniff, no-referrer, X-Frame-Options: DENY, locked Permissions-Policy. |
| Supply chain | HLS.js pinned to an exact version and Subresource-Integrity verified. |
| Observability | /metrics (Prometheus) counts relay outcomes including SSRF blocks; structured JSON logs record host + outcome, never the raw URL (query strings can carry stream tokens). |
Verified
The current build passes its full probe battery: all SSRF vectors rejected (403/400), CSP headers present with no unsafe-*, path traversal blocked, a real Apple HLS manifest fetched and its nested URLs rewritten through the relay, and the live iptv-org catalog (~39k channels) retrieved end-to-end through the hardened path. Rust unit tests and JS syntax checks are green with zero build warnings.
Quick start
Full experience (serves the SPA and the relay from one origin):
cd server
cargo run --release
# → http://127.0.0.1:8088
Front-end only (no relay; uses the built-in mock realm and CORS-clean test streams):
cd web
python3 -m http.server 8099
# → http://127.0.0.1:8099
Keyboard
| Key | Action | Key | Action | |
|---|---|---|---|---|
0–9 |
Tune channel | T |
Theater mode | |
↑ / ↓ |
Volume · guide nav | D |
Dim mode | |
PgUp/PgDn |
Channel up / down | A |
Ambient glow | |
Backspace |
Last channel | F |
Fullscreen | |
G |
Toggle the Almanac | ? |
Help |
Append ?provider=mock or ?provider=iptv to force a data source.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Browser (same origin — no CORS, untainted ambient canvas) │
│ │
│ index.html ── app.js ──┬── player.js (HLS.js, controls) │
│ ├── zapper.js (tuning, OSD) │
│ ├── guide.js (virtualized EPG) │
│ ├── ambient.js (backlight) │
│ └── providers/ │
│ ├── source-provider.js ◄── the │
│ ├── mock.js interface
│ └── iptv-org.js │
└───────────────────────────────┬─────────────────────────────┘
│ / · /relay · /metrics
┌───────────────────────────────▼─────────────────────────────┐
│ Rust server (binds 127.0.0.1) │
│ main.rs static serving + security headers + observability│
│ relay.rs HLS proxy + manifest URL rewrite + redirects │
│ ssrf.rs resolve → validate IP → connect (sole egress) │
└───────────────────────────────┬─────────────────────────────┘
▼
Public HLS origins · iptv-org JSON API
Everything in the UI talks only to the SourceProvider contract — never directly to iptv-org or any one source. MockProvider was built first to keep the UI provably source-agnostic; a future LocalMediaProvider (your own library, packaged to HLS) drops in alongside it.
Tech stack
- Front end: vanilla JavaScript (ES modules) + HLS.js — no framework, no build step.
- Relay: Rust — hyper 1.x, tokio, rustls (ring), hickory-resolver.
Roadmap
Now (v1): hardened same-origin relay · iptv-org provider · zapping · virtualized guide · theater/dim/ambient · dead-stream handling · observability.
Next: stream health prober + scoring · session resurrection · off-thread XMLTV parsing · service-worker PWA.
Later: The Scryer's Lens (live stream trust/provenance overlay) · The Chronicle (channel-lineup-as-code with diff/audit) · Cursed Channels (adversarial test harness that proves the defenses) · The Wizard's Workbench (compose your own channels; export as a signed "Spellbook") · LocalMediaProvider for self-hosted media.
Project structure
web/ Static SPA
index.html CRT shell + markup
styles.css Red/black CRT theme (all effects, responsive, a11y)
app.js Bootstrap, global state, keyboard map, modes
player.js HLS.js wiring, ghost controls, dead-stream handling
zapper.js Channel tuning, OSD, scrying-static transition
guide.js Virtualized EPG overlay
ambient.js Ambient backlight canvas
lexicon.js In-world vocabulary + mundane-mode
providers/ SourceProvider contract + Mock + iptv-org
lib/ Pinned, SRI-verified HLS.js
server/ Rust same-origin server + hardened relay
src/{main,relay,ssrf}.rs
Disclaimer
This project does not host, store, or transmit any media. It indexes publicly available stream URLs aggregated by the iptv-org project. You are solely responsible for ensuring any stream you access is licensed and lawful in your jurisdiction.
Acknowledgments
- Developed as a parallel exploration alongside a peer's NAS-to-CRT "media as 24/7 channels" project.
- Channel data and the public stream index come from the iptv-org community project.
License
MIT — code only. Channel data and streams are the property of their respective owners.