51 lines · 2.4 KB
import { describe, expect, mock, test } from "bun:test";

mock.module("cloudflare:workers", () => ({
  env: { SECRETS_KEY: "test-secret", PUBLIC_URL: "https://forge.example.com" },
}));

const { camoUrl, proxiableImageUrl, verifyCamoPath } = await import("./camo-sign");

describe("camo signing", () => {
  test("signs http(s) URLs and verifies them back", () => {
    const path = camoUrl("https://img.example.com/a.png?x=1#frag");
    expect(path).toMatch(/^\/camo\/[0-9a-f]{64}\/[0-9a-f]+$/);
    expect(verifyCamoPath(path ?? "")).toBe("https://img.example.com/a.png?x=1");
  });

  test("never proxies the forge itself and only accepts lowercase hex", () => {
    expect(camoUrl("https://forge.example.com/acme/app/raw/main/x.png")).toBeNull();
    expect(camoUrl("https://forge.example.com./x.png")).toBeNull();
    const path = camoUrl("https://img.example.com/a.png") ?? "";
    expect(verifyCamoPath(path.toUpperCase().replace("/CAMO/", "/camo/"))).toBeNull();
  });

  test("rejects non-http schemes, credentials and overlong URLs", () => {
    expect(camoUrl("javascript:alert(1)")).toBeNull();
    expect(camoUrl("data:image/png;base64,AAAA")).toBeNull();
    expect(camoUrl("file:///etc/passwd")).toBeNull();
    expect(camoUrl("https://user:pw@img.example.com/a.png")).toBeNull();
    expect(camoUrl(`https://img.example.com/${"a".repeat(3000)}`)).toBeNull();
    expect(proxiableImageUrl("/relative.png")).toBeNull();
  });

  test("tampered signature or URL fails verification", () => {
    const path = camoUrl("https://img.example.com/a.png") ?? "";
    const [, , signature, urlHex] = path.split("/");
    const flipped = (signature.startsWith("0") ? "1" : "0") + signature.slice(1);
    expect(verifyCamoPath(`/camo/${flipped}/${urlHex}`)).toBeNull();
    expect(verifyCamoPath(`/camo/${signature}/${urlHex}00`)).toBeNull();
    expect(verifyCamoPath(`/camo/${signature}/${urlHex}/extra`)).toBeNull();
    expect(verifyCamoPath(`/camo/${signature}/zz`)).toBeNull();
    expect(verifyCamoPath("/camo/")).toBeNull();