// SPDX-License-Identifier: 0BSD import { describe, it, expect, vi, beforeEach } from "vitest"; import App from "../../meshchatx/src/frontend/components/App.vue"; import WebSocketConnection from "../../meshchatx/src/frontend/js/WebSocketConnection"; import ToastUtils from "../../meshchatx/src/frontend/js/ToastUtils"; vi.mock("../../meshchatx/src/frontend/js/ToastUtils", () => ({ default: { success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn(), }, })); vi.mock("../../meshchatx/src/frontend/js/WebSocketConnection", () => ({ default: { send: vi.fn(), connect: vi.fn(), on: vi.fn(), off: vi.fn(), destroy: vi.fn(), }, })); describe("meshchatx://docs deep links (security / fuzz)", () => { beforeEach(() => { vi.clearAllMocks(); }); it("routes only hostname docs, not docs- prefix spoof", () => { const push = vi.fn(); App.methods.handleProtocolLink.call({ $router: { push } }, "meshchatx://docs-foo?reticulum=evil"); expect(push).not.toHaveBeenCalled(); }); it("accepts meshchat alias and path-style manual path", () => { const push = vi.fn(); App.methods.handleProtocolLink.call({ $router: { push } }, "meshchat://docs/manual/interfaces.html"); expect(push).toHaveBeenCalledWith({ name: "documentation", query: { reticulum: encodeURIComponent("manual/interfaces.html") }, }); }); it("passes XSS-shaped reticulum through encodeURIComponent only (opaque to router)", () => { const push = vi.fn(); const malicious = ""; App.methods.handleProtocolLink.call( { $router: { push } }, `meshchatx://docs?reticulum=${encodeURIComponent(malicious)}` ); expect(push).toHaveBeenCalledWith({ name: "documentation", query: { reticulum: encodeURIComponent(malicious) }, }); }); it("does not treat javascript: prefix as docs link", () => { const push = vi.fn(); App.methods.handleProtocolLink.call({ $router: { push } }, "javascript:meshchatx://docs?reticulum=x"); expect(push).not.toHaveBeenCalledWith(expect.objectContaining({ name: "documentation" })); }); it("fuzz random query tails without throwing", () => { const push = vi.fn(); for (let i = 0; i < 40; i++) { const tail = `x=${encodeURIComponent(`${i}\u0000