# ๐Ÿ”ฎ 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)
--- ## 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. 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-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 | | --- | --- | --- | --- | --- | | `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](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 - 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](https://github.com/iptv-org/iptv) community project. ## License [MIT](LICENSE) โ€” code only. Channel data and streams are the property of their respective owners.