// 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