import { describe, expect, it, vi } from "vitest"; import { BrowserFilePolicyRegistry, browserFilePolicyReference, } from "../../src/adapters/browser-files/browser-file-policy-registry.ts"; import { BrowserFileVault } from "../../src/adapters/browser-files/browser-file-vault.ts"; import { BrowserTransientPreview, ObjectUrlLeaseRegistry, } from "../../src/adapters/browser-files/object-url-lease.ts"; const hardLimits = Object.freeze({ maxInspectionBytes: 64, maxRetainedFileBytes: 1_024, maxPreviewBytes: 1_024, maxObjectUrlBytes: 1_024, maxTransferBytes: 1_024, }); describe("browser file composition policy registry", () => { it("deep-snapshots every policy axis and permits only limit reductions", () => { const reference = browserFilePolicyReference( "documents", "select-preview-download-pdf", ); const selection = { policyId: "documents-v1", purpose: "document", classification: "PERSONAL" as const, multiple: false, maxCount: 1, maxFileBytes: 100, maxTotalBytes: 100, allowEmpty: false, accept: [ { mediaType: "application/pdf", extensions: [".pdf"] }, ], }; const inspection = { policyId: "documents-v1", maxInspectionBytes: 8, acceptedSignatures: [ { mediaType: "application/pdf", extensions: [".pdf"], patterns: [{ offset: 0, bytes: [0x25, 0x50] }], }, ], }; const preview = { allowedMediaTypes: ["application/pdf"], maxPreviewBytes: 100, }; const download = { strategy: "PROMPT_AND_STREAM" as const, mediaType: "application/pdf", safeExtension: ".pdf", maxTransferBytes: 100, maxBufferedBytes: 50, integrity: "REQUIRED" as const, }; const registry = new BrowserFilePolicyRegistry({ profiles: [ { reference, selection, inspection, preview, download, }, ], hardLimits, }); selection.maxFileBytes = 1; selection.accept[0]!.extensions[0] = ".exe"; inspection.acceptedSignatures[0]!.patterns[0]!.bytes[0] = 0; preview.allowedMediaTypes[0] = "text/html"; ( download as { strategy: string } ).strategy = "BROWSER_MANAGED"; download.safeExtension = ".exe"; expect( registry.resolveSelection(reference, { maxFileBytes: 80, }), ).toMatchObject({ ok: true, value: { maxFileBytes: 80, accept: [{ extensions: [".pdf"] }], }, }); expect( registry.resolveSelection(reference, { maxFileBytes: 101, }), ).toMatchObject({ ok: false, error: { code: "LIMIT_EXCEEDED" }, }); expect(registry.resolveInspection(reference)).toMatchObject({ ok: true, value: { acceptedSignatures: [ { patterns: [{ bytes: [0x25, 0x50] }] }, ], }, }); const resolvedPreview = registry.resolvePreview(reference); expect(resolvedPreview.ok).toBe(true); if (resolvedPreview.ok) { expect( resolvedPreview.value.allowedMediaTypes.has( "application/pdf", ), ).toBe(true); expect( resolvedPreview.value.allowedMediaTypes.has("text/html"), ).toBe(false); ( resolvedPreview.value.allowedMediaTypes as Set ).clear(); expect( registry.resolvePreview(reference), ).toMatchObject({ ok: true, value: { allowedMediaTypes: new Set(["application/pdf"]), }, }); } expect( registry.resolveDownload(reference, {}), ).toMatchObject({ ok: true, value: { strategy: "PROMPT_AND_STREAM", safeExtension: ".pdf", }, }); expect( registry.resolveDownload( browserFilePolicyReference("unknown", "unknown"), {}, ), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect( registry.resolveSelection( browserFilePolicyReference( "documents", "select-preview-download-pdf", ), ), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect( registry.resolveSelection( { policyKey: "documents", intention: "select-preview-download-pdf", } as typeof reference, ), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); }); it("binds verification receipts to the exact profile, not a reused policyId", async () => { const profileA = browserFilePolicyReference( "images-a", "preview-avatar", ); const profileB = browserFilePolicyReference( "images-b", "preview-banner", ); const inspection = { policyId: "shared-png-v1", maxInspectionBytes: 8, acceptedSignatures: [ { mediaType: "image/png", extensions: [".png"], patterns: [{ offset: 0, bytes: [0x89, 0x50] }], }, ], }; const policies = new BrowserFilePolicyRegistry({ profiles: [profileA, profileB].map((reference) => ({ reference, inspection, preview: { allowedMediaTypes: ["image/png"], maxPreviewBytes: 16, }, })), hardLimits, }); const vault = new BrowserFileVault({ policies, createReference: () => "file:profile-bound", createVerificationReceipt: () => "verification:profile-bound", }); const captured = vault.captureFiles( [ new File( [new Uint8Array([0x89, 0x50, 1])], "avatar.png", { type: "image/png", lastModified: 1 }, ), ], { policyId: "capture-v1", purpose: "capture", classification: "PERSONAL", multiple: false, maxCount: 1, maxFileBytes: 16, maxTotalBytes: 16, allowEmpty: false, accept: [ { mediaType: "image/png", extensions: [".png"] }, ], }, ); expect(captured.ok).toBe(true); if (!captured.ok || !captured.value[0]) return; const inspected = await vault.inspect({ ref: captured.value[0].ref, policy: profileA, signal: new AbortController().signal, }); expect(inspected.ok).toBe(true); if (!inspected.ok || !inspected.value.verificationReceipt) { return; } const createObjectURL = vi.fn(() => "blob:profile-bound"); const previews = new BrowserTransientPreview({ files: vault, policies, hardMaxPreviewBytes: 16, leases: new ObjectUrlLeaseRegistry({ createObjectURL, revokeObjectURL() {}, }), }); expect( await previews.create({ ref: captured.value[0].ref, verificationReceipt: inspected.value.verificationReceipt, policy: profileB, signal: new AbortController().signal, }), ).toMatchObject({ ok: false, error: { code: "POLICY_REJECTED" }, }); expect(createObjectURL).not.toHaveBeenCalled(); expect( await previews.create({ ref: captured.value[0].ref, verificationReceipt: inspected.value.verificationReceipt, policy: profileA, signal: new AbortController().signal, }), ).toMatchObject({ ok: true }); }); });