Something went wrong. Try again.
A local-first event pipeline for independent agents, built on Jazz.
Something went wrong. Try again.
22 kB · 600 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601import { createHash } from "node:crypto";import fs from "node:fs/promises";import path from "node:path";import { afterEach, describe, expect, test } from "vitest";import { downloadTelegramImage, imageAttachmentMetadata, isImageDocument, resolveImageArtifact, selectPhoto, TELEGRAM_IMAGE_MAX_BYTES, type TelegramPhotoSize, type TelegramFile,} from "../src/connectors/telegram-images.js";import { TELEGRAM_IMAGE_MAX_BASE64_CHARS } from "../src/connectors/telegram-image-contract.js";import { SANDBOX_PROTOCOL_VERSION, sandboxRunPacketSchema } from "../src/agents/sandbox/protocol.js";import { temporaryProject } from "./helpers.js";
const roots: string[] = [];
afterEach(async () => { await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true })));});
// Minimal 1x1 PNG image (67 bytes)const PNG_BYTES = Buffer.from([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0d, 0x0a, 0x2d, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,]);
// Minimal JPEG header + footer (not a valid JPEG but sufficient for magic byte tests)const JPEG_BYTES = Buffer.concat([ Buffer.from([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46]), Buffer.alloc(100, 0x00), Buffer.from([0xff, 0xd9]),]);
const INVALID_BYTES = Buffer.from([0x00, 0x01, 0x02, 0x03]);
describe("selectPhoto", () => { test("returns the largest photo within the byte limit", () => { const photos: TelegramPhotoSize[] = [ { file_id: "small", file_unique_id: "u1", width: 100, height: 100, file_size: 10_000 }, { file_id: "large", file_unique_id: "u2", width: 800, height: 800, file_size: 5_000_000 }, ]; const selected = selectPhoto(photos); expect(selected).toBeDefined(); expect(selected!.file_id).toBe("large"); });
test("returns undefined when all photos exceed the byte limit", () => { const photos: TelegramPhotoSize[] = [ { file_id: "big", file_unique_id: "u1", width: 800, height: 800, file_size: TELEGRAM_IMAGE_MAX_BYTES + 1 }, ]; const selected = selectPhoto(photos); expect(selected).toBeUndefined(); });
test("returns undefined for empty array", () => { expect(selectPhoto([])).toBeUndefined(); });});
describe("isImageDocument", () => { test("returns true for PNG documents", () => { const file: TelegramFile = { file_id: "f1", file_unique_id: "u1", mime_type: "image/png" }; expect(isImageDocument(file)).toBe(true); });
test("returns true for JPEG documents", () => { const file: TelegramFile = { file_id: "f1", file_unique_id: "u1", mime_type: "image/jpeg" }; expect(isImageDocument(file)).toBe(true); });
test("returns false for non-image documents", () => { const file: TelegramFile = { file_id: "f1", file_unique_id: "u1", mime_type: "application/pdf" }; expect(isImageDocument(file)).toBe(false); });
test("returns false for documents without MIME type", () => { const file: TelegramFile = { file_id: "f1", file_unique_id: "u1" }; expect(isImageDocument(file)).toBe(false); });});
describe("downloadTelegramImage", () => { test("downloads, validates, and stores a PNG image content-addressed", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const expectedHash = createHash("sha256").update(PNG_BYTES).digest("hex");
const fetchImpl = mockFetchImpl({ getFile: { file_path: "photos/file_123.png" }, file: PNG_BYTES, });
const artifact = await downloadTelegramImage("file-id-123", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, });
expect(artifact.sha256).toBe(expectedHash); expect(artifact.mimeType).toBe("image/png"); expect(artifact.sizeBytes).toBe(PNG_BYTES.byteLength); expect(artifact.relativePath).toBe(`sha256/${expectedHash.slice(0, 2)}/${expectedHash}`);
// Verify the file was written const written = await fs.readFile(path.join(artifactRoot, artifact.relativePath)); expect(written.equals(PNG_BYTES)).toBe(true); });
test("downloads, validates, and stores a JPEG image content-addressed", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const expectedHash = createHash("sha256").update(JPEG_BYTES).digest("hex");
const fetchImpl = mockFetchImpl({ getFile: { file_path: "photos/file_456.jpg" }, file: JPEG_BYTES, });
const artifact = await downloadTelegramImage("file-id-456", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, });
expect(artifact.sha256).toBe(expectedHash); expect(artifact.mimeType).toBe("image/jpeg"); expect(artifact.sizeBytes).toBe(JPEG_BYTES.byteLength); });
test("rejects JPEG-looking bytes without the terminal EOI marker", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true }); const truncatedJpeg = JPEG_BYTES.subarray(0, -2); const fetchImpl = mockFetchImpl({ getFile: { file_path: "photos/truncated.jpg" }, file: truncatedJpeg }); await expect(downloadTelegramImage("file-truncated", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, })).rejects.toThrow("unsupported or unrecognized format"); });
test("aligns the raw image cap with the sandbox base64 character cap", () => { const data = Buffer.alloc(TELEGRAM_IMAGE_MAX_BYTES).toString("base64"); expect(data.length).toBe(TELEGRAM_IMAGE_MAX_BASE64_CHARS); const packet = { version: SANDBOX_PROTOCOL_VERSION, runId: "run-image-bound", systemPrompt: "system", prompt: "prompt", images: [{ type: "image", data, mimeType: "image/png" }], model: { provider: "tinker", id: "thinkingmachines/Inkling-Small", reasoning: true, acceptsImages: true, jsonObjectResponseFormat: false, contextWindow: 32_000, maxTokens: 1_000, }, broker: { socketPath: "/broker/provider.sock", capability: "a".repeat(64), origin: "http://provider.invalid", route: "/v1/chat/completions", }, }; expect(sandboxRunPacketSchema.safeParse(packet).success).toBe(true); expect(sandboxRunPacketSchema.safeParse({ ...packet, images: [{ ...packet.images[0], data: `${data}A` }], }).success).toBe(false); });
test("rejects unsupported image formats", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const fetchImpl = mockFetchImpl({ getFile: { file_path: "photos/file_bad.bin" }, file: INVALID_BYTES, });
await expect(downloadTelegramImage("file-bad", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, })).rejects.toThrow("unsupported or unrecognized format"); });
test("rejects empty files", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const fetchImpl = mockFetchImpl({ getFile: { file_path: "photos/empty.png" }, file: Buffer.alloc(0), });
await expect(downloadTelegramImage("file-empty", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, })).rejects.toThrow("empty"); });
test("rejects oversize files", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const oversize = Buffer.concat([PNG_BYTES, Buffer.alloc(TELEGRAM_IMAGE_MAX_BYTES + 1, 0x00)]);
const fetchImpl = mockFetchImpl({ getFile: { file_path: "photos/huge.png" }, file: oversize, });
await expect(downloadTelegramImage("file-huge", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, })).rejects.toThrow("exceeds"); });
test("rejects an announced oversize body before reading it", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true }); const fetchImpl = (async (input: RequestInfo | URL) => { const url = String(input); if (url.includes("/getFile")) { return new Response(JSON.stringify({ ok: true, result: { file_path: "photos/huge.png" } }), { status: 200, headers: { "content-type": "application/json" }, }); } return new Response(new Uint8Array(PNG_BYTES), { status: 200, headers: { "content-length": String(TELEGRAM_IMAGE_MAX_BYTES + 1) }, }); }) as typeof fetch; await expect(downloadTelegramImage("file-huge", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, })).rejects.toThrow("exceeds"); });
test("rejects unsafe getFile paths", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true }); const fetchImpl = mockFetchImpl({ getFile: { file_path: "../token-leak.png" }, file: PNG_BYTES }); await expect(downloadTelegramImage("file-unsafe", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, })).rejects.toThrow("invalid file path"); });
test("rejects redirects outside the exact Bot API file boundary", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true }); const fetchImpl = (async (input: RequestInfo | URL) => { const url = String(input); if (url.includes("/getFile")) { return new Response(JSON.stringify({ ok: true, result: { file_path: "photos/image.png" } }), { status: 200, headers: { "content-type": "application/json" }, }); } return new Response(null, { status: 302, headers: { location: "https://example.com/image.png" } }); }) as typeof fetch; await expect(downloadTelegramImage("file-redirect", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, })).rejects.toThrow("trusted Bot API file boundary"); });
test("rejects a symlinked artifact root before writing bytes", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const target = path.join(project, "real-artifacts"); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(target); await fs.symlink(target, artifactRoot); const fetchImpl = mockFetchImpl({ getFile: { file_path: "photos/image.png" }, file: PNG_BYTES }); await expect(downloadTelegramImage("file-symlink-root", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, })).rejects.toThrow(/real directory|symlinked parents/); expect(await fs.readdir(target)).toEqual([]); });
test("rejects symlinked content-addressing parents before writing bytes", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); const escape = path.join(project, "escape"); await fs.mkdir(artifactRoot); await fs.mkdir(escape); await fs.symlink(escape, path.join(artifactRoot, "sha256")); const fetchImpl = mockFetchImpl({ getFile: { file_path: "photos/image.png" }, file: PNG_BYTES }); await expect(downloadTelegramImage("file-symlink-parent", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, })).rejects.toThrow(/real directory|symlinked parents/); expect(await fs.readdir(escape)).toEqual([]); });
test("is idempotent: downloading the same image twice writes the same file", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const fetchImpl = mockFetchImpl({ getFile: { file_path: "photos/file_123.png" }, file: PNG_BYTES, });
const first = await downloadTelegramImage("file-id-123", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, });
const second = await downloadTelegramImage("file-id-123", { baseUrl: "https://api.telegram.org", token: "test-token", artifactRoot, fetchImpl, });
expect(first.relativePath).toBe(second.relativePath); expect(first.sha256).toBe(second.sha256); });});
describe("resolveImageArtifact", () => { test("resolves a valid artifact to base64 ImageContent", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const hash = createHash("sha256").update(PNG_BYTES).digest("hex"); const relativePath = `sha256/${hash.slice(0, 2)}/${hash}`; const destination = path.join(artifactRoot, relativePath); await fs.mkdir(path.dirname(destination), { recursive: true }); await fs.writeFile(destination, PNG_BYTES);
const image = await resolveImageArtifact({ path: relativePath, sha256: hash, mimeType: "image/png", sizeBytes: PNG_BYTES.byteLength, }, artifactRoot);
expect(image.type).toBe("image"); expect(image.mimeType).toBe("image/png"); expect(image.data).toBe(PNG_BYTES.toString("base64")); });
test("rejects path traversal attempts", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
await expect(resolveImageArtifact({ path: "../../../etc/passwd", sha256: "fake", mimeType: "image/png", sizeBytes: 100, }, artifactRoot)).rejects.toThrow("path escape"); });
test("rejects non-normalized paths", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
await expect(resolveImageArtifact({ path: "sha256/ab/abc/../../bad", sha256: "fake", mimeType: "image/png", sizeBytes: 100, }, artifactRoot)).rejects.toThrow(); });
test("rejects paths outside the sha256 directory", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
await expect(resolveImageArtifact({ path: "other/dir/file.png", sha256: "fake", mimeType: "image/png", sizeBytes: 100, }, artifactRoot)).rejects.toThrow("content-addressed directory"); });
test("rejects missing files", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
await expect(resolveImageArtifact({ path: "sha256/ab/abc123", sha256: "abc123", mimeType: "image/png", sizeBytes: 100, }, artifactRoot)).rejects.toThrow("missing"); });
test("rejects hash mismatch", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const hash = createHash("sha256").update(PNG_BYTES).digest("hex"); const relativePath = `sha256/${hash.slice(0, 2)}/${hash}`; const destination = path.join(artifactRoot, relativePath); await fs.mkdir(path.dirname(destination), { recursive: true }); await fs.writeFile(destination, PNG_BYTES);
await expect(resolveImageArtifact({ path: relativePath, sha256: "wronghash", mimeType: "image/png", sizeBytes: PNG_BYTES.byteLength, }, artifactRoot)).rejects.toThrow("hash mismatch"); });
test("rejects size mismatch", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const hash = createHash("sha256").update(PNG_BYTES).digest("hex"); const relativePath = `sha256/${hash.slice(0, 2)}/${hash}`; const destination = path.join(artifactRoot, relativePath); await fs.mkdir(path.dirname(destination), { recursive: true }); await fs.writeFile(destination, PNG_BYTES);
await expect(resolveImageArtifact({ path: relativePath, sha256: hash, mimeType: "image/png", sizeBytes: PNG_BYTES.byteLength + 1, }, artifactRoot)).rejects.toThrow("size mismatch"); });
test("rejects MIME mismatch", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true });
const hash = createHash("sha256").update(PNG_BYTES).digest("hex"); const relativePath = `sha256/${hash.slice(0, 2)}/${hash}`; const destination = path.join(artifactRoot, relativePath); await fs.mkdir(path.dirname(destination), { recursive: true }); await fs.writeFile(destination, PNG_BYTES);
await expect(resolveImageArtifact({ path: relativePath, sha256: hash, mimeType: "image/jpeg", sizeBytes: PNG_BYTES.byteLength, }, artifactRoot)).rejects.toThrow("MIME mismatch"); });
test("rejects a symlink at the expected content address", async () => { const project = await temporaryProject("telegram-image-test-"); roots.push(project); const artifactRoot = path.join(project, "artifacts"); await fs.mkdir(artifactRoot, { recursive: true }); const hash = createHash("sha256").update(PNG_BYTES).digest("hex"); const relativePath = `sha256/${hash.slice(0, 2)}/${hash}`; const destination = path.join(artifactRoot, relativePath); const target = path.join(project, "target.png"); await fs.mkdir(path.dirname(destination), { recursive: true }); await fs.writeFile(target, PNG_BYTES); await fs.symlink(target, destination);
await expect(resolveImageArtifact({ path: relativePath, sha256: hash, mimeType: "image/png", sizeBytes: PNG_BYTES.byteLength, }, artifactRoot)).rejects.toThrow("symlink"); });});
describe("imageAttachmentMetadata", () => { test("produces bounded metadata with opaque reference only", () => { const metadata = imageAttachmentMetadata({ relativePath: "sha256/ab/abc123", sha256: "abc123", mimeType: "image/png", sizeBytes: 1024, });
expect(metadata).toEqual({ kind: "image", status: "stored", mimeType: "image/png", sizeBytes: 1024, sha256: "abc123", artifactPath: "sha256/ab/abc123", });
// Must never contain raw bytes, base64, token, URL, or absolute path expect(metadata).not.toHaveProperty("data"); expect(metadata).not.toHaveProperty("token"); expect(metadata).not.toHaveProperty("url"); expect(String(metadata.artifactPath)).not.toContain(".."); expect(String(metadata.artifactPath)).not.toMatch(/^\//); });});
/** * Create a mock fetch implementation that simulates the Telegram Bot API: * - POST /bot{token}/getFile returns the file path * - GET /file/bot{token}/{file_path} returns the file bytes */function mockFetchImpl(options: { getFile: { file_path: string }; file: Buffer;}): typeof fetch { const { getFile: getFileResponse, file } = options; return (async (input: RequestInfo | URL, init?: RequestInit) => { const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; const method = init?.method ?? "GET";
if (url.includes("/getFile")) { return new Response(JSON.stringify({ ok: true, result: { file_path: getFileResponse.file_path }, }), { status: 200, headers: { "content-type": "application/json" } }); }
if (url.includes("/file/bot")) { return new Response(new Uint8Array(file), { status: 200, headers: { "content-type": "application/octet-stream" } }); }
return new Response("not found", { status: 404 }); }) as typeof fetch;}