Some checks failed
Quality gates / quality (pull_request) Failing after 2m39s
Closes the PR 63 hostile-review blockers: 1. Immutable pinned Python/Pillow runtime (TIMMY_PYTHON absolute, --verify-pin), deployment/runtime re-encode smoke gate in build_release + deploy_staging. 2. Header-only width/height/total-pixel/bomb rejection before full decode; proves 6000x6000 and 12000x12000 stay resource bounded (RSS + address-space caps). 3. Fail-fast decoder concurrency ceiling; tests count actual spawned children. 4. Rate-limiter key cardinality hard-bounded under 20k+ unexpired identities, trusted-loopback-proxy identity, no spoofable forwarded headers, fixed-window boundary burst smoothed by two-window sliding count. 5. build_release explicitly syntax/gates every new JS module + Python re-encoder + production-runtime smoke; CI runs reencode-image test and the runtime pin smoke. 6. Robust polyglot contract via canonical container parsing; rejects uppercase/mixed script, appended HTML, ZIP local/EOCD and archive tails, data after canonical JPEG/PNG/WebP end; no naive compressed-byte scans (false-positive controls pass). 7. Canonical base64 data-URL grammar with byte-exact round trip; rejects missing/excess padding, whitespace/CRLF, malformed and noncanonical encodings; exact MIME policy. 8. Missing Pillow / runtime-unavailable maps to sanitized 503 + manual fallback. 9. Inbound body-read timeout and stop-on-oversize; preserves sanitized 413, base path, provider suppression, and temp cleanup; socket torn down on rejection. Audited prior partial edits: reused the sound source modules, re-wired new tests into the unit/syntax gates, fixed a non-canonical readJson that destroyed the socket before delivering 413/408, and hardened test flakiness (port reuse, EPIPE, startup races).
168 lines
6.7 KiB
JavaScript
168 lines
6.7 KiB
JavaScript
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(), /<div id="app"/);
|
|
assert.equal((await fetch(`${origin}/timmy-staging/app.js`)).status, 200);
|
|
assert.equal((await fetch(`${origin}/api/healthz`)).status, 404);
|
|
assert.equal((await fetch(`${origin}/git`)).status, 404);
|
|
});
|
|
|
|
test('prefixed document, manifest, and service worker stay inside the app scope', async t => {
|
|
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, /<base href="\/timmy-staging\/">/);
|
|
assert.doesNotMatch(html, /window\.__TIMMY_CONFIG__\s*=/);
|
|
assert.match(html, /<meta name="timmy-base-path" content="\/timmy-staging">/);
|
|
assert.match(html, /<meta name="timmy-staging-label" content="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: 'test-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: 'test-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'); }, 3000)),
|
|
]);
|
|
assert.notEqual(exitCode, 'timeout', `${JSON.stringify(value)} was accepted`);
|
|
assert.notEqual(exitCode, 0, `${JSON.stringify(value)} exited successfully`);
|
|
assert.match(stderr, /TIMMY_BASE_PATH/);
|
|
}
|
|
});
|