Files
pondering-orb/README.md
T
Diablo_Rain c58fa9591b feat: initial release of The Pondering Orb
A retro CRT-themed web TV that channel-surfs live HLS streams through a
security-hardened, same-origin Rust relay.

Front end (vanilla JS + HLS.js, no framework/build step):
- SourceProvider abstraction + canonical Channel/Program model so public
  streams and future self-hosted media share one UI (MockProvider built first
  to keep the UI provably source-agnostic; IptvOrgProvider for live data)
- Red/black phosphor CRT theme, virtualized 1994-style guide (The Almanac),
  cable-box zapping with 7-segment OSD, scrying-static transitions
- Theater/Dim/Ambient immersion modes, dead-stream "orb clouds over" handling
- The Lexicon in-world naming with mundane-mode; full a11y + reduced-motion
- HLS.js pinned + Subresource-Integrity verified

Rust relay (hyper 1.x + tokio-rustls/ring + hickory-resolver):
- Serves the SPA and proxies HLS from one origin (no CORS, untainted canvas)
- SSRF defense: resolve -> reject private/loopback/link-local/metadata IPs ->
  connect to the validated IP (no re-resolve, defeats DNS-rebind), fail-closed
- HTTPS-only egress, re-validated redirects, manifest URL rewrite, size/
  concurrency caps, strict CSP + security headers, path-traversal guard
- /metrics observability + structured logs (host only, never raw URLs)

Verified: all SSRF probes rejected, CSP clean, real Apple HLS manifest
rewritten, ~39k iptv-org channels fetched end-to-end, tests green, zero warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 05:34:09 -04:00

10 KiB
Raw Blame History

🔮 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.

Stack Relay Security License


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.

This is a side project built side-by-side with a mentor's reference design (a NAS + ErsatzTV + Raspberry Pi feeding a physical CRT). Same premise — your media as 24/7 TV channels — different architecture: 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-motion kill-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
09 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

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

  • Inspired by a mentor's NAS → CRT "your movies as 24/7 channels" build.
  • Channel data and the public stream index come from the excellent iptv-org community project.

License

MIT — code only. Channel data and streams are the property of their respective owners.