harden: relay timeouts, DNS resolver reuse, cache hygiene + frontend polish

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>
This commit is contained in:
2026-06-15 14:20:06 -04:00
parent a1e3e6852f
commit 0b761f684b
7 changed files with 218 additions and 72 deletions
+9 -3
View File
@@ -112,7 +112,7 @@ export class Guide {
_renderRow(ch, idx) {
const row = document.createElement('div');
row.className = 'guide-row';
row.className = 'guide-row' + (idx === this.focusRow ? ' focused' : '');
row.style.position = 'absolute';
row.style.top = (idx * ROW_H) + 'px';
row.style.left = '0'; row.style.right = '0';
@@ -189,12 +189,18 @@ export class Guide {
_onKey(e) {
const list = this.agg.channels;
switch (e.key) {
case 'ArrowDown': e.preventDefault(); this.focusRow = Math.min(list.length-1, this.focusRow+1); this._scrollToFocus(); break;
case 'ArrowUp': e.preventDefault(); this.focusRow = Math.max(0, this.focusRow-1); this._scrollToFocus(); break;
case 'ArrowDown': e.preventDefault(); this.focusRow = Math.min(list.length-1, this.focusRow+1); this._moveFocus(); break;
case 'ArrowUp': e.preventDefault(); this.focusRow = Math.max(0, this.focusRow-1); this._moveFocus(); break;
case 'Enter': e.preventDefault(); { const ch = list[this.focusRow]; if (ch){ this.onTune(ch); this.hide(); } } break;
case 'Escape': e.preventDefault(); this.hide(); break;
}
}
// Scroll the focused row into view, then repaint so the .focused highlight
// tracks the cursor even when no scroll was needed (scroll event wouldn't fire).
_moveFocus() {
this._scrollToFocus();
this._renderViewport();
}
_scrollToFocus() {
const y = this.focusRow * ROW_H;
if (y < this.scroll.scrollTop) this.scroll.scrollTop = y;