timmy-talking-turd/tests/image-polyglot.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

87 lines
4.1 KiB
JavaScript

// Container/polyglot rejection contract.
//
// The requirement is malformed/polyglot REJECTION, not "the re-encoder happens
// to drop the tail". Rejection must come from canonical parsing of the image
// container — the declared structure must consume exactly the supplied bytes —
// so it cannot be evaded by changing keyword casing or archive flavour, and it
// must not fire on arbitrary compressed bytes that merely contain those
// sequences inside legal image structure.
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { validateImageIngress } from '../src/image-ingress.js';
import { analyzePhoto } from '../src/vision-service.js';
const fixture = (name) => fileURLToPath(new URL(`../tests/fixtures/${name}`, import.meta.url));
const readFixture = (name) => readFile(fixture(name));
const dataUrl = (bytes, mime = 'image/jpeg') => `data:${mime};base64,${bytes.toString('base64')}`;
const TRAILING_DATA_FIXTURES = [
['ingress-tail-upper-script.jpg', 'uppercase <SCRIPT> tail'],
['ingress-tail-mixed-script.jpg', 'mixed-case <ScRiPt> tail'],
['ingress-tail-html.jpg', 'generic appended HTML with no script tag'],
['ingress-tail-zip-eocd.jpg', 'ZIP end-of-central-directory tail'],
['ingress-tail-zip-local.jpg', 'ZIP local-header tail'],
['ingress-tail-rar.jpg', 'RAR archive tail'],
['ingress-tail-7z.jpg', '7z archive tail'],
['ingress-tail-gzip.jpg', 'gzip member tail'],
['ingress-tail-single-nul.jpg', 'single appended NUL byte'],
];
test('appended trailing data is rejected regardless of casing or container flavour', async () => {
for (const [name, description] of TRAILING_DATA_FIXTURES) {
const bytes = await readFixture(name);
await assert.rejects(
() => validateImageIngress({ consent: true, imageDataUrl: dataUrl(bytes) }),
(error) => {
assert.match(error.message, /not a supported image|corrupt or malformed/i,
`${description} must be rejected with a sanitized ingress error`);
return true;
},
`${description} must not pass ingress`,
);
}
});
test('trailing data after a PNG IEND chunk is rejected', async () => {
const bytes = await readFixture('ingress-tail-after-iend.png');
await assert.rejects(
() => validateImageIngress({ consent: true, imageDataUrl: dataUrl(bytes, 'image/png') }),
/not a supported image|corrupt or malformed/i,
);
});
test('rejection is structural, not substring scanning: legal images carrying archive and script byte sequences are accepted', async () => {
// A JPEG COM segment legally contains arbitrary bytes. These exact sequences
// are what the old scanner searched for; canonical parsing must still accept.
const buried = await readFixture('ingress-buried-signatures.jpg');
assert.ok(buried.includes(Buffer.from('PK\x03\x04')), 'fixture must contain a ZIP local header sequence');
assert.ok(buried.includes(Buffer.from('PK\x05\x06')), 'fixture must contain a ZIP EOCD sequence');
assert.ok(buried.includes(Buffer.from('<script')), 'fixture must contain a lowercase script sequence');
const result = await validateImageIngress({ consent: true, imageDataUrl: dataUrl(buried) });
assert.equal(result.format, 'jpeg');
assert.ok(result.bytes > 0);
// High-entropy compressed data must not be misclassified either.
const entropy = await readFixture('ingress-entropy-control.jpg');
const entropyResult = await validateImageIngress({ consent: true, imageDataUrl: dataUrl(entropy) });
assert.equal(entropyResult.format, 'jpeg');
});
test('the provider is never reached for any trailing-data polyglot', async () => {
for (const [name] of TRAILING_DATA_FIXTURES) {
const bytes = await readFixture(name);
let providerCalled = false;
await assert.rejects(
() => analyzePhoto({
payload: { consent: true, imageDataUrl: dataUrl(bytes) },
config: { model: 'm', baseUrl: 'http://127.0.0.1:9/v1' },
fetchImpl: async () => { providerCalled = true; throw new Error('provider reached'); },
}),
);
assert.equal(providerCalled, false, `${name} must fail before any provider fetch`);
}
});