import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import JSZip from "jszip"; import { scanBundle, type ScanFinding } from "../bundler/bundle-scanner.js"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- const MINIMAL_MANIFEST = { format_version: "2", name: "test-app", created_at: "2025-01-01T00:00:00Z", created_by: "test", entry: "index.html", capabilities: [], }; let tempDir: string; beforeAll(async () => { tempDir = await mkdtemp(join(tmpdir(), "bundle-scanner-test-")); }); afterAll(async () => { await rm(tempDir, { recursive: true, force: true }); }); async function createBundle( files: Record, manifest?: Record, ): Promise { const zip = new JSZip(); const m = manifest ?? MINIMAL_MANIFEST; zip.file("manifest.json", JSON.stringify(m)); for (const [name, content] of Object.entries(files)) { if (typeof content === "string") { zip.file(name, content); } else { zip.file(name, content); } } const data = await zip.generateAsync({ type: "uint8array" }); const path = join( tempDir, `test-${Date.now()}-${Math.random().toString(36).slice(2)}.vellum`, ); await Bun.write(path, data); return path; } function findByCode( findings: ScanFinding[], code: string, ): ScanFinding | undefined { return findings.find((f) => f.code === code); } // --------------------------------------------------------------------------- // Format version gate (block) // --------------------------------------------------------------------------- describe("format_version gate", () => { test("blocks legacy format_version 1 bundles", async () => { const path = await createBundle( { "index.html": "Hello" }, { ...MINIMAL_MANIFEST, format_version: 1 }, ); const result = await scanBundle(path); const f = findByCode(result.findings, "unsupported_format"); expect(f).toBeDefined(); expect(f!.level).toBe("block"); expect(result.passed).toBe(false); }); test("passes format_version 2 bundles", async () => { const path = await createBundle({ "index.html": "Hello", }); const result = await scanBundle(path); expect(findByCode(result.findings, "unsupported_format")).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // SVG ', }); const result = await scanBundle(path); const f = findByCode(result.findings, "svg_script"); expect(f).toBeDefined(); expect(f!.level).toBe("block"); }); test("passes clean SVG without script", async () => { const path = await createBundle({ "index.html": "Hello", "assets/icon.svg": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "svg_script"); expect(f).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // iframe srcdoc (block) // --------------------------------------------------------------------------- describe("iframe srcdoc", () => { test("blocks iframe with srcdoc attribute", async () => { const path = await createBundle({ "index.html": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "iframe_srcdoc"); expect(f).toBeDefined(); expect(f!.level).toBe("block"); }); test("passes HTML without srcdoc", async () => { const path = await createBundle({ "index.html": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "iframe_srcdoc"); expect(f).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // formaction attribute (block) // --------------------------------------------------------------------------- describe("formaction attribute", () => { test("blocks formaction with external URL", async () => { const path = await createBundle({ "index.html": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "formaction_external"); expect(f).toBeDefined(); expect(f!.level).toBe("block"); }); test("passes form without formaction", async () => { const path = await createBundle({ "index.html": '
', }); const result = await scanBundle(path); const f = findByCode(result.findings, "formaction_external"); expect(f).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // HTML event handler attributes (warn) // --------------------------------------------------------------------------- describe("HTML event handlers", () => { test("warns on onerror attribute", async () => { const path = await createBundle({ "index.html": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "html_event_handler"); expect(f).toBeDefined(); expect(f!.level).toBe("warn"); }); test("warns on onclick attribute", async () => { const path = await createBundle({ "index.html": '
Click
', }); const result = await scanBundle(path); const f = findByCode(result.findings, "html_event_handler"); expect(f).toBeDefined(); expect(f!.level).toBe("warn"); }); test("passes HTML without event handlers", async () => { const path = await createBundle({ "index.html": '
Safe
', }); const result = await scanBundle(path); const f = findByCode(result.findings, "html_event_handler"); expect(f).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // CSS @import (warn) // --------------------------------------------------------------------------- describe("CSS @import", () => { test("warns on @import url()", async () => { const path = await createBundle({ "index.html": "", }); const result = await scanBundle(path); const f = findByCode(result.findings, "css_import"); expect(f).toBeDefined(); expect(f!.level).toBe("warn"); }); test("warns on @import with quoted URL", async () => { const path = await createBundle({ "index.html": "", }); const result = await scanBundle(path); const f = findByCode(result.findings, "css_import"); expect(f).toBeDefined(); }); test("passes without CSS @import", async () => { const path = await createBundle({ "index.html": "", }); const result = await scanBundle(path); const f = findByCode(result.findings, "css_import"); expect(f).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // CSS url() with external URLs (warn) // --------------------------------------------------------------------------- describe("CSS external url()", () => { test("warns on url() with https", async () => { const path = await createBundle({ "index.html": "", }); const result = await scanBundle(path); const f = findByCode(result.findings, "css_external_url"); expect(f).toBeDefined(); expect(f!.level).toBe("warn"); }); test("passes url() with local path", async () => { const path = await createBundle({ "index.html": "", }); const result = await scanBundle(path); const f = findByCode(result.findings, "css_external_url"); expect(f).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // data: URI in src/href (warn) // --------------------------------------------------------------------------- describe("data: URI", () => { test("warns on script src with data: URI", async () => { const path = await createBundle({ "index.html": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "data_uri"); expect(f).toBeDefined(); expect(f!.level).toBe("warn"); }); test("passes normal script src", async () => { const path = await createBundle({ "index.html": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "data_uri"); expect(f).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // javascript: URI (warn) // --------------------------------------------------------------------------- describe("javascript: URI", () => { test("warns on href with javascript: URI", async () => { const path = await createBundle({ "index.html": 'Click', }); const result = await scanBundle(path); const f = findByCode(result.findings, "javascript_uri"); expect(f).toBeDefined(); expect(f!.level).toBe("warn"); }); test("passes normal href", async () => { const path = await createBundle({ "index.html": 'Link', }); const result = await scanBundle(path); const f = findByCode(result.findings, "javascript_uri"); expect(f).toBeUndefined(); }); }); // --------------------------------------------------------------------------- // SVG event handlers (warn) // --------------------------------------------------------------------------- describe("SVG event handlers", () => { test("warns on SVG with onload handler", async () => { const path = await createBundle({ "index.html": "Hello", "assets/icon.svg": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "svg_event_handler"); expect(f).toBeDefined(); expect(f!.level).toBe("warn"); }); test("warns on SVG element with onclick handler", async () => { const path = await createBundle({ "index.html": "Hello", "assets/icon.svg": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "svg_event_handler"); expect(f).toBeDefined(); }); test("passes clean SVG without event handlers", async () => { const path = await createBundle({ "index.html": "Hello", "assets/icon.svg": '', }); const result = await scanBundle(path); const f = findByCode(result.findings, "svg_event_handler"); expect(f).toBeUndefined(); }); });