Credibility pass: the session seams sanded from the clips-and-camera batch
Two blind reviews of everything since the last pass (afa0e17), every
finding checked against the code, no behavior changed: all thirty
scene goldens match without a re-bless and the engine suite is
untouched.
Reel and renderer: the camera's look-down rule is stated once, beside
LOOK_DOWN, instead of twice in the effect; the empty aim branch that
stood where a cutaway used to be is gone (the guard it implied is now
explicit); the pit events and the punch are handled by their own
types, not through "in" casts; smoothstep is one export used by every
tween instead of eleven inline copies; the two floor rings share one
painter; project() takes a Billboard instead of a third hand-typed
copy of its fields; the strides-left figure and the web rim no longer
shadow the reel's steps and the pane's fx; the die card's verdict is
built from events, not by matching an emoji; the workshop asks for the
hover cue by name instead of passing an empty click handler.
Server and engine: one requestBase() for the origin, one slug pattern
in store.ts gating both the clip page and its files, one 404 for both;
LOOPBACK sits above its only caller; doCounteract names what a counter
is played against once; fearCells sits beside its own docblock rather
than between sightedCellsFor and its.
Deploy: chromiumExe, the private server, ffmpeg, and the reel rewind
live in deploy/lib/harness.mjs, shared by the gate, the recorder, and
the card cutter instead of pasted three times; the recorder drops its
duplicate frame counters and names its poster settle; the card uses the
gallery's exact gold; the one-time Sentry URL bootstrap leaves
deploy.sh; the backup comment states the rule rather than the incident.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
ddd2e347bd
commit
e1a740119c
@@ -17,6 +17,7 @@ const PAGE_CSS = `
|
||||
.mast a { font-family: "Oswald", sans-serif; text-transform: uppercase; letter-spacing: 0.18em;
|
||||
font-size: 0.95rem; color: #e9e1cb; text-decoration: none; }
|
||||
.mast .crumb { color: #8d8672; font-size: 0.85rem; letter-spacing: 0.06em; }
|
||||
.mast .crumb a { font: inherit; letter-spacing: inherit; text-transform: none; color: inherit; }
|
||||
.mast .deal { margin-left: auto; font-size: 0.8rem; letter-spacing: 0.12em; color: #e0b34a; }
|
||||
h1 { font-family: "Oswald", sans-serif; font-weight: 500; font-size: 1.7rem; letter-spacing: 0.06em;
|
||||
text-transform: uppercase; margin: 0 0 0.3rem; color: #e9e1cb; }
|
||||
@@ -137,7 +138,7 @@ export function clipPageHtml(clip: ClipMeta, base: string): string {
|
||||
`<meta name="twitter:card" content="summary_large_image"/>`,
|
||||
];
|
||||
const body = `
|
||||
<nav class="mast"><a href="/">Wiz-War</a><span class="crumb"><a href="/clips" style="font:inherit;letter-spacing:inherit;text-transform:none;color:inherit">clips</a> / ${esc(clip.title)}</span><a class="deal" href="/">deal yourself in →</a></nav>
|
||||
<nav class="mast"><a href="/">Wiz-War</a><span class="crumb"><a href="/clips">clips</a> / ${esc(clip.title)}</span><a class="deal" href="/">deal yourself in →</a></nav>
|
||||
<h1><a href="/clips">${esc(clip.title)}</a></h1>
|
||||
<p class="sub">${esc(clip.blurb)}</p>
|
||||
<button class="share" type="button" data-title="${esc(clip.title)}" data-text="${esc(clip.blurb)}">Share this clip</button>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
// {type:"error", message}
|
||||
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { createServer } from "node:http";
|
||||
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { readFileSync, existsSync, realpathSync, statSync, createReadStream } from "node:fs";
|
||||
import { extname, join, normalize, sep } from "node:path";
|
||||
@@ -65,7 +65,7 @@ import {
|
||||
abandonRoom,
|
||||
} from "./rooms";
|
||||
import { engagementStats, recordHotseat } from "./stats";
|
||||
import { appendFeedback, readFeedback, readClips, clipAssetPath } from "./store";
|
||||
import { appendFeedback, readFeedback, readClips, clipAssetPath, CLIP_SLUG } from "./store";
|
||||
import { clipsIndexHtml, clipPageHtml } from "./clips";
|
||||
import { SlidingLimit, clientAddress } from "./ratelimit";
|
||||
import { getShare, loadShares, mintShare } from "./shares";
|
||||
@@ -184,6 +184,15 @@ function ogPage(metas: string[]): string {
|
||||
|
||||
/** Host and proto arrive from request headers — attacker-writable text
|
||||
* that must never reach an HTML attribute raw. */
|
||||
const noSuchClip = (res: ServerResponse) =>
|
||||
res.writeHead(404, { "content-type": "text/plain" }).end("no such clip — see /clips");
|
||||
|
||||
/** The absolute origin a request came in on, as the proxy saw it. */
|
||||
function requestBase(req: IncomingMessage): string {
|
||||
const proto = String(req.headers["x-forwarded-proto"] ?? "http").split(",")[0]!.trim();
|
||||
return safeBase(String(req.headers.host ?? `localhost:${port}`), proto);
|
||||
}
|
||||
|
||||
function safeBase(rawHost: string, rawProto: string): string {
|
||||
const proto = /^https?$/.test(rawProto) ? rawProto : "https";
|
||||
return `${proto}://${escapeHtml(rawHost)}`;
|
||||
@@ -312,16 +321,15 @@ const httpServer = createServer((req, res) => {
|
||||
// with Range support — Safari refuses an mp4 whose server can't
|
||||
// serve bytes 0-1 on demand.
|
||||
if (url === "/clips" || url === "/clips/") {
|
||||
const proto = String(req.headers["x-forwarded-proto"] ?? "http").split(",")[0]!.trim();
|
||||
const base = safeBase(String(req.headers.host ?? `localhost:${port}`), proto);
|
||||
const base = requestBase(req);
|
||||
res.writeHead(200, { "content-type": "text/html", "cache-control": "no-cache" });
|
||||
res.end(clipsIndexHtml(readClips(), base));
|
||||
return;
|
||||
}
|
||||
const clipAsset = url.match(/^\/clips\/([a-z0-9-]{1,70}\.(?:mp4|jpg))$/);
|
||||
const clipAsset = url.match(/^\/clips\/([^/]+\.(?:mp4|jpg))$/);
|
||||
if (clipAsset) {
|
||||
const path = clipAssetPath(clipAsset[1]!);
|
||||
if (!path) { res.writeHead(404).end("no such clip"); return; }
|
||||
if (!path) { noSuchClip(res); return; }
|
||||
const size = statSync(path).size;
|
||||
const type = path.endsWith(".mp4") ? "video/mp4" : "image/jpeg";
|
||||
const range = /^bytes=(\d*)-(\d*)$/.exec(String(req.headers.range ?? ""));
|
||||
@@ -349,17 +357,16 @@ const httpServer = createServer((req, res) => {
|
||||
createReadStream(path).pipe(res);
|
||||
return;
|
||||
}
|
||||
const clipPage = url.match(/^\/clips\/([a-z0-9-]{1,60})$/);
|
||||
if (clipPage) {
|
||||
const clipPage = url.match(/^\/clips\/([^/]+)$/);
|
||||
if (clipPage && CLIP_SLUG.test(clipPage[1]!)) {
|
||||
const clip = readClips().find((c) => c.name === clipPage[1]);
|
||||
if (clip) {
|
||||
const proto = String(req.headers["x-forwarded-proto"] ?? "http").split(",")[0]!.trim();
|
||||
const base = safeBase(String(req.headers.host ?? `localhost:${port}`), proto);
|
||||
const base = requestBase(req);
|
||||
res.writeHead(200, { "content-type": "text/html", "cache-control": "no-cache" });
|
||||
res.end(clipPageHtml(clip, base));
|
||||
return;
|
||||
}
|
||||
res.writeHead(404, { "content-type": "text/html" }).end("no such clip — see /clips");
|
||||
noSuchClip(res);
|
||||
return;
|
||||
}
|
||||
// Room invitations: a living room gets its recruiting card; a dead
|
||||
|
||||
@@ -31,6 +31,8 @@ export class SlidingLimit {
|
||||
}
|
||||
}
|
||||
|
||||
const LOOPBACK = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
||||
|
||||
/** The client's address as Caddy reports it. The proxy APPENDS the true
|
||||
* peer to X-Forwarded-For, so the last entry is the trustworthy one; a
|
||||
* client can write anything into the first. And the header is believed
|
||||
@@ -43,5 +45,3 @@ export function clientAddress(headers: Record<string, string | string[] | undefi
|
||||
const parts = (Array.isArray(fwd) ? fwd.join(",") : fwd ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
||||
return parts[parts.length - 1] || peer;
|
||||
}
|
||||
|
||||
const LOOPBACK = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
||||
|
||||
@@ -235,24 +235,28 @@ export interface ClipMeta {
|
||||
|
||||
const clipsDir = () => join(DATA_DIR, "..", "clips");
|
||||
|
||||
/** A clip's name, which is also its file stem: no separators, no dots,
|
||||
* so a name can never name a path. */
|
||||
const SLUG = "[a-z0-9-]{1,60}";
|
||||
export const CLIP_SLUG = new RegExp(`^${SLUG}$`);
|
||||
const CLIP_FILE = new RegExp(`^${SLUG}(?:(?:-fpv|-board)\\.mp4|(?:-card)?\\.jpg)$`);
|
||||
|
||||
export function readClips(): ClipMeta[] {
|
||||
const file = join(clipsDir(), "clips.json");
|
||||
if (!existsSync(file)) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(file, "utf8")) as ClipMeta[];
|
||||
return parsed.filter((c) => /^[a-z0-9-]{1,60}$/.test(c.name));
|
||||
return parsed.filter((c) => CLIP_SLUG.test(c.name));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve a clip asset request to its path — or null for any name that
|
||||
* is not exactly a published clip file shape. The gate IS the security:
|
||||
* nothing outside <slug>(-fpv|-board).mp4 / <slug>(-card)?.jpg can be named. */
|
||||
* is not exactly a published clip file: <slug>-fpv.mp4, <slug>-board.mp4,
|
||||
* <slug>.jpg, <slug>-card.jpg. The pattern is the only path guard. */
|
||||
export function clipAssetPath(file: string): string | null {
|
||||
if (!/^[a-z0-9-]{1,60}(-fpv|-board)\.mp4$/.test(file) && !/^[a-z0-9-]{1,60}(-card)?\.jpg$/.test(file)) {
|
||||
return null;
|
||||
}
|
||||
if (!CLIP_FILE.test(file)) return null;
|
||||
const path = join(clipsDir(), file);
|
||||
return existsSync(path) ? path : null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user