Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

61 lines · 2.3 KBRaw History
1import { deflateSync } from "node:zlib";
2import { describe, expect, it } from "vitest";
3import { applyDelta, parsePack, sha1 } from "../src/index.js";
4
5/** A pack from raw entries: [type code, declared size, body (already deflated, with any delta base prefix)]. */
6async function pack(entries: Array<[number, number, Uint8Array]>): Promise<Uint8Array> {
7 const parts: number[] = [..."PACK"].map((c) => c.charCodeAt(0));
8 const u32 = (n: number) => parts.push((n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255);
9 u32(2);
10 u32(entries.length);
11 for (const [type, size, body] of entries) {
12 let s = size;
13 let c = (type << 4) | (s & 15);
14 s = Math.floor(s / 16);
15 while (s > 0) {
16 parts.push(c | 0x80);
17 c = s & 0x7f;
18 s = Math.floor(s / 128);
19 }
20 parts.push(c);
21 parts.push(...body);
22 }
23 const body = new Uint8Array(parts);
24 const out = new Uint8Array(body.length + 20);
25 out.set(body);
26 out.set(await sha1(body), body.length);
27 return out;
28}
29const varint = (n: number) => {
30 const out: number[] = [];
31 do {
32 let b = n % 128;
33 n = Math.floor(n / 128);
34 if (n > 0) b |= 0x80;
35 out.push(b);
36 } while (n > 0);
37 return out;
38};
39
40describe("untrusted packs can't make the server inflate or allocate without bound", () => {
41 it("stops inflating an object at its declared size", async () => {
42 // Declares 10 bytes, inflates to 64 MiB.
43 const bomb = deflateSync(Buffer.alloc(64 * 1024 * 1024));
44 await expect(parsePack(await pack([[3, 10, bomb]]))).rejects.toThrow(/larger than its declared size/);
45 });
46
47 it("refuses a pack whose objects unpack past the budget", async () => {
48 const blob = Buffer.alloc(600 * 1024, 120);
49 const p = await pack([
50 [3, blob.length, deflateSync(blob)],
51 [3, blob.length, deflateSync(Buffer.concat([blob.subarray(1), Buffer.from("y")]))],
52 ]);
53 await expect(parsePack(p, undefined, { maxBytes: 1024 * 1024 })).rejects.toThrow(/unpacks to more than/);
54 expect((await parsePack(p, undefined, { maxBytes: 2 * 1024 * 1024 })).length).toBe(2);
55 });
56
57 it("checks a delta's result size before allocating it", () => {
58 const delta = Uint8Array.from([...varint(3), ...varint(2 ** 40), 0x91, 0, 3]);
59 expect(() => applyDelta(Uint8Array.from([1, 2, 3]), delta, 1024 * 1024)).toThrow(/larger than/);
60 });
61});