proxy-machine-protocol.test.ts
Rama main · réplica de lectura del repositorio original.
import { afterEach, describe, expect, it, vi } from "vitest";
import { NextRequest } from "next/server";
const TEMPLATE = "{id}.mso.example.com";
async function loadProxy(template: string) {
vi.resetModules();
vi.stubEnv("NEXT_PUBLIC_MANAGED_APP_HOST_TEMPLATE", template);
return (await import("./proxy")).proxy;
}
function req(
host: string,
path: string,
init: { method?: string; headers?: HeadersInit; body?: string } = {},
): NextRequest {
const headers = new Headers(init.headers);
headers.set("host", host);
return new NextRequest(`https://${host}${path}`, { ...init, headers });
}
const rewriteOf = (res: Response) => res.headers.get("x-middleware-rewrite");
afterEach(() => {
vi.unstubAllEnvs();
});
// The session cookie is widened to Domain=mso.example.com, so ANY host under that
// name receives it. A host in the namespace that is not one of the apps must
// therefore 404 rather than serve the cockpit — otherwise adding a DNS record is
// enough to hand a page a fully authenticated cockpit, with no code change at all.
describe("an unclaimed host inside the app namespace serves nothing", () => {
it.each([
"staging.mso.example.com",
"deep.sub.mso.example.com",
"xhermes.mso.example.com",
])("404s %s", async (host) => {
const proxy = await loadProxy(TEMPLATE);
for (const path of [
"/",
"/api/v1/exec/run",
"/api/auth/login",
"/_next/static/chunks/main.js",
]) {
const res = await proxy(req(host, path));
expect(res.status).toBe(404);
expect(rewriteOf(res)).toBeNull();
}
});
it("refuses only the namespace: the cockpit, its siblings and single-origin mode all still serve", async () => {
const split = await loadProxy(TEMPLATE);
for (const host of [
"mso.example.com",
"api-mso.example.com",
"rahmanef.com",
]) {
expect((await split(req(host, "/files"))).status).toBe(200);
}
const single = await loadProxy("");
expect(
(await single(req("staging.mso.example.com", "/files"))).status,
).toBe(200);
});
});
describe("single-origin mode (template unset) is unchanged", () => {
it("stamps a nonce + document policy on a page", async () => {
const proxy = await loadProxy("");
const res = await proxy(req("mso.example.com", "/files"));
const csp = res.headers.get("content-security-policy")!;
expect(csp).toContain("frame-ancestors 'none'");
expect(csp).toMatch(/script-src 'self' 'nonce-[^']+' 'strict-dynamic'/);
expect(res.headers.get("x-middleware-request-x-nonce")).toBeTruthy();
});
it("waves through the paths the matcher used to exclude, with no policy", async () => {
const proxy = await loadProxy("");
for (const path of [
"/_next/static/chunks/main.js",
"/_next/image?url=%2Fx.png",
"/favicon.ico",
]) {
const res = await proxy(req("mso.example.com", path));
expect(res.status).toBe(200);
expect(res.headers.get("content-security-policy")).toBeNull();
expect(res.headers.get("x-middleware-request-x-nonce")).toBeNull();
}
});
it("never treats an app-shaped host as an app host", async () => {
const proxy = await loadProxy("");
const res = await proxy(req("hermes.mso.example.com", "/"));
expect(rewriteOf(res)).toBeNull();
});
it("still blocks a cross-site mutating /api and leaves reads alone", async () => {
const proxy = await loadProxy("");
const blocked = await proxy(
req("mso.example.com", "/api/v1/exec/run", {
method: "POST",
headers: { "sec-fetch-site": "cross-site" },
}),
);
expect(blocked.status).toBe(403);
const read = await proxy(req("mso.example.com", "/api/v1/sys/status"));
expect(read.status).toBe(200);
expect(read.headers.get("content-security-policy")).toBeNull();
});
it("treats A2A discovery and protocol routes as machine surfaces, not documents", async () => {
const proxy = await loadProxy("");
for (const path of ["/.well-known/agent-card.json", "/a2a/v1"]) {
const res = await proxy(req("mso.example.com", path));
expect(res.status).toBe(200);
expect(res.headers.get("content-security-policy")).toBeNull();
expect(res.headers.get("x-middleware-request-x-nonce")).toBeNull();
}
});
it("lets remote bearer-authenticated A2A POST reach the route while keeping owner A2A management CSRF-protected", async () => {
const proxy = await loadProxy("");
const protocol = await proxy(
req("mso.example.com", "/a2a/v1", {
method: "POST",
headers: { "sec-fetch-site": "cross-site" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "ListTasks",
params: {},
}),
}),
);
expect(protocol.status).toBe(200);
expect(protocol.headers.get("content-security-policy")).toBeNull();
const management = await proxy(
req("mso.example.com", "/api/v1/a2a", {
method: "POST",
headers: { "sec-fetch-site": "cross-site" },
body: JSON.stringify({ action: "state" }),
}),
);
expect(management.status).toBe(403);
expect(await management.json()).toEqual({ error: "cross_origin_blocked" });
});
// Full coverage of this bridge (forged cookie, revoked device, upgrade, off-box
// destination) lives in proxy-websocket.test.ts, which has the session harness.
it("does not let an unverified cookie near the camoufox VNC bridge", async () => {
const proxy = await loadProxy("");
const res = await proxy(
req("mso.example.com", "/camoufox-vnc/", {
headers: { cookie: "session=anything" },
}),
);
expect(res.status).toBe(404);
expect(res.headers.get("x-middleware-rewrite")).toBeNull();
});
it("allows cross-origin workflow webhook triggers while keeping workflow management CSRF-protected", async () => {
const proxy = await loadProxy("");
const webhook = await proxy(
req("mso.example.com", "/api/v1/workflows/webhook/graph-1/hook", {
method: "POST",
headers: { "sec-fetch-site": "cross-site", "content-type": "application/json" },
body: JSON.stringify({ event: "smoke" }),
}),
);
expect(webhook.status).toBe(200);
const management = await proxy(
req("mso.example.com", "/api/v1/workflows", {
method: "POST",
headers: { "sec-fetch-site": "cross-site", "content-type": "application/json" },
body: JSON.stringify({ action: "create" }),
}),
);
expect(management.status).toBe(403);
expect(await management.json()).toEqual({ error: "cross_origin_blocked" });
});
});