// MV-4: decompression-bomb output bounds on the zstd and pako legs. import { describe, expect, it } from "vitest"; import { deflate, inflate } from "pako"; import { compressZstd, decompressZstd } from "./zstd"; import { IMPORT_LIMITS } from "../shared/import-limits"; import { inflateBounded } from "../popup/codex/generate"; describe("MV-4 decompressZstd output cap", () => { it("throws IMPORT_TOO_LARGE when the decompressed output exceeds the cap", async () => { // Zeros compress to a tiny frame but decompress past the 64 MiB ceiling. const huge = new Uint8Array(IMPORT_LIMITS.MAX_DECOMPRESSED_BYTES + 1); const compressed = await compressZstd(huge); expect(compressed.byteLength).toBeLessThan(huge.byteLength); await expect(decompressZstd(compressed)).rejects.toThrow(/IMPORT_TOO_LARGE/); }); it("decompresses a normal payload unchanged (cap does not break legit use)", async () => { const payload = new TextEncoder().encode("a normal little vault section"); const round = await decompressZstd(await compressZstd(payload)); expect(round).toEqual(payload); }); }); describe("MV-4 inflateBounded", () => { it("throws when inflated output exceeds the supplied ceiling (deflate bomb)", () => { // 8 MiB of zeros deflates tiny; cap it at 1 MiB to trip the ceiling. const bomb = deflate(new Uint8Array(8 * 1024 * 1024)); expect(bomb.length).toBeLessThan(1024 * 1024); expect(() => inflateBounded(bomb, 1024 * 1024)).toThrow(/IMPORT_TOO_LARGE/); }); it("returns bytes identical to one-shot inflate for an in-bounds payload", () => { const original = new TextEncoder().encode( JSON.stringify({ entries: Array.from({ length: 200 }, (_, i) => ({ id: i })) }), ); const compressed = deflate(original); const bounded = inflateBounded(compressed, IMPORT_LIMITS.MAX_DECOMPRESSED_BYTES); expect(bounded).toEqual(inflate(compressed)); expect(bounded).toEqual(original); }); });