import test from 'node:test'; import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { chmod, mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; const root = fileURLToPath(new URL('..', import.meta.url)); const hermesFixture = fileURLToPath(new URL('./fixtures/fake-hermes.mjs', import.meta.url)); let nextPort = 43100; async function startServer(t, env = {}) { const port = nextPort++; const origin = `http://127.0.0.1:${port}`; const child = spawn(process.execPath, ['server.mjs'], { cwd: root, env: { ...process.env, PORT: String(port), ...env, ...(env.TIMMY_PUBLIC_ORIGIN === '__ORIGIN__' ? { TIMMY_PUBLIC_ORIGIN: origin } : {}) }, stdio: ['ignore', 'pipe', 'pipe'], }); let stderr = ''; let stdout = ''; child.stdout.on('data', chunk => { stdout += chunk; }); child.stderr.on('data', chunk => { stderr += chunk; }); t.after(() => child.kill('SIGTERM')); const deadline = Date.now() + 10_000; const basePath = (env.TIMMY_BASE_PATH || '').replace(/\/$/, ''); while (Date.now() < deadline) { if (child.exitCode !== null) throw new Error(`server exited ${child.exitCode}: ${stderr}`); try { const response = await fetch(`${origin}${basePath}/api/healthz`); if (response.status) return { origin, child, getStdout: () => stdout }; } catch {} await new Promise(resolve => setTimeout(resolve, 40)); } throw new Error(`server did not become ready: ${stderr}`); } test('staging host can bind loopback instead of every network interface', async t => { const { getStdout } = await startServer(t, { HOST: '127.0.0.1', TIMMY_VISION_ENABLED: '0' }); assert.match(getStdout(), /http:\/\/127\.0\.0\.1:/); assert.doesNotMatch(getStdout(), /http:\/\/0\.0\.0\.0:/); }); test('health endpoint exposes only bounded staging identity and feature flags', async t => { const { origin } = await startServer(t, { TIMMY_RELEASE_TAG: 'daily-2026-08-20.3', TIMMY_RELEASE_COMMIT: 'ca31e6d38bec649407f63880504554c59f2878ae', TIMMY_VISION_ENABLED: '0', TIMMY_AGENT_ENABLED: 'false', SECRET_TOKEN: 'must-not-leak', }); const response = await fetch(`${origin}/api/healthz`); assert.equal(response.status, 200); assert.deepEqual(await response.json(), { ok: true, release: 'daily-2026-08-20.3', commit: 'ca31e6d38bec649407f63880504554c59f2878ae', visionEnabled: false, agentEnabled: false, }); assert.deepEqual([...response.headers.keys()].filter(name => /token|cookie|session|path|environment|credential/i.test(name)), []); }); test('base path contains static files and APIs without capturing sibling routes', async t => { const { origin } = await startServer(t, { TIMMY_BASE_PATH: '/timmy-staging', TIMMY_VISION_ENABLED: '0' }); const health = await fetch(`${origin}/timmy-staging/api/healthz`); assert.equal(health.status, 200); const page = await fetch(`${origin}/timmy-staging/`); assert.equal(page.status, 200); assert.match(await page.text(), /
{ const { origin } = await startServer(t, { TIMMY_BASE_PATH: '/timmy-staging', TIMMY_RELEASE_TAG: 'daily-2026-08-20.3', TIMMY_RELEASE_COMMIT: 'ca31e6d38bec649407f63880504554c59f2878ae', TIMMY_STAGING_LABEL: 'true', TIMMY_VISION_ENABLED: '0', }); const redirect = await fetch(`${origin}/timmy-staging`, { redirect: 'manual' }); assert.equal(redirect.status, 308); assert.equal(redirect.headers.get('location'), '/timmy-staging/'); const html = await (await fetch(`${origin}/timmy-staging/`)).text(); assert.match(html, //); assert.match(html, /window\.__TIMMY_CONFIG__=/); assert.match(html, /"basePath":"\/timmy-staging"/); assert.match(html, /"stagingLabel":"Staging · daily-2026-08-20\.3 · ca31e6d38bec"/); const manifest = await (await fetch(`${origin}/timmy-staging/manifest.webmanifest`)).json(); assert.equal(manifest.start_url, '/timmy-staging/'); assert.equal(manifest.scope, '/timmy-staging/'); assert.ok(manifest.icons.every(icon => icon.src.startsWith('/timmy-staging/'))); assert.equal((await fetch(`${origin}/timmy-staging/service-worker.js`)).status, 200); }); test('agent cookie is constrained to the normalized base path', async t => { const workdir = await mkdtemp(join(tmpdir(), 'timmy-staging-cookie-')); await chmod(hermesFixture, 0o700); t.after(() => rm(workdir, { recursive: true, force: true })); const { origin } = await startServer(t, { TIMMY_BASE_PATH: '/timmy-staging/', TIMMY_AGENT_ENABLED: 'true', TIMMY_AGENT_ACCESS_TOKEN: 'integration-access-code-2026', TIMMY_PUBLIC_ORIGIN: '__ORIGIN__', TIMMY_AGENT_WORKDIR: workdir, TIMMY_HERMES_COMMAND: hermesFixture, TIMMY_VISION_ENABLED: '0', }); const response = await fetch(`${origin}/timmy-staging/api/agent/unlock`, { method: 'POST', headers: { 'content-type': 'application/json', origin, 'sec-fetch-site': 'same-origin' }, body: JSON.stringify({ accessCode: 'integration-access-code-2026' }), }); assert.equal(response.status, 200); assert.match(response.headers.get('set-cookie'), /; Path=\/timmy-staging\//); }); test('malformed and escaping base paths are rejected at startup', async () => { const invalid = [ 'timmy-staging', '/../git', '/timmy/./nested', '/%2e%2e/git', '/%252e%252e/git', '/timmy%2Fgit', '/timmy%5cgit', '/timmy%2dstaging', '/timmy\\git', '/timmy?debug=1', '/timmy#fragment', '/timmy%', '/timmy//nested', '/timmy-staging//', '/timmy-staging;Secure', '/timmy-staging"quoted', "/timmy-staging'quoted", '/timmy staging', '/timmy\nstaging', ]; for (const value of invalid) { const child = spawn(process.execPath, ['server.mjs'], { cwd: root, env: { ...process.env, PORT: '0', TIMMY_BASE_PATH: value }, stdio: ['ignore', 'ignore', 'pipe'], }); let stderr = ''; child.stderr.on('data', chunk => { stderr += chunk; }); const exitCode = await Promise.race([ new Promise(resolve => child.once('exit', resolve)), new Promise(resolve => setTimeout(() => { child.kill('SIGTERM'); resolve('timeout'); }, 800)), ]); assert.notEqual(exitCode, 'timeout', `${JSON.stringify(value)} was accepted`); assert.notEqual(exitCode, 0, `${JSON.stringify(value)} exited successfully`); assert.match(stderr, /TIMMY_BASE_PATH/); } });