/** * Phase 7.2 R1 Agent C (H2) — BundleManifest schema validation tests. * * Covers the primary Medium-severity finding from the Phase 7.1 audit * (`docs/security/phase-7-1-audit.md` §M-02): tampered manifest injection * via `shared.fastRefresh.{glue,runtime}` URL replacement. The schema * now rejects such tampers at load time; the contract asserted below * pins the precise shape so regressions fail fast. * * References: * docs/security/phase-7-1-audit.md §M-02 * docs/bun/phase-7-2-team-plan.md §3 Agent C H2 * packages/core/src/bundler/manifest-schema.ts */ import { describe, expect, test } from "bun:test"; import { BundleManifestSchema, MAX_MANIFEST_URL_LEN, ManifestValidationError, SAFE_MANDU_URL_REGEX, isSafeManduUrl, safeValidateBundleManifest, validateBundleManifest, } from "../manifest-schema"; /** Known-good manifest, used as the basis for mutation tests. */ const VALID_MANIFEST = { version: 1, buildTime: "2026-04-19T00:00:00.000Z", env: "development" as const, bundles: { page: { js: "/.mandu/client/page.js", dependencies: [], priority: "visible" as const, }, }, shared: { runtime: "/.mandu/client/_runtime.js", vendor: "/.mandu/client/_vendor-react.js", fastRefresh: { runtime: "/.mandu/client/_fast-refresh-runtime.js", glue: "/.mandu/client/_vendor-react-refresh.js", }, }, }; // ═══════════════════════════════════════════════════════════════════ // Section A — primitive URL predicate // ═══════════════════════════════════════════════════════════════════ describe("manifest-schema — isSafeManduUrl", () => { test("[A1] accepts bundler-shape URLs", () => { expect(isSafeManduUrl("/.mandu/client/page.js")).toBe(true); expect(isSafeManduUrl("/.mandu/client/_vendor-react.js")).toBe(true); expect(isSafeManduUrl("/.mandu/client/nested/island.js")).toBe(true); expect(isSafeManduUrl("/.mandu/client/styles.css")).toBe(true); }); test("[A2] rejects absolute URLs with protocol", () => { expect(isSafeManduUrl("https://evil.example.com/steal.js")).toBe(false); expect(isSafeManduUrl("http://evil.example.com/x.js")).toBe(false); expect(isSafeManduUrl("data:application/javascript,alert(1)")).toBe(false); expect(isSafeManduUrl("javascript:alert(1)")).toBe(false); }); test("[A3] rejects path traversal", () => { expect(isSafeManduUrl("/.mandu/client/../secret.js")).toBe(false); expect(isSafeManduUrl("/.mandu/client/a/../b.js")).toBe(false); expect(isSafeManduUrl("/.mandu/..//client/page.js")).toBe(false); }); test("[A4] rejects empty / overlong / wrong-shape", () => { expect(isSafeManduUrl("")).toBe(false); expect(isSafeManduUrl("x".repeat(MAX_MANIFEST_URL_LEN + 1))).toBe(false); expect(isSafeManduUrl("/not-mandu/client/page.js")).toBe(false); expect(isSafeManduUrl("/.mandu/client/page.txt")).toBe(false); expect(isSafeManduUrl("page.js")).toBe(false); // relative }); test("[A5] rejects control / special characters", () => { expect(isSafeManduUrl("/.mandu/client/page\n.js")).toBe(false); expect(isSafeManduUrl("/.mandu/client/page