timmy-talking-turd/tests/image-dataurl.test.js
Timmy 517c8dbac3
Some checks failed
Quality gates / quality (pull_request) Failing after 2m39s
Harden image ingress: pinned runtime, header-bomb rejection, concurrency ceiling, bounded rate limiter, canonical data-URL/polyglot contract, 503-on-unavailable, body-read timeout
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).
2026-08-22 23:22:42 +00:00

96 lines
4.8 KiB
JavaScript

// Canonical base64 data-URL grammar contract.
//
// The client always produces a canonical `data:<mime>;base64,<canonical b64>`
// URL (canvas.toDataURL). Anything else is a hand-crafted request, so ingress
// must require the canonical grammar and a byte-exact round trip rather than
// silently repairing missing padding, embedded newlines, or non-zero trailing
// pad bits. The exact supported MIME policy is unchanged: JPEG/PNG/WebP only.
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { decodeImageDataUrl, validateImageIngress } from '../src/image-ingress.js';
const fixture = (name) => fileURLToPath(new URL(`../tests/fixtures/${name}`, import.meta.url));
const clean = await readFile(fixture('ingress-clean.jpg'));
const canonical = clean.toString('base64');
const ingest = (imageDataUrl) => validateImageIngress({ consent: true, imageDataUrl });
test('canonical base64 data URLs round-trip byte-exactly', () => {
const decoded = decodeImageDataUrl(`data:image/jpeg;base64,${canonical}`);
assert.ok(decoded, 'canonical data URL must decode');
assert.equal(decoded.mime, 'image/jpeg');
assert.ok(decoded.bytes.equals(clean), 'decoded bytes must equal the source bytes exactly');
});
test('missing base64 padding is rejected', async () => {
const unpadded = canonical.replace(/=+$/, '');
assert.notEqual(unpadded, canonical, 'fixture must actually require padding');
assert.equal(decodeImageDataUrl(`data:image/jpeg;base64,${unpadded}`), null);
await assert.rejects(() => ingest(`data:image/jpeg;base64,${unpadded}`), /not a supported image/i);
});
test('excess base64 padding is rejected', async () => {
assert.equal(decodeImageDataUrl(`data:image/jpeg;base64,${canonical}===`), null);
await assert.rejects(() => ingest(`data:image/jpeg;base64,${canonical}====`), /not a supported image/i);
});
test('CR and LF inside the base64 payload are rejected', async () => {
const withCrlf = `${canonical.slice(0, 40)}\r\n${canonical.slice(40)}`;
const withLf = `${canonical.slice(0, 40)}\n${canonical.slice(40)}`;
const withCr = `${canonical.slice(0, 40)}\r${canonical.slice(40)}`;
for (const payload of [withCrlf, withLf, withCr]) {
assert.equal(decodeImageDataUrl(`data:image/jpeg;base64,${payload}`), null);
await assert.rejects(() => ingest(`data:image/jpeg;base64,${payload}`), /not a supported image/i);
}
});
test('non-canonical encodings are rejected: non-zero trailing pad bits, whitespace, URL-safe alphabet', async () => {
const nonZeroPadBits = `${canonical.slice(0, -2)}/=`;
const withSpace = `${canonical.slice(0, 20)} ${canonical.slice(20)}`;
const urlSafe = canonical.replace(/\+/g, '-').replace(/\//g, '_');
for (const payload of [nonZeroPadBits, withSpace]) {
assert.equal(decodeImageDataUrl(`data:image/jpeg;base64,${payload}`), null);
await assert.rejects(() => ingest(`data:image/jpeg;base64,${payload}`), /not a supported image/i);
}
if (urlSafe !== canonical) {
assert.equal(decodeImageDataUrl(`data:image/jpeg;base64,${urlSafe}`), null);
}
});
test('the supported MIME policy is exact and unchanged', async () => {
for (const mime of ['image/jpeg', 'image/png', 'image/webp']) {
assert.ok(decodeImageDataUrl(`data:${mime};base64,${canonical}`), `${mime} must remain supported`);
}
for (const mime of ['image/gif', 'image/svg+xml', 'text/html', 'application/octet-stream', '']) {
assert.equal(decodeImageDataUrl(`data:${mime};base64,${canonical}`), null, `${mime} must not be supported`);
await assert.rejects(() => ingest(`data:${mime};base64,${canonical}`), /not a supported image/i);
}
});
test('malformed data-URL envelopes are rejected without repair', async () => {
const malformed = [
`data:image/jpeg,${canonical}`, // no base64 token
`data:image/jpeg;base64;${canonical}`, // wrong separator
`DATA:image/jpeg;base64,${canonical}`, // scheme casing is not canonical here
`data:image/jpeg;base64,`, // empty payload
` data:image/jpeg;base64,${canonical}`, // leading whitespace
`data:image/jpeg;base64,${canonical} `, // trailing whitespace
`data:image/jpeg;charset=utf-8;base64,${canonical}`, // extra parameters
canonical, // bare base64, no envelope
];
for (const value of malformed) {
assert.equal(decodeImageDataUrl(value), null, `must reject: ${value.slice(0, 42)}`);
await assert.rejects(() => ingest(value), /not a supported image/i);
}
});
test('a canonical supported data URL still completes ingress end to end', async () => {
const result = await ingest(`data:image/jpeg;base64,${canonical}`);
assert.equal(result.format, 'jpeg');
assert.equal(result.mime, 'image/jpeg');
assert.match(result.imageDataUrl, /^data:image\/jpeg;base64,[A-Za-z0-9+/]+={0,2}$/);
});