Files
Diablo_Rain a1e3e6852f feat: Phase 1.5 — health prober, Oracle's Memory, off-thread EPG, PWA
Resilience + scale pass on top of the v1 core.

Stream health prober:
- New SSRF-validated /probe endpoint (HEAD -> ranged-GET fallback, 6s timeout,
  5-min TTL cache, concurrency-capped, no-store) + orb_probe_total metrics
- Front-end health.js: lazy bounded-concurrency probing (current + sampled
  channels, not the whole catalog), LIVE/DEGRADED/DEAD scoring, guide skull
  glyph, instant no-signal on zap to a known-dead channel

Oracle's Memory (memory.js):
- Session resurrection (last channel, volume, mute, theater/dim/ambient)
- Exponential-decay per-channel uptime history; pinning; dead-channel auto-skip

Off-thread XMLTV:
- lib/xmltv-parse.js (pure, hand-rolled scanner, no DOMParser/regex backtracking)
  run in epg-worker.js module worker; wired into the iptv-org provider
- Unit-tested in node: entity decode, TZ offsets, 20k programmes off-thread

Service worker / PWA:
- manifest.webmanifest + orb icons; sw.js (cache-first shell + hls.js,
  stale-while-revalidate for JSON-over-relay, never caches streams)
- CSP gains worker-src + manifest-src; relay sends no-store on /relay + /probe
  so the SW is the sole metadata freshness authority; webmanifest MIME fix

Docs: README de-emojified (title only) and reframed as a peer parallel build.

Verified: SSRF probes blocked, legit probe + cache, new headers present,
PWA assets served, parser tests 8/8, JS syntax clean, zero Rust warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 11:25:55 -04:00

10 KiB
Raw Permalink 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.

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):

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

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