mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 07:36:50 +03:00
Live: lock down the local server against same-machine token theft (#304)
Two defense-in-depth layers close the P1 in issue #304, where any browser tab on the machine could fetch /live.js, extract the embedded token, and drive every token-gated route. 1. Loopback-restricted CORS. The shared handler replaced its wildcard `Access-Control-Allow-Origin: *` with reflection gated on a strict isLoopbackOrigin() that URL-parses the Origin (so localhost.evil.com and 127.0.0.1.evil.com fail) and accepts only http/https on localhost, 127.0.0.1, or [::1]. Reflection always pairs with `Vary: Origin` so a cache never hands one origin's authorized response to another. Remote origins get no ACAO header; origin-less callers (script tags, curl, the agent's own fetches) are unaffected. 2. Token-gated /live.js. The handler now 401s unless `?token=` matches state.token, so the bundle (which embeds the token) is no longer served to unauthenticated local pages. The injected <script src> carries the token: live.mjs passes --token to live-inject.mjs, which threads it through every injection path (HTML/JSX tag, Nuxt plugin, SvelteKit root component) via a shared buildLiveScriptSrc(). The token stays optional in live-inject so static fixture tests keep their bare src. Tests: new live-server integration cases for the 401 gate, remote-origin denial, loopback reflection + Vary, and token-guarded routes under a loopback Origin; e2e session harness now injects with the token. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
da2982ab95
commit
3f9fccdfd0
@@ -117,10 +117,14 @@ export function stopLiveServer(tmp) {
|
||||
} catch { /* already gone */ }
|
||||
}
|
||||
|
||||
export function runInject(tmp, port) {
|
||||
export function runInject(tmp, port, token) {
|
||||
const out = execFileSync(
|
||||
process.execPath,
|
||||
[join(SCRIPTS_DIR, 'live-inject.mjs'), '--port', String(port)],
|
||||
[
|
||||
join(SCRIPTS_DIR, 'live-inject.mjs'),
|
||||
'--port', String(port),
|
||||
...(token ? ['--token', String(token)] : []),
|
||||
],
|
||||
{
|
||||
cwd: tmp,
|
||||
encoding: 'utf-8',
|
||||
@@ -296,7 +300,7 @@ export async function bootFixtureSession({
|
||||
const injectStartedAt = Date.now();
|
||||
trace('setup.inject.start', { fixture: name });
|
||||
log(`live-inject --port ${live.port}`);
|
||||
const injectResult = runInject(tmp, live.port);
|
||||
const injectResult = runInject(tmp, live.port, live.token);
|
||||
if (!injectResult.ok) throw new Error('live-inject failed: ' + JSON.stringify(injectResult));
|
||||
trace('setup.inject.end', { fixture: name, files: injectResult.files || injectResult.pageFiles || [] });
|
||||
log(`live-inject complete in ${formatDuration(Date.now() - injectStartedAt)}`);
|
||||
|
||||
Reference in New Issue
Block a user