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>
This commit is contained in:
2026-06-15 05:34:09 -04:00
commit c58fa9591b
22 changed files with 3851 additions and 0 deletions
+158
View File
@@ -0,0 +1,158 @@
<div align="center">
# 🔮 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](https://img.shields.io/badge/frontend-vanilla_JS_+_HLS.js-e10000?style=flat-square)](#tech-stack)
[![Relay](https://img.shields.io/badge/relay-Rust_(hyper_+_rustls)-e10000?style=flat-square)](#the-relay)
[![Security](https://img.shields.io/badge/posture-SSRF--hardened_·_strict_CSP-e10000?style=flat-square)](#security-posture)
[![License](https://img.shields.io/badge/license-MIT-e10000?style=flat-square)](#license)
</div>
---
## 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](https://github.com/iptv-org/iptv)) 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):
```sh
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):
```sh
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
- **Front end:** vanilla JavaScript (ES modules) + [HLS.js](https://github.com/video-dev/hls.js) — no framework, no build step.
- **Relay:** Rust — [hyper](https://hyper.rs) 1.x, [tokio](https://tokio.rs), [rustls](https://github.com/rustls/rustls) (ring), [hickory-resolver](https://github.com/hickory-dns/hickory-dns).
## 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](https://github.com/iptv-org/iptv) 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](https://github.com/iptv-org/iptv) community project.
## License
[MIT](LICENSE) — code only. Channel data and streams are the property of their respective owners.